humanish 0.139.1 → 0.141.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 +23 -0
- package/README.md +5 -4
- package/dist/actors/codex/restricted-participant.js +1 -1
- package/dist/actors/computer-use/external-handoff.d.ts +22 -0
- package/dist/actors/computer-use/external-handoff.js +83 -0
- package/dist/actors/computer-use/loop/observation.d.ts +7 -0
- package/dist/actors/computer-use/loop/observation.js +17 -1
- package/dist/actors/computer-use/loop/types.d.ts +6 -0
- package/dist/analysis/analysis-limits.d.ts +6 -0
- package/dist/analysis/analysis-limits.js +6 -0
- package/dist/analysis/analysis-lock.d.ts +8 -0
- package/dist/analysis/analysis-lock.js +129 -0
- package/dist/analysis/automatic-completion.js +31 -0
- package/dist/analysis/automatic-config.d.ts +15 -6
- package/dist/analysis/automatic-config.js +25 -9
- package/dist/analysis/execute.js +1 -1
- package/dist/analysis/job.d.ts +1 -0
- package/dist/analysis/job.js +6 -2
- package/dist/analysis/recording-ended.d.ts +16 -0
- package/dist/analysis/recording-ended.js +29 -0
- package/dist/analysis/service.d.ts +1 -3
- package/dist/analysis/service.js +23 -50
- package/dist/analysis/source.d.ts +1 -1
- package/dist/browser-control/protocol.d.ts +8 -0
- package/dist/browser-control/protocol.js +21 -0
- package/dist/cli/commands/analysis-signals.d.ts +10 -4
- package/dist/cli/commands/analysis-signals.js +28 -6
- package/dist/cli/commands/runs.js +8 -2
- package/dist/cli/commands/study-route-computer-use.js +4 -5
- package/dist/cli/commands/study-route-scripted.js +5 -6
- package/dist/cli/commands/study-route-shared-world.js +4 -5
- package/dist/cli/commands/study-route-terminal.js +5 -6
- package/dist/cli/commands/study-run.js +1 -1
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.js +3 -0
- package/dist/cli/findings.d.ts +40 -14
- package/dist/cli/findings.js +117 -35
- package/dist/cli/io.d.ts +5 -1
- package/dist/cli/io.js +12 -2
- package/dist/cli/run-findings.d.ts +19 -0
- package/dist/cli/run-findings.js +106 -0
- package/dist/evidence/browser-hygiene.d.ts +12 -1
- package/dist/evidence/browser-hygiene.js +45 -2
- package/dist/feedback/export-bundle.js +1 -1
- package/dist/guest/bootstrap.d.ts +4 -2
- package/dist/guest/bootstrap.js +15 -7
- package/dist/guest/display-config.d.ts +31 -0
- package/dist/guest/display-config.js +35 -0
- package/dist/guest/external-links.d.ts +14 -0
- package/dist/guest/external-links.js +48 -0
- package/dist/guest/runtime-desktop.d.ts +9 -3
- package/dist/guest/runtime-desktop.js +64 -27
- package/dist/guest/runtime.d.ts +2 -1
- package/dist/guest/runtime.js +1 -1
- package/dist/guest-runtime-main.js +2 -1
- package/dist/observer/data.d.ts +4 -0
- package/dist/observer/run-routes.js +12 -3
- package/dist/observer-app.html +2 -2
- package/dist/routes/computer-use/e2b-desktop/desktop.js +27 -17
- package/dist/routes/computer-use/e2b-desktop/fidelity.js +10 -17
- package/dist/routes/computer-use/e2b-desktop/teardown.js +1 -1
- package/dist/routes/computer-use/in-process-desktop.js +2 -2
- package/dist/routes/computer-use/live-phase.js +1 -1
- package/dist/routes/computer-use/local-vm.js +34 -1
- package/dist/routes/computer-use/plan.js +1 -1
- package/dist/routes/computer-use/setup.js +2 -2
- package/dist/routes/scripted/plan.js +1 -1
- package/dist/routes/shared-world/plan.js +1 -1
- package/dist/routes/shared-world/route.js +1 -1
- package/dist/routes/terminal/plan.js +1 -1
- package/dist/run/display.d.ts +6 -1
- package/dist/run/display.js +11 -0
- package/dist/run/host-suspension.js +4 -1
- package/dist/run/preflight-receipts.d.ts +3 -12
- package/dist/run/preflight-receipts.js +7 -67
- package/dist/run/process-owner.d.ts +28 -0
- package/dist/run/process-owner.js +83 -0
- package/dist/run/projection.js +1 -1
- package/dist/run/reclaim.d.ts +6 -0
- package/dist/run/reclaim.js +32 -2
- package/dist/run/route-shell.js +2 -2
- package/dist/run/run-index.d.ts +3 -1
- package/dist/run/run-index.js +23 -11
- package/dist/run/run.d.ts +16 -5
- package/dist/run/run.js +25 -10
- package/dist/run/status.d.ts +57 -2
- package/dist/run/status.js +87 -1
- package/dist/run/stored-runs.d.ts +3 -0
- package/dist/run/stored-runs.js +9 -4
- package/dist/study/doctor.js +11 -6
- package/dist/study/init-templates.js +9 -3
- package/dist/study/mobile-emulation.d.ts +27 -0
- package/dist/study/mobile-emulation.js +60 -0
- package/dist/study/plan.js +1 -1
- package/dist/study/preflight-result.js +1 -1
- package/dist/study/run-study-events.d.ts +7 -1
- package/dist/study/summary.js +1 -1
- package/dist/study/warnings.js +5 -2
- package/dist/substrates/e2b/cdp-probe.d.ts +2 -6
- package/dist/substrates/e2b/desktop-browser.js +3 -1
- package/dist/substrates/e2b/desktop-cdp.d.ts +0 -2
- package/dist/substrates/e2b/desktop-cdp.js +0 -2
- package/dist/substrates/e2b/desktop-executor.d.ts +7 -0
- package/dist/substrates/e2b/desktop-executor.js +3 -0
- package/dist/substrates/e2b/desktop-handoff.d.ts +22 -0
- package/dist/substrates/e2b/desktop-handoff.js +86 -0
- package/dist/substrates/local/firecracker-desktop.d.ts +5 -0
- package/dist/substrates/local/firecracker-desktop.js +7 -1
- package/dist/substrates/local/runtime-config.d.ts +12 -0
- package/dist/substrates/local/runtime-config.js +55 -13
- package/dist/substrates/local/runtime-release.js +8 -8
- package/dist/substrates/local/runtime.js +1 -0
- package/dist/tui-app.js +115 -115
- package/docs/architecture/observer.md +7 -3
- package/docs/contracts/schemas.md +24 -3
- package/docs/contracts/study-analysis.md +15 -9
- package/docs/principles/invariants-and-defaults.md +1 -1
- package/docs/product/automatic-analysis.md +75 -18
- package/package.json +1 -1
- package/skills/humanish/SKILL.md +153 -31
package/dist/analysis/job.js
CHANGED
|
@@ -48,6 +48,8 @@ const reasons = [
|
|
|
48
48
|
"AUTOMATIC_ANALYSIS_ACTOR_CANCELLED",
|
|
49
49
|
"AUTOMATIC_ANALYSIS_NO_PARTICIPANT_EVIDENCE",
|
|
50
50
|
"AUTOMATIC_ANALYSIS_CLEANUP_UNCONFIRMED",
|
|
51
|
+
// Only a read sets it, for a queued or running job whose owner stopped refreshing it.
|
|
52
|
+
"AUTOMATIC_ANALYSIS_STOPPED",
|
|
51
53
|
];
|
|
52
54
|
const id = z.string().regex(ANALYSIS_ID_PATTERN);
|
|
53
55
|
const date = z.iso.datetime();
|
|
@@ -173,9 +175,11 @@ export async function readAutomaticAnalysisPrepared(prepared, now = Date.now())
|
|
|
173
175
|
...(record.admission === undefined ? {} : { admission: record.admission }),
|
|
174
176
|
};
|
|
175
177
|
const updated = Date.parse(record.updatedAt);
|
|
176
|
-
if (updated > now
|
|
178
|
+
if (updated > now)
|
|
177
179
|
return { ...view, state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN" };
|
|
178
|
-
|
|
180
|
+
// A job its owner stopped refreshing: the process most likely ended during the analysis.
|
|
181
|
+
if (pending(record.state) && now - updated > AUTOMATIC_ANALYSIS_STALE_MS)
|
|
182
|
+
return { ...view, state: "unknown", reason: "AUTOMATIC_ANALYSIS_STOPPED" };
|
|
179
183
|
if (!(await terminalResultMatches(prepared, record)))
|
|
180
184
|
return { ...view, state: "unknown", reason: "AUTOMATIC_ANALYSIS_OUTCOME_UNKNOWN" };
|
|
181
185
|
return view;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RunStatusRecord } from "../run/status.js";
|
|
2
|
+
/** What the gate reads: the run's status record, when it has one, and its streams. */
|
|
3
|
+
export interface RecordingFacts {
|
|
4
|
+
runId: string;
|
|
5
|
+
status: RunStatusRecord | undefined;
|
|
6
|
+
/** How many of the bundle's streams are still running or still hold live items. */
|
|
7
|
+
runningStreams: number;
|
|
8
|
+
streams: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Throws the refusal for a recording that has not ended. A `finished` record, an `interrupted`
|
|
12
|
+
* one, and a stale `running` one whose process is gone from this host have ended; analysis then
|
|
13
|
+
* also needs every stream ended. A fresh `running` record is a live run, and a stale one whose pid
|
|
14
|
+
* is still in use cannot be told apart from a live run, so both are refused.
|
|
15
|
+
*/
|
|
16
|
+
export declare function assertRecordingEnded(facts: RecordingFacts, nowMs?: number, processRunning?: (pid: number) => boolean): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Whether a run's recording has ended, so analysis can read it: the completion gate's reading of
|
|
2
|
+
// status.json and the bundle's streams. A refusal says what the run is doing and the command that
|
|
3
|
+
// moves it on, since "wait" helps only a run that is still alive.
|
|
4
|
+
import { cli } from "../cli/invocation.js";
|
|
5
|
+
import { classifyRunStatus, runOwnerGone } from "../run/status.js";
|
|
6
|
+
import { studyProvenanceOf } from "../run/study-provenance.js";
|
|
7
|
+
import { AnalysisSourceRefusedError } from "./source.js";
|
|
8
|
+
/**
|
|
9
|
+
* Throws the refusal for a recording that has not ended. A `finished` record, an `interrupted`
|
|
10
|
+
* one, and a stale `running` one whose process is gone from this host have ended; analysis then
|
|
11
|
+
* also needs every stream ended. A fresh `running` record is a live run, and a stale one whose pid
|
|
12
|
+
* is still in use cannot be told apart from a live run, so both are refused.
|
|
13
|
+
*/
|
|
14
|
+
export function assertRecordingEnded(facts, nowMs = Date.now(), processRunning) {
|
|
15
|
+
const { runId, status } = facts;
|
|
16
|
+
const runs = `\`${cli("runs")}\``;
|
|
17
|
+
if (status?.state === "running") {
|
|
18
|
+
if (classifyRunStatus(status, nowMs) === "running")
|
|
19
|
+
throw new AnalysisSourceRefusedError("ANALYSIS_RUN_ACTIVE", `Participants are still running in this run, so its recording has not ended. Automatic analysis starts when they finish; ${runs} shows when the run is analyzing.`);
|
|
20
|
+
if (!runOwnerGone(status, nowMs, processRunning))
|
|
21
|
+
throw new AnalysisSourceRefusedError("ANALYSIS_RUN_UNCONFIRMED", `This run's process stopped updating status.json at ${status.updatedAt}, but pid ${status.pid} is still in use on this machine, so humanish cannot tell whether the run is still writing. If the run is gone, \`${cli(`reclaim --run ${runId}`)}\` stops its sandboxes and marks it interrupted; then analyze it again.`);
|
|
22
|
+
}
|
|
23
|
+
if (facts.runningStreams === 0)
|
|
24
|
+
return;
|
|
25
|
+
if (status?.state === "finished")
|
|
26
|
+
throw new AnalysisSourceRefusedError("ANALYSIS_RUN_ACTIVE", `${facts.runningStreams} of ${facts.streams} participant streams in this run have not ended. ${runs} shows the run's state.`);
|
|
27
|
+
const study = status === undefined ? undefined : studyProvenanceOf(status)?.id;
|
|
28
|
+
throw new AnalysisSourceRefusedError("ANALYSIS_RUN_INCOMPLETE", `This run ended while ${facts.runningStreams} of ${facts.streams} participant streams were still running, so its recording is incomplete and is not analyzed. Run the study again with \`${cli(`run ${study ?? "<study>"}`)}\`.`);
|
|
29
|
+
}
|
|
@@ -58,9 +58,7 @@ export declare function resolveAnalysisRun(cwd: string, run: string, expectedRun
|
|
|
58
58
|
export declare function dryRunBundleRefusal(cwd: string, run: string, dryRun: boolean, expectedRun?: PreparedRunArtifactPaths): Promise<AnalyzeResult | null>;
|
|
59
59
|
/** Re-render the run's Observer after an analysis write. False when it could not be refreshed. */
|
|
60
60
|
export declare function refreshObserver(cwd: string, runId: string, prepared: PreparedRunArtifactPaths): Promise<boolean>;
|
|
61
|
-
/**
|
|
62
|
-
export declare function withAnalysisLock<T>(prepared: PreparedRunArtifactPaths, action: () => Promise<T>): Promise<T>;
|
|
63
|
-
/** The completion gate: the run verifies, is live and finished, and has participants. */
|
|
61
|
+
/** The completion gate: the run verifies, is live, its recording ended, and it has participants. */
|
|
64
62
|
export declare function readCompletedAnalysisSource(cwd: string, prepared: PreparedRunArtifactPaths): Promise<Buffer>;
|
|
65
63
|
export declare function analyzeRun(cwdInput: string, run: string, options: AnalyzeOptions, deps?: AnalyzeDeps): Promise<AnalyzeResult>;
|
|
66
64
|
export declare function showAnalysis(cwd: string, run: string, id?: string): Promise<LoadedAnalysis>;
|
package/dist/analysis/service.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defaultCodexCliVersion, describeCodexCliAdmission, } from "../actors/codex/codex-admission.js";
|
|
2
2
|
import { validCodexAnalysisConfig } from "./codex-config.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { realpath } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { renderObserver } from "../observer/render.js";
|
|
7
7
|
import { containsSensitive } from "../evidence/redaction.js";
|
|
@@ -11,6 +11,8 @@ import { verifyRunPrepared } from "../verify/verify.js";
|
|
|
11
11
|
import { loadRunBundlePrepared, resolveRunPath } from "../run/locate.js";
|
|
12
12
|
import { physicalCwdOf, runIdOf, resolvePhysicalCwd, validatePreparedRunRootIdentity, } from "../run/paths.js";
|
|
13
13
|
import { isRunStatusRecord, RUN_STATUS_FILE } from "../run/status.js";
|
|
14
|
+
import { assertRecordingEnded } from "./recording-ended.js";
|
|
15
|
+
import { withAnalysisLock } from "./analysis-lock.js";
|
|
14
16
|
import { captureEvidence, EVIDENCE_LIMITS } from "./evidence.js";
|
|
15
17
|
import { AnalysisSourceRefusedError, runFileTooLarge, throwAsAnalysisRefusal } from "./source.js";
|
|
16
18
|
import { pathMissing, readBoundedFile, readBoundedFileResult } from "../run/evidence-files.js";
|
|
@@ -46,12 +48,12 @@ export async function resolveAnalysisRun(cwd, run, expectedRun) {
|
|
|
46
48
|
const messages = {
|
|
47
49
|
ANALYSIS_RUN_NOT_FOUND: "The selected run is unavailable or its storage is unsafe.",
|
|
48
50
|
ANALYSIS_VERIFY_FAILED: "The run must pass verification before analysis.",
|
|
49
|
-
ANALYSIS_RUN_ACTIVE: "
|
|
51
|
+
ANALYSIS_RUN_ACTIVE: "This run's status.json is missing, unreadable or names another run, so humanish cannot tell whether its recording has ended. `humanish verify` checks the run's files.",
|
|
50
52
|
ANALYSIS_NO_PARTICIPANTS: "This run contains no participant evidence to analyze.",
|
|
51
53
|
ANALYSIS_REQUIRES_LIVE_RUN: "A dry run has no participant behavior to analyze. Select a completed live run.",
|
|
52
54
|
ANALYSIS_SOURCE_UNAVAILABLE: "The source recording is missing or unsafe.",
|
|
53
55
|
ANALYSIS_SOURCE_CHANGED: "The source recording changed during analysis. The result cannot be published against different evidence.",
|
|
54
|
-
ANALYSIS_BUSY: "Another analysis holds this run's .analysis-lock directory
|
|
56
|
+
ANALYSIS_BUSY: "Another analysis holds this run's .analysis-lock directory, and its process is still running or humanish cannot check it (another host, or a lock written by humanish 0.139.1 or earlier). Wait for it to finish; if no humanish process is running for this run, remove that directory.",
|
|
55
57
|
ANALYSIS_HISTORY_UNAVAILABLE: "Analysis history is unavailable or full. No request was sent. Inspect this run's analysis and analysis-attempts storage before retrying.",
|
|
56
58
|
ANALYSIS_CONFIG_INVALID: "OpenAI analysis needs a supported model, positive USD admission limit and 256–32768 output tokens. Codex account analysis uses its qualified model and null dollar/output-token caps. Both require timeout 1–600000 ms.",
|
|
57
59
|
ANALYSIS_QUESTION_UNSAFE: "The reviewer question matched a sensitive-text pattern. Remove sensitive details before retrying.",
|
|
@@ -109,43 +111,7 @@ export async function refreshObserver(cwd, runId, prepared) {
|
|
|
109
111
|
return false;
|
|
110
112
|
}
|
|
111
113
|
}
|
|
112
|
-
/**
|
|
113
|
-
export async function withAnalysisLock(prepared, action) {
|
|
114
|
-
await validatePreparedRunRootIdentity(prepared);
|
|
115
|
-
const target = path.join(prepared.physicalRunRoot, ".analysis-lock");
|
|
116
|
-
try {
|
|
117
|
-
await mkdir(target, { mode: 0o700 });
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
if (error.code === "EEXIST")
|
|
121
|
-
throw new Error("ANALYSIS_BUSY");
|
|
122
|
-
throw new Error("ANALYSIS_UNAVAILABLE");
|
|
123
|
-
}
|
|
124
|
-
const identity = await lstat(target, { bigint: true });
|
|
125
|
-
try {
|
|
126
|
-
await validatePreparedRunRootIdentity(prepared);
|
|
127
|
-
if (!identity.isDirectory() || identity.isSymbolicLink())
|
|
128
|
-
throw new Error("ANALYSIS_UNAVAILABLE");
|
|
129
|
-
return await action();
|
|
130
|
-
}
|
|
131
|
-
finally {
|
|
132
|
-
// Only remove our empty, unchanged directory. No recursive deletion and no replacement authority.
|
|
133
|
-
try {
|
|
134
|
-
await validatePreparedRunRootIdentity(prepared);
|
|
135
|
-
const current = await lstat(target, { bigint: true });
|
|
136
|
-
if (current.isDirectory() &&
|
|
137
|
-
!current.isSymbolicLink() &&
|
|
138
|
-
current.dev === identity.dev &&
|
|
139
|
-
current.ino === identity.ino &&
|
|
140
|
-
current.birthtimeNs === identity.birthtimeNs)
|
|
141
|
-
await rmdir(target);
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
/* Leave ambiguous storage in place for explicit operator inspection. */
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** The completion gate: the run verifies, is live and finished, and has participants. */
|
|
114
|
+
/** The completion gate: the run verifies, is live, its recording ended, and it has participants. */
|
|
149
115
|
export async function readCompletedAnalysisSource(cwd, prepared) {
|
|
150
116
|
const read = await readBoundedFileResult(prepared, RUN_BUNDLE_FILE, EVIDENCE_LIMITS.sourceBytes);
|
|
151
117
|
if (read.state === "limit")
|
|
@@ -170,18 +136,19 @@ export async function readCompletedAnalysisSource(cwd, prepared) {
|
|
|
170
136
|
if (!missing)
|
|
171
137
|
throw new Error("ANALYSIS_RUN_ACTIVE");
|
|
172
138
|
}
|
|
139
|
+
let status;
|
|
173
140
|
if (statusBytes) {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
if (!isRunStatusRecord(status) ||
|
|
177
|
-
status.runId !== loaded.bundle.runId ||
|
|
178
|
-
status.state !== "finished") {
|
|
141
|
+
const record = JSON.parse(statusBytes.toString("utf8"));
|
|
142
|
+
if (!isRunStatusRecord(record) || record.runId !== loaded.bundle.runId)
|
|
179
143
|
throw new Error("ANALYSIS_RUN_ACTIVE");
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
if (loaded.bundle.streams.some((stream) => !TERMINAL_SIMULATION_STATUSES.has(stream.status) || stream.liveActor !== undefined)) {
|
|
183
|
-
throw new Error("ANALYSIS_RUN_ACTIVE");
|
|
144
|
+
status = record;
|
|
184
145
|
}
|
|
146
|
+
assertRecordingEnded({
|
|
147
|
+
runId: loaded.bundle.runId,
|
|
148
|
+
status,
|
|
149
|
+
runningStreams: loaded.bundle.streams.filter((stream) => !TERMINAL_SIMULATION_STATUSES.has(stream.status) || stream.liveActor !== undefined).length,
|
|
150
|
+
streams: loaded.bundle.streams.length,
|
|
151
|
+
});
|
|
185
152
|
return bytes;
|
|
186
153
|
}
|
|
187
154
|
/** The refusal code for a configuration analyzeRun cannot run, before any run file is read. */
|
|
@@ -384,7 +351,13 @@ export async function analyzeRun(cwdInput, run, options, deps = {}) {
|
|
|
384
351
|
if (deps.expectedRun !== undefined)
|
|
385
352
|
cwd = physicalCwdOf(prepared);
|
|
386
353
|
const execute = () => executeAnalysis({ cwd, run, prepared, config, options, deps, dryRun });
|
|
387
|
-
|
|
354
|
+
if (dryRun)
|
|
355
|
+
return await execute();
|
|
356
|
+
const tookOver = [];
|
|
357
|
+
const result = await withAnalysisLock(prepared, execute, (warning) => tookOver.push(warning));
|
|
358
|
+
return tookOver.length === 0
|
|
359
|
+
? result
|
|
360
|
+
: { ...result, warnings: [...tookOver, ...result.warnings] };
|
|
388
361
|
}
|
|
389
362
|
catch (error) {
|
|
390
363
|
if (error instanceof AnalysisSourceRefusedError)
|
|
@@ -7,7 +7,7 @@ import type { RunStream, StoredRunStream } from "../run/streams.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class AnalysisSourceRefusedError extends Error {
|
|
9
9
|
readonly reason: string;
|
|
10
|
-
constructor(code: "ANALYSIS_SOURCE_TOO_LARGE" | "ANALYSIS_SOURCE_INVALID", reason: string);
|
|
10
|
+
constructor(code: "ANALYSIS_SOURCE_TOO_LARGE" | "ANALYSIS_SOURCE_INVALID" | "ANALYSIS_RUN_ACTIVE" | "ANALYSIS_RUN_INCOMPLETE" | "ANALYSIS_RUN_UNCONFIRMED", reason: string);
|
|
11
11
|
}
|
|
12
12
|
/** The refusal of a run.json of `size` bytes, more than the `max` analysis reads of it. */
|
|
13
13
|
export declare function runFileTooLarge(size: number | bigint, max: number): AnalysisSourceRefusedError;
|
|
@@ -287,6 +287,10 @@ declare const observationSchema: z.ZodObject<{
|
|
|
287
287
|
text: z.ZodString;
|
|
288
288
|
durationMs: z.ZodNumber;
|
|
289
289
|
}, z.core.$strict>>>;
|
|
290
|
+
externalHandoffs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
291
|
+
scheme: z.ZodString;
|
|
292
|
+
recipients: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
293
|
+
}, z.core.$strict>>>;
|
|
290
294
|
}, z.core.$strict>;
|
|
291
295
|
type WireObservation = z.infer<typeof observationSchema>;
|
|
292
296
|
declare const requestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -503,6 +507,10 @@ declare const replySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
503
507
|
text: z.ZodString;
|
|
504
508
|
durationMs: z.ZodNumber;
|
|
505
509
|
}, z.core.$strict>>>;
|
|
510
|
+
externalHandoffs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
511
|
+
scheme: z.ZodString;
|
|
512
|
+
recipients: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
513
|
+
}, z.core.$strict>>>;
|
|
506
514
|
}, z.core.$strict>>;
|
|
507
515
|
recording: z.ZodOptional<z.ZodObject<{
|
|
508
516
|
mimeType: z.ZodLiteral<"video/mp4">;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PNG } from "pngjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CUA_SPEECH_LIMITS } from "../actors/computer-use/speech.js";
|
|
4
|
+
import { EXTERNAL_HANDOFF_LIMITS } from "../actors/computer-use/external-handoff.js";
|
|
4
5
|
import { ComputerUseExecutorError, CUA_REJECTION_REASONS, isComputerUseExecutorError, } from "../actors/computer-use/executor-error.js";
|
|
5
6
|
import { CUA_DIAGNOSTIC_CATEGORIES, CUA_DIAGNOSTIC_STEPS, } from "../actors/computer-use/executor-diagnostic.js";
|
|
6
7
|
import { desktopRecordingMetadataSchema } from "../evidence/desktop-recording-types.js";
|
|
@@ -152,6 +153,17 @@ const heardSpeechListSchema = z
|
|
|
152
153
|
.min(1)
|
|
153
154
|
.max(BROWSER_CONTROL_LIMITS.heardSpeech)
|
|
154
155
|
.refine((rows) => new Set(rows.map((row) => row.id)).size === rows.length);
|
|
156
|
+
// The guest sends a handoff as externalHandoffOf made it: a scheme and a mailto link's recipients.
|
|
157
|
+
const externalHandoffListSchema = z
|
|
158
|
+
.array(z.strictObject({
|
|
159
|
+
scheme: z.string().regex(/^[a-z][a-z0-9+.-]{0,31}$/),
|
|
160
|
+
recipients: z
|
|
161
|
+
.array(z.string().min(3).max(EXTERNAL_HANDOFF_LIMITS.addressLength))
|
|
162
|
+
.max(EXTERNAL_HANDOFF_LIMITS.recipients)
|
|
163
|
+
.optional(),
|
|
164
|
+
}))
|
|
165
|
+
.min(1)
|
|
166
|
+
.max(EXTERNAL_HANDOFF_LIMITS.perObservation);
|
|
155
167
|
const observationSchema = z.strictObject({
|
|
156
168
|
png: z
|
|
157
169
|
.string()
|
|
@@ -165,6 +177,7 @@ const observationSchema = z.strictObject({
|
|
|
165
177
|
text: text.optional(),
|
|
166
178
|
scrollY: coordinate.optional(),
|
|
167
179
|
heardSpeech: heardSpeechListSchema.optional(),
|
|
180
|
+
externalHandoffs: externalHandoffListSchema.optional(),
|
|
168
181
|
});
|
|
169
182
|
const common = {
|
|
170
183
|
version: z.literal(BROWSER_CONTROL_VERSION),
|
|
@@ -344,6 +357,9 @@ export function encodeBrowserControlObservation(observation) {
|
|
|
344
357
|
...(observation.text !== undefined ? { text: observation.text } : {}),
|
|
345
358
|
...(observation.scrollY !== undefined ? { scrollY: observation.scrollY } : {}),
|
|
346
359
|
...(observation.heardSpeech !== undefined ? { heardSpeech: observation.heardSpeech } : {}),
|
|
360
|
+
...(observation.externalHandoffs !== undefined
|
|
361
|
+
? { externalHandoffs: observation.externalHandoffs }
|
|
362
|
+
: {}),
|
|
347
363
|
});
|
|
348
364
|
if (!parsed.success)
|
|
349
365
|
throw new ComputerUseExecutorError("invalid_response", "outcome_uncertain");
|
|
@@ -366,6 +382,11 @@ export function decodeBrowserControlObservation(value) {
|
|
|
366
382
|
...(state.text !== undefined ? { text: state.text } : {}),
|
|
367
383
|
...(state.scrollY !== undefined ? { scrollY: state.scrollY } : {}),
|
|
368
384
|
...(state.heardSpeech !== undefined ? { heardSpeech: state.heardSpeech } : {}),
|
|
385
|
+
...(state.externalHandoffs !== undefined
|
|
386
|
+
? {
|
|
387
|
+
externalHandoffs: state.externalHandoffs.map(({ scheme, recipients }) => recipients === undefined ? { scheme } : { scheme, recipients }),
|
|
388
|
+
}
|
|
389
|
+
: {}),
|
|
369
390
|
};
|
|
370
391
|
}
|
|
371
392
|
export function safeBrowserControlFailure(error, dispatched) {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { RunStudyOptions } from "../../run-study.js";
|
|
2
|
+
import { type HostClock } from "../../run/host-suspension.js";
|
|
2
3
|
import type { CliIo } from "../io.js";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* it.
|
|
6
|
-
* (run-signals.ts) hold the signals and
|
|
5
|
+
* The CLI's run events. When the run starts, one stderr line names it and the command that
|
|
6
|
+
* follows it. While analysis runs, SIGINT, SIGTERM and SIGHUP abort it; the listeners exist only
|
|
7
|
+
* during analysis, since before it the run's own handlers (run-signals.ts) hold the signals and
|
|
8
|
+
* hand them over here. A human also gets a line when analysis starts and one with its elapsed
|
|
9
|
+
* time every 20 s; `--json` gets only the first line, on stderr, so stdout stays one result.
|
|
7
10
|
*/
|
|
8
|
-
export declare function
|
|
11
|
+
export declare function cliRunEventOptions(io: Pick<CliIo, "writeErr">, options?: {
|
|
12
|
+
json?: boolean;
|
|
13
|
+
clock?: HostClock;
|
|
14
|
+
}): Pick<RunStudyOptions, "onEvent" | "analysisSignal">;
|
|
@@ -1,25 +1,47 @@
|
|
|
1
|
+
import { ANALYSIS_USUALLY_TAKES } from "../../analysis/analysis-limits.js";
|
|
2
|
+
import { systemHostClock } from "../../run/host-suspension.js";
|
|
3
|
+
import { formatDuration } from "../../run/projection.js";
|
|
4
|
+
import { cli } from "../invocation.js";
|
|
1
5
|
import { handOverRunSignals } from "./run-signals.js";
|
|
6
|
+
/** How often a human sees how long analysis has run: often enough to look alive, rarely enough
|
|
7
|
+
* to read as one line per glance. */
|
|
8
|
+
const ANALYSIS_PROGRESS_MS = 20_000;
|
|
2
9
|
/**
|
|
3
|
-
*
|
|
4
|
-
* it.
|
|
5
|
-
* (run-signals.ts) hold the signals and
|
|
10
|
+
* The CLI's run events. When the run starts, one stderr line names it and the command that
|
|
11
|
+
* follows it. While analysis runs, SIGINT, SIGTERM and SIGHUP abort it; the listeners exist only
|
|
12
|
+
* during analysis, since before it the run's own handlers (run-signals.ts) hold the signals and
|
|
13
|
+
* hand them over here. A human also gets a line when analysis starts and one with its elapsed
|
|
14
|
+
* time every 20 s; `--json` gets only the first line, on stderr, so stdout stays one result.
|
|
6
15
|
*/
|
|
7
|
-
export function
|
|
16
|
+
export function cliRunEventOptions(io, options = {}) {
|
|
17
|
+
const clock = options.clock ?? systemHostClock;
|
|
8
18
|
const controller = new AbortController();
|
|
9
19
|
const cancel = () => {
|
|
10
20
|
controller.abort();
|
|
11
21
|
};
|
|
12
22
|
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
23
|
+
let stopProgress;
|
|
13
24
|
return {
|
|
14
25
|
analysisSignal: controller.signal,
|
|
15
26
|
onEvent: (event) => {
|
|
16
|
-
if (event.type === "
|
|
17
|
-
io.writeErr(
|
|
27
|
+
if (event.type === "run-started") {
|
|
28
|
+
io.writeErr(`Run ${event.runId} started. Follow it with ${cli(`observe --run ${event.runId}`)}; ${cli("runs")} lists its state.\n`);
|
|
29
|
+
}
|
|
30
|
+
else if (event.type === "analysis-started") {
|
|
31
|
+
io.writeErr(`Participants finished. Writing findings; this usually takes ${ANALYSIS_USUALLY_TAKES}.\n`);
|
|
32
|
+
if (options.json !== true) {
|
|
33
|
+
const startedMs = clock.now();
|
|
34
|
+
stopProgress = clock.every(ANALYSIS_PROGRESS_MS, () => {
|
|
35
|
+
io.writeErr(`Still writing findings: ${formatDuration(clock.now() - startedMs)} elapsed.\n`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
18
38
|
handOverRunSignals();
|
|
19
39
|
for (const signal of signals)
|
|
20
40
|
process.on(signal, cancel);
|
|
21
41
|
}
|
|
22
42
|
else if (event.type === "analysis-finished") {
|
|
43
|
+
stopProgress?.();
|
|
44
|
+
stopProgress = undefined;
|
|
23
45
|
for (const signal of signals)
|
|
24
46
|
process.off(signal, cancel);
|
|
25
47
|
}
|
|
@@ -6,7 +6,7 @@ import { DEFAULT_EXPORT_MAX_BYTES, exportRun, formatExportHuman } from "../../fe
|
|
|
6
6
|
import { listRuns, readReview } from "../../run/stored-runs.js";
|
|
7
7
|
import { verifyRun } from "../../verify/verify.js";
|
|
8
8
|
import { reclaimPreflightSandboxes, reclaimRunSandboxes, } from "../../run/reclaim.js";
|
|
9
|
-
import { runDisplay } from "../../run/display.js";
|
|
9
|
+
import { analysisPhaseLabel, runDisplay } from "../../run/display.js";
|
|
10
10
|
import { addRunOptions, handleRun } from "./run-command.js";
|
|
11
11
|
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, parsePositiveInteger, RUN_OPTION_DESCRIPTION, writeResult, humanError, } from "../io.js";
|
|
12
12
|
import { plural } from "../../run/text.js";
|
|
@@ -251,6 +251,8 @@ function formatReclaimHuman(result, cwd, preflight) {
|
|
|
251
251
|
for (const outcome of result.outcomes) {
|
|
252
252
|
lines.push(` sandbox ${outcome.sandboxIdDigest} (${outcome.laneId}, from ${outcome.source}): ${outcome.state}${outcome.detail ? `: ${outcome.detail}` : ""}`);
|
|
253
253
|
}
|
|
254
|
+
if (result.markedInterrupted === true)
|
|
255
|
+
lines.push("Its status.json had stopped updating while it still said running, so the run is now recorded as interrupted.");
|
|
254
256
|
for (const warning of result.warnings)
|
|
255
257
|
lines.push(` warning: ${warning}`);
|
|
256
258
|
if (result.error === undefined && result.state !== "clean")
|
|
@@ -378,6 +380,10 @@ function formatRunsHuman(result) {
|
|
|
378
380
|
}
|
|
379
381
|
return ([
|
|
380
382
|
`latest: ${result.latest ?? "none"}`,
|
|
381
|
-
...result.runs.map((run) =>
|
|
383
|
+
...result.runs.map((run) => {
|
|
384
|
+
const analysis = analysisPhaseLabel(run.analysis);
|
|
385
|
+
const label = `${run.display?.label ?? "unreadable"}${analysis === undefined ? "" : `, ${analysis}`}`;
|
|
386
|
+
return `- ${run.runId} ${run.mode ?? "unknown"} ${label} ${run.createdAt ?? "unknown"} ${run.path}`;
|
|
387
|
+
}),
|
|
382
388
|
].join("\n") + "\n");
|
|
383
389
|
}
|
|
@@ -5,11 +5,11 @@ import { resolveStudyDryRun } from "../../study/plan.js";
|
|
|
5
5
|
import { serveObserver } from "../../observer/render.js";
|
|
6
6
|
import { startExposedObserver, validateExposure } from "../../observer/exposure.js";
|
|
7
7
|
import { ServeTunnelError } from "../../observer/tunnel.js";
|
|
8
|
-
import {
|
|
8
|
+
import { cliRunEventOptions } from "./analysis-signals.js";
|
|
9
9
|
import { onRunShutdown } from "./run-signals.js";
|
|
10
10
|
import { parseStudyCount, parseParticipantIds, parseObserverPort, wantsJson, writeResult, } from "../io.js";
|
|
11
11
|
import { exposureRequestFromOptions, followDecision, followObserver, planObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
12
|
-
import {
|
|
12
|
+
import { writeRunResult } from "../run-findings.js";
|
|
13
13
|
import { formatCuaStudyHuman } from "./study-format.js";
|
|
14
14
|
import { resolveRouteShouldOpen } from "./study-route-open.js";
|
|
15
15
|
import { refusedResult } from "../../run/study-result.js";
|
|
@@ -127,7 +127,7 @@ function prepareCuaWatch(args, settings) {
|
|
|
127
127
|
function cuaRunOptions(args, settings, prepared, live) {
|
|
128
128
|
const { finishedPlan } = prepared;
|
|
129
129
|
return {
|
|
130
|
-
...
|
|
130
|
+
...cliRunEventOptions(args.io, { json: wantsJson(args.command) }),
|
|
131
131
|
cwd: args.options.cwd,
|
|
132
132
|
// A followed watch opens the served Observer (or prints the phone target under --expose), so
|
|
133
133
|
// its static render never opens. A non-follow watch opens what its plan says, once. Run mode
|
|
@@ -236,9 +236,8 @@ async function reportCuaRun(args, prepared, result, live) {
|
|
|
236
236
|
],
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
await writeRunResult(args.command, args.io, args.options.cwd, output, (value) => formatCuaStudyHuman(value, args.config.subject));
|
|
240
240
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
241
|
-
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
242
241
|
if (server && (result.observer?.ok || attachedObserver)) {
|
|
243
242
|
const followResult = output.observer?.ok
|
|
244
243
|
? output.observer
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { automaticAnalysisSucceeded } from "../../analysis/automatic-completion.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import {
|
|
4
|
-
import { wantsJson
|
|
5
|
-
import {
|
|
3
|
+
import { cliRunEventOptions } from "./analysis-signals.js";
|
|
4
|
+
import { wantsJson } from "../io.js";
|
|
5
|
+
import { writeRunResult } from "../run-findings.js";
|
|
6
6
|
import { showObserver } from "../observer-follow.js";
|
|
7
7
|
import { formatScriptedStudyHuman } from "./study-format.js";
|
|
8
8
|
import { observerOpen, resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
@@ -23,7 +23,7 @@ export function scriptedRouteRun(args) {
|
|
|
23
23
|
return undefined;
|
|
24
24
|
return {
|
|
25
25
|
options: {
|
|
26
|
-
...
|
|
26
|
+
...cliRunEventOptions(args.io, { json: wantsJson(args.command) }),
|
|
27
27
|
cwd: args.options.cwd,
|
|
28
28
|
open: observerOpen(args.mode, finishedPlan, shouldOpen),
|
|
29
29
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
@@ -34,9 +34,8 @@ export function scriptedRouteRun(args) {
|
|
|
34
34
|
throw new Error(`Expected the scripted route, got ${outcome.route}.`);
|
|
35
35
|
}
|
|
36
36
|
const result = outcome.result;
|
|
37
|
-
|
|
37
|
+
await writeRunResult(args.command, args.io, args.options.cwd, result, (value) => formatScriptedStudyHuman(value, args.config.subject));
|
|
38
38
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
39
|
-
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
40
39
|
// Watch mode serves the Observer the route rendered through its finished run.
|
|
41
40
|
if (finishedPlan !== undefined && result.ok && result.observer !== undefined) {
|
|
42
41
|
await showObserver({
|
|
@@ -3,10 +3,10 @@ import { Command } from "commander";
|
|
|
3
3
|
import { resolveStudyDryRun } from "../../study/plan.js";
|
|
4
4
|
import { serveObserver } from "../../observer/render.js";
|
|
5
5
|
import { redactText } from "../../evidence/redaction.js";
|
|
6
|
-
import {
|
|
6
|
+
import { cliRunEventOptions } from "./analysis-signals.js";
|
|
7
7
|
import { parseObserverPort, wantsJson, writeResult, } from "../io.js";
|
|
8
8
|
import { followDecision, followObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
9
|
-
import {
|
|
9
|
+
import { writeRunResult } from "../run-findings.js";
|
|
10
10
|
import { formatConcurrentSharedWorldStudyHuman } from "./study-format.js";
|
|
11
11
|
import { resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
12
12
|
import { refusedResult } from "../../run/study-result.js";
|
|
@@ -65,7 +65,7 @@ export function sharedWorldRouteRun(args) {
|
|
|
65
65
|
let observerFailure;
|
|
66
66
|
return {
|
|
67
67
|
options: {
|
|
68
|
-
...
|
|
68
|
+
...cliRunEventOptions(args.io, { json: wantsJson(args.command) }),
|
|
69
69
|
cwd: args.options.cwd,
|
|
70
70
|
open: wantsFollow
|
|
71
71
|
? false
|
|
@@ -120,9 +120,8 @@ export function sharedWorldRouteRun(args) {
|
|
|
120
120
|
],
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
-
|
|
123
|
+
await writeRunResult(args.command, args.io, args.options.cwd, output, formatConcurrentSharedWorldStudyHuman);
|
|
124
124
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
125
|
-
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
126
125
|
if (server && output.observer?.ok) {
|
|
127
126
|
await followObserver(args.io, output.observer, server);
|
|
128
127
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { automaticAnalysisSucceeded } from "../../analysis/automatic-completion.js";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import {
|
|
4
|
-
import { wantsJson
|
|
5
|
-
import {
|
|
3
|
+
import { cliRunEventOptions } from "./analysis-signals.js";
|
|
4
|
+
import { wantsJson } from "../io.js";
|
|
5
|
+
import { writeRunResult } from "../run-findings.js";
|
|
6
6
|
import { showObserver } from "../observer-follow.js";
|
|
7
7
|
import { formatTerminalStudyHuman } from "./study-format.js";
|
|
8
8
|
import { observerOpen, resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
@@ -23,7 +23,7 @@ export function terminalRouteRun(args) {
|
|
|
23
23
|
return undefined;
|
|
24
24
|
return {
|
|
25
25
|
options: {
|
|
26
|
-
...
|
|
26
|
+
...cliRunEventOptions(args.io, { json: wantsJson(args.command) }),
|
|
27
27
|
cwd: args.options.cwd,
|
|
28
28
|
open: observerOpen(args.mode, finishedPlan, shouldOpen),
|
|
29
29
|
...(args.options.dryRun === undefined ? {} : { dryRun: args.options.dryRun }),
|
|
@@ -34,9 +34,8 @@ export function terminalRouteRun(args) {
|
|
|
34
34
|
throw new Error(`Expected the terminal route, got ${outcome.route}.`);
|
|
35
35
|
}
|
|
36
36
|
const result = outcome.result;
|
|
37
|
-
|
|
37
|
+
await writeRunResult(args.command, args.io, args.options.cwd, result, formatTerminalStudyHuman);
|
|
38
38
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
39
|
-
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
40
39
|
if (finishedPlan !== undefined && result.ok && result.observer !== undefined) {
|
|
41
40
|
await showObserver({
|
|
42
41
|
command: args.command,
|
|
@@ -138,7 +138,7 @@ export async function runStudyCommand(args) {
|
|
|
138
138
|
// Surface it so the invoker (who may not be the manifest author) knows executable code ran.
|
|
139
139
|
args.io.writeErr(`warning: review scorer ${scorer.provenance.ref} (${scorer.provenance.source}) is code that humanish loaded and ran in this process. Review it as you would any code you run.\n`);
|
|
140
140
|
}
|
|
141
|
-
const analysisBudget = automaticAnalysisBudget(config.review?.analysis, route, computerUseParticipantCount(config));
|
|
141
|
+
const analysisBudget = automaticAnalysisBudget(config.review?.analysis, route, computerUseParticipantCount(config), config.actor);
|
|
142
142
|
if (analysisBudget && resolveStudyDryRun(config, args.options.dryRun, true) === false) {
|
|
143
143
|
args.io.writeErr(`${formatAutomaticAnalysisBudget(analysisBudget)}\n`);
|
|
144
144
|
}
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export declare function doctor(cwdInput: string, options?: {
|
|
|
53
53
|
keyDeps?: KeyResolutionDeps;
|
|
54
54
|
/** The hosted Codex participant's operator handshake; tests replace it. */
|
|
55
55
|
codexParticipantReadiness?: StudySetupCheckArgs["codexParticipantReadiness"];
|
|
56
|
+
/** The Codex account analyst's setup check; tests replace it. */
|
|
57
|
+
codexAnalysisReadiness?: StudySetupCheckArgs["codexAnalysisReadiness"];
|
|
56
58
|
/** What `--dotenv` loaded, so a key row names the file that supplied it. */
|
|
57
59
|
dotenv?: DotenvLoad;
|
|
58
60
|
}): Promise<DoctorResult>;
|
package/dist/cli/doctor.js
CHANGED
|
@@ -89,6 +89,9 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
89
89
|
...(options.codexParticipantReadiness === undefined
|
|
90
90
|
? {}
|
|
91
91
|
: { codexParticipantReadiness: options.codexParticipantReadiness }),
|
|
92
|
+
...(options.codexAnalysisReadiness === undefined
|
|
93
|
+
? {}
|
|
94
|
+
: { codexAnalysisReadiness: options.codexAnalysisReadiness }),
|
|
92
95
|
})
|
|
93
96
|
: undefined;
|
|
94
97
|
// Without --study, each key row names the studies that need it instead of failing.
|