humanish 0.110.0 → 0.110.2
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/ARCHITECTURE.md +15 -12
- package/CHANGELOG.md +26 -0
- package/CONTEXT.md +8 -6
- package/README.md +103 -53
- package/dist/actors/computer-use/loop/ending.js +4 -4
- package/dist/actors/local-agent/claude-participant.d.ts +74 -0
- package/dist/actors/local-agent/claude-participant.js +243 -0
- package/dist/actors/local-agent/claude-session.d.ts +16 -3
- package/dist/actors/local-agent/claude-session.js +64 -23
- package/dist/actors/local-agent/cli.d.ts +17 -0
- package/dist/actors/local-agent/cli.js +111 -24
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +20 -12
- package/dist/cli/commands/analyze.js +21 -5
- package/dist/cli/commands/observe.js +15 -2
- package/dist/cli/commands/run-signals.d.ts +2 -0
- package/dist/cli/commands/run-signals.js +106 -45
- package/dist/cli/commands/runs.d.ts +5 -0
- package/dist/cli/commands/runs.js +113 -74
- package/dist/cli/commands/setup.js +23 -14
- package/dist/cli/commands/study-route-computer-use.js +9 -3
- package/dist/cli/commands/study-route-open.js +1 -0
- package/dist/cli/commands/study-route-preview.js +1 -0
- package/dist/cli/commands/study-route-scripted.js +2 -0
- package/dist/cli/commands/study-route-shared-world.js +6 -2
- package/dist/cli/commands/study-route-terminal.js +2 -0
- package/dist/cli/commands/watch.js +14 -4
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.js +51 -10
- package/dist/cli/findings.d.ts +103 -0
- package/dist/cli/findings.js +317 -0
- package/dist/cli/first-run-path.d.ts +32 -11
- package/dist/cli/first-run-path.js +113 -69
- package/dist/cli/invocation.d.ts +7 -0
- package/dist/cli/invocation.js +32 -0
- package/dist/cli/io.d.ts +3 -1
- package/dist/cli/io.js +8 -15
- package/dist/cli/observer-follow.d.ts +20 -0
- package/dist/cli/observer-follow.js +36 -1
- package/dist/cli/orientation.js +16 -14
- package/dist/cli/product-sentence.d.ts +3 -3
- package/dist/cli/product-sentence.js +3 -3
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +11 -0
- package/dist/evidence/encoded-text.d.ts +9 -6
- package/dist/evidence/encoded-text.js +84 -19
- package/dist/evidence/redaction.d.ts +2 -0
- package/dist/evidence/redaction.js +123 -21
- package/dist/feedback/export-bundle.js +2 -2
- package/dist/keys/env-file.d.ts +4 -0
- package/dist/keys/env-file.js +17 -0
- package/dist/keys/key-resolution.js +6 -5
- package/dist/observer/data.d.ts +14 -1
- package/dist/observer/data.js +19 -9
- package/dist/observer/exposure.js +1 -1
- package/dist/observer/library.d.ts +4 -0
- package/dist/observer/library.js +21 -17
- package/dist/observer/render.js +10 -3
- package/dist/observer/run-routes.d.ts +2 -0
- package/dist/observer/run-routes.js +51 -35
- package/dist/observer/serve.js +2 -1
- package/dist/observer-app.html +3 -3
- package/dist/routes/computer-use/bundle.js +3 -1
- package/dist/routes/computer-use/fanout-records.js +1 -1
- package/dist/routes/computer-use/participant-execution.js +3 -1
- package/dist/routes/computer-use/participant-model.js +5 -2
- package/dist/routes/computer-use/plan.js +9 -4
- package/dist/routes/computer-use/preflight.js +1 -1
- package/dist/routes/computer-use/result.js +17 -28
- package/dist/routes/computer-use/single-bundle.js +7 -1
- package/dist/routes/scripted/bundle.js +4 -2
- package/dist/routes/scripted/plan.js +2 -2
- package/dist/routes/scripted/result.d.ts +3 -5
- package/dist/routes/scripted/result.js +16 -22
- package/dist/routes/scripted/scenario.js +4 -4
- package/dist/routes/shared-world/bundle-records.js +6 -1
- package/dist/routes/shared-world/bundle.js +2 -0
- package/dist/routes/shared-world/plan.js +6 -2
- package/dist/routes/shared-world/result.js +22 -22
- package/dist/routes/terminal/bundle.js +6 -3
- package/dist/routes/terminal/credentials.d.ts +5 -5
- package/dist/routes/terminal/credentials.js +14 -6
- package/dist/routes/terminal/dry-run.js +8 -12
- package/dist/routes/terminal/ledger.d.ts +3 -3
- package/dist/routes/terminal/ledger.js +6 -6
- package/dist/routes/terminal/live-finish.js +15 -13
- package/dist/routes/terminal/plan.js +12 -4
- package/dist/routes/terminal/result.d.ts +6 -7
- package/dist/routes/terminal/result.js +6 -22
- package/dist/routes/terminal/route.js +5 -4
- package/dist/routes/terminal/runtime-auth.d.ts +6 -0
- package/dist/routes/terminal/runtime-auth.js +5 -0
- package/dist/routes/terminal/session.js +1 -1
- package/dist/run/bundle-shape.d.ts +3 -1
- package/dist/run/bundle-shape.js +25 -1
- package/dist/run/bundle.d.ts +24 -0
- package/dist/run/display.d.ts +59 -0
- package/dist/run/display.js +106 -0
- package/dist/run/dry-run.js +13 -8
- package/dist/run/judge.d.ts +3 -2
- package/dist/run/judge.js +12 -1
- package/dist/run/local-only-files.d.ts +13 -0
- package/dist/run/local-only-files.js +35 -0
- package/dist/run/pricing.js +1 -1
- package/dist/run/projection.d.ts +0 -2
- package/dist/run/projection.js +0 -11
- package/dist/run/reclaim-outcomes.d.ts +89 -0
- package/dist/run/reclaim-outcomes.js +213 -0
- package/dist/run/reclaim.d.ts +23 -17
- package/dist/run/reclaim.js +213 -114
- package/dist/run/results.d.ts +10 -20
- package/dist/run/results.js +2 -2
- package/dist/run/run-index.d.ts +11 -1
- package/dist/run/run-index.js +11 -0
- package/dist/run/run-not-found.js +3 -2
- package/dist/run/run.d.ts +34 -18
- package/dist/run/run.js +166 -67
- package/dist/run/sandbox-creates.d.ts +59 -0
- package/dist/run/sandbox-creates.js +140 -0
- package/dist/run/sandbox-receipts.d.ts +20 -5
- package/dist/run/sandbox-receipts.js +56 -7
- package/dist/run/stats.d.ts +5 -0
- package/dist/run/stats.js +8 -4
- package/dist/run/status.d.ts +6 -9
- package/dist/run/status.js +7 -23
- package/dist/run/stored-runs.d.ts +5 -14
- package/dist/run/stored-runs.js +31 -146
- package/dist/run/streams.d.ts +3 -2
- package/dist/study/agents-section.d.ts +21 -0
- package/dist/study/agents-section.js +86 -0
- package/dist/study/doctor.d.ts +1 -1
- package/dist/study/doctor.js +8 -4
- package/dist/study/init-templates.js +0 -95
- package/dist/study/init.js +45 -37
- package/dist/study/keys.d.ts +12 -0
- package/dist/study/keys.js +32 -0
- package/dist/study/local-browser-host.d.ts +11 -0
- package/dist/study/local-browser-host.js +42 -0
- package/dist/study/parse/actors.js +9 -1
- package/dist/study/parse/subject.js +12 -0
- package/dist/study/parse/url-credentials.d.ts +9 -0
- package/dist/study/parse/url-credentials.js +50 -0
- package/dist/study/persona.d.ts +4 -0
- package/dist/study/persona.js +4 -2
- package/dist/study/preflight-probes.js +2 -1
- package/dist/study/types.d.ts +8 -6
- package/dist/study/url-credentials.d.ts +6 -0
- package/dist/study/url-credentials.js +34 -0
- package/dist/substrates/e2b/peer-install.d.ts +51 -0
- package/dist/substrates/e2b/peer-install.js +161 -0
- package/dist/substrates/e2b/sandbox.d.ts +34 -6
- package/dist/substrates/e2b/sandbox.js +141 -30
- package/dist/substrates/e2b/sdk.d.ts +33 -0
- package/dist/substrates/e2b/sdk.js +21 -21
- package/dist/substrates/local/firecracker-desktop.js +3 -2
- package/dist/substrates/local/runtime-host.js +5 -4
- package/dist/substrates/local/runtime.js +5 -4
- package/dist/substrates/shell.d.ts +2 -0
- package/dist/substrates/shell.js +4 -0
- package/dist/tui/contract.js +5 -4
- package/dist/tui-app.js +133 -133
- package/dist/verify/liveness.d.ts +19 -3
- package/dist/verify/liveness.js +47 -25
- package/dist/verify/verify.d.ts +3 -0
- package/dist/verify/verify.js +8 -3
- package/docs/architecture/actor-contract.md +2 -1
- package/docs/architecture/observer.md +22 -7
- package/docs/architecture/task-protocol-support.md +13 -13
- package/docs/architecture/terminal-product-route.md +15 -13
- package/docs/contracts/run-bundle.md +95 -10
- package/docs/contracts/schemas.md +44 -26
- package/docs/contracts/study-analysis.md +45 -0
- package/docs/contracts/study-costs.md +8 -0
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +3 -0
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +9 -3
- package/docs/principles/invariants-and-defaults.md +11 -8
- package/package.json +6 -3
- package/skills/humanish/SKILL.md +38 -20
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { Command, Option } from "commander";
|
|
3
|
-
import {
|
|
3
|
+
import { shellArg } from "../../substrates/shell.js";
|
|
4
4
|
import { computeStats, formatStatsHuman } from "../../run/stats.js";
|
|
5
5
|
import { DEFAULT_EXPORT_MAX_BYTES, exportRun, formatExportHuman } from "../../feedback/export.js";
|
|
6
|
-
import {
|
|
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
9
|
import { addRunOptions, handleRun } from "./run-command.js";
|
|
10
10
|
import { applyEnvFileOption, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, RUN_OPTION_DESCRIPTION, writeResult, humanError, } from "../io.js";
|
|
11
11
|
import { plural } from "../../run/text.js";
|
|
12
|
+
import { warnAndQueue } from "../deprecations.js";
|
|
13
|
+
import { formatFindings, readRunFindings } from "../findings.js";
|
|
14
|
+
import { cli } from "../invocation.js";
|
|
12
15
|
export function registerRunCommand(parent, io) {
|
|
13
16
|
addRunOptions(parent
|
|
14
17
|
.command("run")
|
|
@@ -30,30 +33,40 @@ export function registerVerifyCommand(parent, io) {
|
|
|
30
33
|
io.setExitCode(result.ok ? 0 : 2);
|
|
31
34
|
});
|
|
32
35
|
}
|
|
36
|
+
const CLEANUP_RENAMED = "`cleanup` is now `reclaim --check`; `cleanup` is removed in 0.112.0.";
|
|
37
|
+
/**
|
|
38
|
+
* The deprecated `cleanup`, hidden from help: one warning, then exactly `reclaim --check`, with
|
|
39
|
+
* its output and exit code. The old command read only what computer-use runs recorded and passed
|
|
40
|
+
* when it found no record; the check asks E2B.
|
|
41
|
+
*/
|
|
33
42
|
export function registerCleanupCommand(parent, io) {
|
|
34
43
|
parent
|
|
35
|
-
.command("cleanup")
|
|
36
|
-
.description(
|
|
37
|
-
.summary("Check that a run's resources were stopped.")
|
|
38
|
-
.option("--run <id>", RUN_OPTION_DESCRIPTION, "latest")
|
|
44
|
+
.command("cleanup", { hidden: true })
|
|
45
|
+
.description(`Deprecated: ${CLEANUP_RENAMED}`)
|
|
39
46
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
47
|
+
.option("--run <id>", RUN_OPTION_DESCRIPTION, "latest")
|
|
48
|
+
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
49
|
+
.addOption(envFileAliasOption())
|
|
40
50
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
41
51
|
.action(async (options, command) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
io.setExitCode(result.ok ? 0 : 2);
|
|
52
|
+
warnAndQueue(command, io, CLEANUP_RENAMED);
|
|
53
|
+
await runReclaimCommand(io, command, { ...options, check: true });
|
|
45
54
|
});
|
|
46
55
|
}
|
|
47
56
|
export function registerReviewCommand(parent, io) {
|
|
48
57
|
parent
|
|
49
58
|
.command("review")
|
|
50
|
-
.description("Show a run's review: verdict, summary and
|
|
51
|
-
.summary("
|
|
59
|
+
.description("Show a run's review: verdict, summary, gaps and the findings of its analysis, with the evidence each one cites.")
|
|
60
|
+
.summary("Show a run's outcome and analysis findings.")
|
|
52
61
|
.option("--run <id>", RUN_OPTION_DESCRIPTION, "latest")
|
|
53
62
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
54
63
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
55
64
|
.action(async (options, command) => {
|
|
56
|
-
const
|
|
65
|
+
const review = await readReview(options.cwd, options.run);
|
|
66
|
+
// The verified run's exact id, so a run that moves `latest` meanwhile cannot swap the analysis.
|
|
67
|
+
const result = "verdict" in review
|
|
68
|
+
? { ...review, analysis: await readRunFindings(options.cwd, review.runId) }
|
|
69
|
+
: review;
|
|
57
70
|
writeResult(command, io, result, formatReviewHuman);
|
|
58
71
|
io.setExitCode("ok" in result && result.ok === false ? 2 : 0);
|
|
59
72
|
});
|
|
@@ -150,7 +163,7 @@ export function registerRuntimeCommands(parent, io) {
|
|
|
150
163
|
catch (error) {
|
|
151
164
|
const message = error instanceof Error
|
|
152
165
|
? error.message
|
|
153
|
-
:
|
|
166
|
+
: `Local runtime setup failed. Run ${cli("runtime setup")} to retry.`;
|
|
154
167
|
const result = {
|
|
155
168
|
schema: "humanish.runtime-result.v1",
|
|
156
169
|
ok: false,
|
|
@@ -165,50 +178,96 @@ export function registerRuntimeCommands(parent, io) {
|
|
|
165
178
|
export function registerReclaimCommand(parent, io) {
|
|
166
179
|
parent
|
|
167
180
|
.command("reclaim")
|
|
168
|
-
.description("Kill an interrupted run's sandboxes
|
|
169
|
-
.summary("Stop an interrupted run's
|
|
181
|
+
.description("Kill an interrupted run's sandboxes: the exact ids journaled in its sandbox-receipts.ndjson, and any sandbox E2B lists with this run's owner tags (a sandbox whose id never reached a receipt). It lists only sandboxes that match every tag of this run. --check asks E2B whether each still exists and kills nothing. Needs E2B_API_KEY in the environment or in --dotenv.")
|
|
182
|
+
.summary("Stop or check an interrupted run's sandboxes.")
|
|
170
183
|
.option("--cwd <path>", CWD_OPTION_DESCRIPTION, ".")
|
|
171
184
|
.option("--run <id>", RUN_OPTION_DESCRIPTION, "latest")
|
|
172
185
|
.addOption(new Option("--preflight", "Reclaim sandboxes left by interrupted `humanish study check` probes (journaled in .humanish/preflight) instead of a run's.").conflicts("run"))
|
|
186
|
+
.option("--check", "Ask E2B whether each sandbox still exists; kill nothing and write nothing. Exits 0 only when all are gone.")
|
|
173
187
|
.option("--dotenv <path>", DOTENV_OPTION_DESCRIPTION)
|
|
174
188
|
.addOption(envFileAliasOption())
|
|
175
189
|
.option("--json", JSON_OPTION_DESCRIPTION)
|
|
176
|
-
.action(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
.action((options, command) => runReclaimCommand(io, command, options));
|
|
191
|
+
}
|
|
192
|
+
/** `reclaim`, and the deprecated `cleanup`, which runs it with `check`. */
|
|
193
|
+
async function runReclaimCommand(io, command, options) {
|
|
194
|
+
// The kill calls read E2B_API_KEY from the environment: the env file, then discovered keys.
|
|
195
|
+
if (!(await applyEnvFileOption({
|
|
196
|
+
command,
|
|
197
|
+
cwd: options.cwd,
|
|
198
|
+
envFile: dotenvPathOf(options, command, io),
|
|
199
|
+
io,
|
|
200
|
+
})))
|
|
201
|
+
return;
|
|
202
|
+
const hooks = { check: options.check === true };
|
|
203
|
+
const result = options.preflight
|
|
204
|
+
? await reclaimPreflightSandboxes(options.cwd, hooks)
|
|
205
|
+
: await reclaimRunSandboxes(options.cwd, options.run, hooks);
|
|
206
|
+
writeResult(command, io, result, (value) => formatReclaimHuman(value, options.cwd, options.preflight === true));
|
|
207
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
208
|
+
}
|
|
209
|
+
/** The command that finishes what a reclaim left open, with --cwd when it was given. */
|
|
210
|
+
function reclaimCommand(result, cwd, preflight) {
|
|
211
|
+
const target = preflight ? "--preflight" : `--run ${shellArg(result.runId)}`;
|
|
212
|
+
return cli(`reclaim ${target}${cwd === "." ? "" : ` --cwd ${shellArg(cwd)}`}`);
|
|
191
213
|
}
|
|
192
|
-
function formatReclaimHuman(result) {
|
|
214
|
+
function formatReclaimHuman(result, cwd, preflight) {
|
|
193
215
|
const lines = [];
|
|
194
|
-
|
|
216
|
+
const check = result.mode === "check";
|
|
217
|
+
const search = result.tagSearch.status === "done"
|
|
218
|
+
? `E2B lists ${plural(result.tagSearch.found, "more sandbox", "more sandboxes")} tagged with it`
|
|
219
|
+
: `E2B tag search ${result.tagSearch.status}`;
|
|
220
|
+
lines.push(result.reason === "dry-run"
|
|
221
|
+
? `${check ? "Reclaim check" : "Reclaim"} ${result.runId}: ${result.state}. It was a dry run, which creates no sandboxes, so E2B was not contacted.`
|
|
222
|
+
: `${check ? "Reclaim check" : "Reclaim"} ${result.runId}: ${result.state}. ${plural(result.receiptCount, "sandbox receipt")}; ${search}.`);
|
|
195
223
|
for (const outcome of result.outcomes) {
|
|
196
|
-
lines.push(` sandbox ${outcome.sandboxIdDigest} (${outcome.laneId}): ${outcome.state}${outcome.detail ?
|
|
224
|
+
lines.push(` sandbox ${outcome.sandboxIdDigest} (${outcome.laneId}, from ${outcome.source}): ${outcome.state}${outcome.detail ? `: ${outcome.detail}` : ""}`);
|
|
197
225
|
}
|
|
198
226
|
for (const warning of result.warnings)
|
|
199
227
|
lines.push(` warning: ${warning}`);
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
if (result.error === undefined && result.state !== "clean")
|
|
229
|
+
lines.push(result.state === "running"
|
|
230
|
+
? `To stop them, run ${reclaimCommand(result, cwd, preflight)}.`
|
|
231
|
+
: `Run ${reclaimCommand(result, cwd, preflight)} again once E2B is reachable; each sandbox's create-time timeout is the backstop.`);
|
|
232
|
+
const stdout = `${lines.join("\n")}\n`;
|
|
233
|
+
return result.error === undefined ? stdout : { stdout, error: result.error };
|
|
202
234
|
}
|
|
235
|
+
const NOTHING_TESTED = "no product behavior was tested";
|
|
203
236
|
const REVIEW_VERDICTS = {
|
|
204
237
|
// A dry run, or a live run with no participant result; review's result does not carry the mode.
|
|
205
|
-
contract_proof_only:
|
|
238
|
+
contract_proof_only: `no verdict; ${NOTHING_TESTED}`,
|
|
206
239
|
pass: "pass",
|
|
207
240
|
fail: "fail",
|
|
208
241
|
blocked: "blocked",
|
|
209
242
|
timed_out: "timed out",
|
|
210
243
|
};
|
|
211
|
-
/**
|
|
244
|
+
/** The state runDisplay gives a finished run whose own ok agrees with its verdict. */
|
|
245
|
+
const VERDICT_STATES = {
|
|
246
|
+
pass: "passed",
|
|
247
|
+
fail: "failed",
|
|
248
|
+
blocked: "blocked",
|
|
249
|
+
timed_out: "timed_out",
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Review's headline: the run's display label, as `humanish runs`, review.md and the Observer show
|
|
253
|
+
* it. A verdict that names another state follows in parentheses, so an interrupted run whose last
|
|
254
|
+
* flush judged its participants `fail` reads "interrupted (verdict fail)". A run with no display,
|
|
255
|
+
* which verify does not pass, falls back to the verdict.
|
|
256
|
+
*/
|
|
257
|
+
function reviewHeadline(verdict, display) {
|
|
258
|
+
if (display === undefined)
|
|
259
|
+
return REVIEW_VERDICTS[verdict];
|
|
260
|
+
if (display.state === "no_verdict" || display.state === "dry_run")
|
|
261
|
+
return `${display.label}; ${NOTHING_TESTED}`;
|
|
262
|
+
const judged = VERDICT_STATES[verdict];
|
|
263
|
+
return judged === undefined || judged === display.state
|
|
264
|
+
? display.label
|
|
265
|
+
: `${display.label} (verdict ${REVIEW_VERDICTS[verdict]})`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* A run's review: how it ended (runDisplay) and why it failed when it says, its summary and gaps,
|
|
269
|
+
* its analysis findings, and where review.json is.
|
|
270
|
+
*/
|
|
212
271
|
function formatReviewHuman(result) {
|
|
213
272
|
if (!("verdict" in result)) {
|
|
214
273
|
// A run that did not pass verify has no review to show; verify says why.
|
|
@@ -220,17 +279,20 @@ function formatReviewHuman(result) {
|
|
|
220
279
|
? {
|
|
221
280
|
error: {
|
|
222
281
|
...error,
|
|
223
|
-
message: `${error.message}
|
|
282
|
+
message: `${error.message} ${cli(`verify --run ${result.run}`)} shows why.`,
|
|
224
283
|
},
|
|
225
284
|
}
|
|
226
285
|
: humanError(error);
|
|
227
286
|
}
|
|
287
|
+
const display = result.display;
|
|
228
288
|
return ([
|
|
229
|
-
`humanish review ${result.runId}: ${
|
|
289
|
+
`humanish review ${result.runId}: ${reviewHeadline(result.verdict, display)}`,
|
|
290
|
+
...(display?.reason === undefined ? [] : [`why: ${display.reason}`]),
|
|
230
291
|
"",
|
|
231
292
|
result.summary,
|
|
232
293
|
...(result.gaps.length === 0 ? [] : ["", "gaps:", ...result.gaps.map((gap) => `- ${gap}`)]),
|
|
233
294
|
"",
|
|
295
|
+
...formatFindings(result.analysis),
|
|
234
296
|
`review: ${result.path}`,
|
|
235
297
|
].join("\n") + "\n");
|
|
236
298
|
}
|
|
@@ -246,6 +308,12 @@ function shareSafetyLines(result) {
|
|
|
246
308
|
.filter((reason) => reason.code !== "VERIFY_FAILED")
|
|
247
309
|
.map((reason) => `share-safety: ${reason.code}: ${reason.message}`);
|
|
248
310
|
}
|
|
311
|
+
/** For a run that is not finished, its liveness and sandbox state, so the line leads with them. */
|
|
312
|
+
function unfinishedNote(result) {
|
|
313
|
+
return result.unfinished === undefined
|
|
314
|
+
? ""
|
|
315
|
+
: ` (${result.unfinished.liveness}, sandboxes ${result.unfinished.sandboxes})`;
|
|
316
|
+
}
|
|
249
317
|
/**
|
|
250
318
|
* One line for a pass. A failure lists only the failing checks. `--verbose` prints every check.
|
|
251
319
|
* A run that does not exist prints only that.
|
|
@@ -254,18 +322,19 @@ function formatVerifyHuman(result) {
|
|
|
254
322
|
if (result.error?.code === "HUMANISH_RUN_NOT_FOUND")
|
|
255
323
|
return humanError(result.error);
|
|
256
324
|
const runId = verifiedRunId(result);
|
|
325
|
+
const named = `${runId}${unfinishedNote(result)}`;
|
|
257
326
|
const total = result.checks.length;
|
|
258
327
|
const failed = result.checks.filter((check) => !check.ok);
|
|
259
328
|
const lines = result.ok
|
|
260
|
-
? [`verified ${
|
|
329
|
+
? [`verified ${named} · ${result.shareSafety.status} · ${total} checks passed`]
|
|
261
330
|
: [
|
|
262
|
-
`verify failed: ${
|
|
331
|
+
`verify failed: ${named} · ${result.shareSafety.status} · ${failed.length} of ${total} checks failed`,
|
|
263
332
|
...failed.map((check) => `- ${check.message}`),
|
|
264
333
|
];
|
|
265
334
|
lines.push(...shareSafetyLines(result), ...result.warnings.map((warning) => `warning: ${warning}`));
|
|
266
335
|
const cwdFlag = result.cwd === process.cwd() ? "" : ` --cwd ${result.cwd}`;
|
|
267
336
|
if (!result.ok)
|
|
268
|
-
lines.push(`every check:
|
|
337
|
+
lines.push(`every check: ${cli(`verify --run ${runId}${cwdFlag} --verbose`)}`);
|
|
269
338
|
return `${lines.join("\n")}\n`;
|
|
270
339
|
}
|
|
271
340
|
function formatVerifyVerbose(result) {
|
|
@@ -273,43 +342,13 @@ function formatVerifyVerbose(result) {
|
|
|
273
342
|
return humanError(result.error);
|
|
274
343
|
return ([
|
|
275
344
|
`humanish verify ${result.ok ? "passed" : "failed"}`,
|
|
276
|
-
`run: ${verifiedRunId(result)}`,
|
|
345
|
+
`run: ${verifiedRunId(result)}${unfinishedNote(result)}`,
|
|
277
346
|
`share-safety: ${result.shareSafety.status}`,
|
|
278
347
|
...result.shareSafety.reasons.map((reason) => `share-safety reason: ${reason.code}: ${reason.message}`),
|
|
279
348
|
...result.checks.map((check) => `- ${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`),
|
|
280
349
|
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
281
350
|
].join("\n") + "\n");
|
|
282
351
|
}
|
|
283
|
-
/** A command-line argument, single-quoted only when the shell would split or expand it. */
|
|
284
|
-
function shellArg(value) {
|
|
285
|
-
return /^[\w@%+=:,./-]+$/.test(value) ? value : shellQuote(value);
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Cleanup only reads evidence and never stops a sandbox. A failed resource is an E2B sandbox not
|
|
289
|
-
* recorded as stopped, which reclaim can stop from the run's sandbox receipts, so the last line
|
|
290
|
-
* names it. A skipped resource is a provider reclaim does not handle, so it gets no line. `cwd` is
|
|
291
|
-
* the --cwd the command was given, since cleanup's result masks it.
|
|
292
|
-
*/
|
|
293
|
-
function reclaimPointer(result, cwd) {
|
|
294
|
-
if (result.runId === undefined || result.summary.failed === 0)
|
|
295
|
-
return [];
|
|
296
|
-
const cwdFlag = cwd === "." ? "" : ` --cwd ${shellArg(cwd)}`;
|
|
297
|
-
return [
|
|
298
|
-
`To stop leftover sandboxes, run humanish reclaim --run ${shellArg(result.runId)}${cwdFlag}.`,
|
|
299
|
-
];
|
|
300
|
-
}
|
|
301
|
-
function formatCleanupHuman(result, cwd) {
|
|
302
|
-
if (!result.ok && result.error)
|
|
303
|
-
return humanError(result.error);
|
|
304
|
-
return ([
|
|
305
|
-
`humanish cleanup ${result.ok ? "passed" : "failed"}`,
|
|
306
|
-
`run: ${result.runId ?? result.run}`,
|
|
307
|
-
`resources: already-clean ${result.summary.alreadyClean}, skipped ${result.summary.skipped}, failed ${result.summary.failed}`,
|
|
308
|
-
...(result.cleanupPath ? [`cleanup: ${result.cleanupPath}`] : []),
|
|
309
|
-
...result.warnings.map((warning) => `warning: ${warning}`),
|
|
310
|
-
...reclaimPointer(result, cwd),
|
|
311
|
-
].join("\n") + "\n");
|
|
312
|
-
}
|
|
313
352
|
function formatRunsHuman(result) {
|
|
314
353
|
if (!result.ok)
|
|
315
354
|
return humanError(result.error);
|
|
@@ -318,6 +357,6 @@ function formatRunsHuman(result) {
|
|
|
318
357
|
}
|
|
319
358
|
return ([
|
|
320
359
|
`latest: ${result.latest ?? "none"}`,
|
|
321
|
-
...result.runs.map((run) => `- ${run.runId} ${run.mode ?? "unknown"} ${run.createdAt ?? "unknown"} ${run.path}`),
|
|
360
|
+
...result.runs.map((run) => `- ${run.runId} ${run.mode ?? "unknown"} ${run.display?.label ?? "unreadable"} ${run.createdAt ?? "unknown"} ${run.path}`),
|
|
322
361
|
].join("\n") + "\n");
|
|
323
362
|
}
|
|
@@ -6,6 +6,7 @@ import { buildPayload, disabledByEnvironment, readTelemetryState, telemetryState
|
|
|
6
6
|
import { doctor } from "../doctor.js";
|
|
7
7
|
import { applyEnvFileOption, CLI_VERSION, CWD_OPTION_DESCRIPTION, DOTENV_OPTION_DESCRIPTION, dotenvPathOf, envFileAliasOption, JSON_OPTION_DESCRIPTION, markInvocationEnvelopeWritten, wantsJson, writeResult, formatCliError, } from "../io.js";
|
|
8
8
|
import { plural } from "../../run/text.js";
|
|
9
|
+
import { cli } from "../invocation.js";
|
|
9
10
|
export function registerInitCommand(parent, io) {
|
|
10
11
|
parent
|
|
11
12
|
.command("init")
|
|
@@ -283,7 +284,7 @@ export function registerKeysCommand(parent, io) {
|
|
|
283
284
|
store: storePath,
|
|
284
285
|
names,
|
|
285
286
|
message: names.length === 0
|
|
286
|
-
?
|
|
287
|
+
? `The store is empty. Add a key with \`${cli("keys set <vendor>")}\`.`
|
|
287
288
|
: `${plural(names.length, "key name")} stored. Values are never printed.`,
|
|
288
289
|
};
|
|
289
290
|
writeResult(command, io, result, formatKeysHuman);
|
|
@@ -304,19 +305,27 @@ function formatInitHuman(result) {
|
|
|
304
305
|
const title = result.ok ? `humanish init ${result.mode}` : `humanish init ${result.mode} blocked`;
|
|
305
306
|
const lines = [title, `cwd: ${result.cwd}`];
|
|
306
307
|
if (result.ok && result.mode === "applied") {
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
308
|
+
// Name each file init wrote, so the person or agent who ran it can review exactly those
|
|
309
|
+
// files; directories and preserved files are counts. JSON always carries every change.
|
|
310
|
+
const files = (action) => result.changes.filter((change) => change.action === action && change.target !== "runtime");
|
|
311
|
+
const created = files("create");
|
|
312
|
+
const updated = files("update");
|
|
313
|
+
const preserved = result.changes.filter((change) => change.action === "skip").length;
|
|
314
|
+
const directories = result.changes.filter((change) => change.action === "mkdir").length;
|
|
315
|
+
lines.push("");
|
|
316
|
+
if (created.length > 0)
|
|
317
|
+
lines.push("created:", ...created.map((change) => ` ${change.path}`));
|
|
318
|
+
if (updated.length > 0)
|
|
319
|
+
lines.push("updated:", ...updated.map((change) => ` ${change.path} (${change.reason})`));
|
|
320
|
+
if (created.length === 0 && updated.length === 0)
|
|
321
|
+
lines.push("no files changed");
|
|
322
|
+
if (preserved > 0 || directories > 0)
|
|
323
|
+
lines.push([
|
|
324
|
+
...(preserved > 0 ? [`${plural(preserved, "existing path")} unchanged`] : []),
|
|
325
|
+
...(directories > 0
|
|
326
|
+
? [`${plural(directories, "ignored directory", "ignored directories")} prepared`]
|
|
327
|
+
: []),
|
|
328
|
+
].join(", "));
|
|
320
329
|
}
|
|
321
330
|
else {
|
|
322
331
|
lines.push("", "changes:", ...result.changes.map(formatInitChange));
|
|
@@ -8,7 +8,8 @@ import { ServeTunnelError } from "../../observer/tunnel.js";
|
|
|
8
8
|
import { cliAnalysisOptions } from "./analysis-signals.js";
|
|
9
9
|
import { onRunShutdown } from "./run-signals.js";
|
|
10
10
|
import { parseStudyCount, parseParticipantIds, parseObserverPort, wantsJson, writeResult, } from "../io.js";
|
|
11
|
-
import { exposureRequestFromOptions, followObserver, planObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
11
|
+
import { exposureRequestFromOptions, followDecision, followObserver, planObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
12
|
+
import { writeRunFindings } from "../findings.js";
|
|
12
13
|
import { formatCuaStudyHuman } from "./study-format.js";
|
|
13
14
|
import { resolveRouteShouldOpen } from "./study-route-open.js";
|
|
14
15
|
import { studyResultIdentity } from "../../run/study-result.js";
|
|
@@ -86,7 +87,9 @@ function resolveCuaSettings(args) {
|
|
|
86
87
|
}
|
|
87
88
|
const dryRun = resolveStudyDryRun(args.config, args.options.dryRun, true) ?? true;
|
|
88
89
|
const port = parseObserverPort(args.options.port ?? "0");
|
|
89
|
-
const wantsFollow = args.mode === "watch" &&
|
|
90
|
+
const wantsFollow = args.mode === "watch" &&
|
|
91
|
+
dryRun !== true &&
|
|
92
|
+
followDecision({ wantsMachine, detach: args.options.detach, serve: args.options.serve }).follow;
|
|
90
93
|
if (port === null) {
|
|
91
94
|
refuseCua(args, dryRun, "HUMANISH_WATCH_OPTION_CONFLICT", "--port must be an integer between 0 and 65535.");
|
|
92
95
|
return undefined;
|
|
@@ -99,7 +102,8 @@ function prepareCuaWatch(args, settings) {
|
|
|
99
102
|
// one surface that serves runtime E2B stream URLs, so it must sit behind edge auth.
|
|
100
103
|
const exposeValidation = validateExposure("watch", exposureRequestFromOptions(args.options), {
|
|
101
104
|
dryRun: settings.dryRun,
|
|
102
|
-
|
|
105
|
+
// A watch that will not follow, because of --detach or no interactive terminal, cannot expose.
|
|
106
|
+
detach: args.options.detach === true || (!settings.wantsMachine && !settings.wantsFollow),
|
|
103
107
|
json: settings.wantsMachine,
|
|
104
108
|
});
|
|
105
109
|
if (!exposeValidation.ok) {
|
|
@@ -116,6 +120,7 @@ function prepareCuaWatch(args, settings) {
|
|
|
116
120
|
port: args.options.port ?? "0",
|
|
117
121
|
open: settings.shouldOpen,
|
|
118
122
|
...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
|
|
123
|
+
...(args.options.serve === undefined ? {} : { serve: args.options.serve }),
|
|
119
124
|
})
|
|
120
125
|
: undefined;
|
|
121
126
|
if (finishedPlan === null)
|
|
@@ -237,6 +242,7 @@ async function reportCuaRun(args, prepared, result, live) {
|
|
|
237
242
|
}
|
|
238
243
|
writeResult(args.command, args.io, output, formatCuaStudyHuman);
|
|
239
244
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
245
|
+
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
240
246
|
if (server && (result.observer?.ok || attachedObserver)) {
|
|
241
247
|
const followResult = output.observer?.ok
|
|
242
248
|
? output.observer
|
|
@@ -27,6 +27,7 @@ export function watchFinishedPlan(args, wantsMachine, shouldOpen) {
|
|
|
27
27
|
port: args.options.port ?? "0",
|
|
28
28
|
open: shouldOpen,
|
|
29
29
|
...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
|
|
30
|
+
...(args.options.serve === undefined ? {} : { serve: args.options.serve }),
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
/** The route's static render opens only when nothing else will open the Observer. */
|
|
@@ -35,6 +35,7 @@ export function previewRouteRun(args) {
|
|
|
35
35
|
io: args.io,
|
|
36
36
|
port: args.options.port ?? "0",
|
|
37
37
|
...(args.options.detach === undefined ? {} : { detach: args.options.detach }),
|
|
38
|
+
...(args.options.serve === undefined ? {} : { serve: args.options.serve }),
|
|
38
39
|
...(openOverride === undefined ? {} : { open: openOverride }),
|
|
39
40
|
})
|
|
40
41
|
: undefined;
|
|
@@ -2,6 +2,7 @@ import { automaticAnalysisSucceeded } from "../../analysis/automatic-completion.
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { cliAnalysisOptions } from "./analysis-signals.js";
|
|
4
4
|
import { wantsJson, writeResult } from "../io.js";
|
|
5
|
+
import { writeRunFindings } from "../findings.js";
|
|
5
6
|
import { showObserver } from "../observer-follow.js";
|
|
6
7
|
import { formatScriptedStudyHuman } from "./study-format.js";
|
|
7
8
|
import { observerOpen, resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
@@ -35,6 +36,7 @@ export function scriptedRouteRun(args) {
|
|
|
35
36
|
const result = outcome.result;
|
|
36
37
|
writeResult(args.command, args.io, result, formatScriptedStudyHuman);
|
|
37
38
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
39
|
+
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
38
40
|
// Watch mode serves the Observer the route rendered through its finished run.
|
|
39
41
|
if (finishedPlan !== undefined && result.ok && result.observer !== undefined) {
|
|
40
42
|
await showObserver({
|
|
@@ -5,7 +5,8 @@ import { serveObserver } from "../../observer/render.js";
|
|
|
5
5
|
import { redactText } from "../../evidence/redaction.js";
|
|
6
6
|
import { cliAnalysisOptions } from "./analysis-signals.js";
|
|
7
7
|
import { parseObserverPort, wantsJson, writeResult, } from "../io.js";
|
|
8
|
-
import { followObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
8
|
+
import { followDecision, followObserver, showObserver, staticObserverOpen, withObserverServer, } from "../observer-follow.js";
|
|
9
|
+
import { writeRunFindings } from "../findings.js";
|
|
9
10
|
import { formatConcurrentSharedWorldStudyHuman } from "./study-format.js";
|
|
10
11
|
import { resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
11
12
|
import { rosterOf } from "../../study/parse/actors.js";
|
|
@@ -24,7 +25,9 @@ export function sharedWorldRouteRun(args) {
|
|
|
24
25
|
mode: args.mode,
|
|
25
26
|
wantsMachine,
|
|
26
27
|
});
|
|
27
|
-
const wantsFollow = args.mode === "watch" &&
|
|
28
|
+
const wantsFollow = args.mode === "watch" &&
|
|
29
|
+
dryRun !== true &&
|
|
30
|
+
followDecision({ wantsMachine, detach: args.options.detach, serve: args.options.serve }).follow;
|
|
28
31
|
const port = parseObserverPort(args.options.port ?? "0");
|
|
29
32
|
const failConcurrent = (message, runId) => {
|
|
30
33
|
const result = {
|
|
@@ -118,6 +121,7 @@ export function sharedWorldRouteRun(args) {
|
|
|
118
121
|
}
|
|
119
122
|
writeResult(args.command, args.io, output, formatConcurrentSharedWorldStudyHuman);
|
|
120
123
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
124
|
+
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
121
125
|
if (server && output.observer?.ok) {
|
|
122
126
|
await followObserver(args.io, output.observer, server);
|
|
123
127
|
}
|
|
@@ -2,6 +2,7 @@ import { automaticAnalysisSucceeded } from "../../analysis/automatic-completion.
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { cliAnalysisOptions } from "./analysis-signals.js";
|
|
4
4
|
import { wantsJson, writeResult } from "../io.js";
|
|
5
|
+
import { writeRunFindings } from "../findings.js";
|
|
5
6
|
import { showObserver } from "../observer-follow.js";
|
|
6
7
|
import { formatTerminalStudyHuman } from "./study-format.js";
|
|
7
8
|
import { observerOpen, resolveRouteShouldOpen, watchFinishedPlan } from "./study-route-open.js";
|
|
@@ -35,6 +36,7 @@ export function terminalRouteRun(args) {
|
|
|
35
36
|
const result = outcome.result;
|
|
36
37
|
writeResult(args.command, args.io, result, formatTerminalStudyHuman);
|
|
37
38
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
39
|
+
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
38
40
|
if (finishedPlan !== undefined && result.ok && result.observer !== undefined) {
|
|
39
41
|
await showObserver({
|
|
40
42
|
command: args.command,
|
|
@@ -5,7 +5,8 @@ import { runDryRun } from "../../run/dry-run.js";
|
|
|
5
5
|
import { runStudyCommand } from "./study-run.js";
|
|
6
6
|
import { addRunOptions, studyOnlyFlags } from "./run-command.js";
|
|
7
7
|
import { applyEnvFileOption, collectRepeated, dotenvPathOf, formatRunHuman, parseObserverPort, parsePositiveInteger, wantsJson, writeResult, } from "../io.js";
|
|
8
|
-
import { followObserver, formatObserverHuman, watchExposeRequested } from "../observer-follow.js";
|
|
8
|
+
import { followDecision, followObserver, formatObserverHuman, unattendedObserverWarning, watchExposeRequested, } from "../observer-follow.js";
|
|
9
|
+
import { cli } from "../invocation.js";
|
|
9
10
|
export function registerWatchCommand(parent, io) {
|
|
10
11
|
// A run's flags come from addRunOptions, the helper `run` uses, so the two cannot drift.
|
|
11
12
|
addRunOptions(parent
|
|
@@ -23,6 +24,7 @@ export function registerWatchCommand(parent, io) {
|
|
|
23
24
|
.option("--allow-email <addr>", "Edge OAuth allow rule: permit this email. Repeatable. Requires --oauth.", collectRepeated)
|
|
24
25
|
.option("--allow-domain <domain>", "Edge OAuth allow rule: permit this domain. Repeatable. Requires --oauth.", collectRepeated)
|
|
25
26
|
.option("--public-url <origin>", "Bring-your-own authed edge (Cloudflare Access/Tailscale/manual). Binds loopback and trusts your edge. Requires --expose.")
|
|
27
|
+
.addOption(new Option("--serve", "Serve the Observer and stay attached until Ctrl-C even without an interactive terminal; by default an agent's shell or a pipe gets the result and an exit.").conflicts(["detach", "json"]))
|
|
26
28
|
// Hidden: watch rejects --safe with a pointer to the library filter and to edge auth, which
|
|
27
29
|
// a bare "unknown option" would lose.
|
|
28
30
|
.addOption(new Option("--safe").hideHelp())
|
|
@@ -37,7 +39,7 @@ export function registerWatchCommand(parent, io) {
|
|
|
37
39
|
"Watch a live computer-use run from your phone through an authenticated tunnel:",
|
|
38
40
|
" humanish watch my-browser-study --expose --tunnel ngrok --oauth google --allow-email you@example.com",
|
|
39
41
|
"",
|
|
40
|
-
"Agent/CI path:",
|
|
42
|
+
"Agent/CI path (without an interactive terminal, watch prints the result and exits):",
|
|
41
43
|
" humanish watch --json --no-open",
|
|
42
44
|
"",
|
|
43
45
|
"Saved runs:",
|
|
@@ -84,7 +86,7 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
84
86
|
if (watchExposeRequested(options)) {
|
|
85
87
|
refuseWatch(command, io, options.cwd, {
|
|
86
88
|
code: "HUMANISH_WATCH_OPTION_CONFLICT",
|
|
87
|
-
message:
|
|
89
|
+
message: `--expose/--tunnel/--oauth apply only to a live computer-use run; to expose finished evidence use \`${cli("observe --all --expose")}\`.`,
|
|
88
90
|
});
|
|
89
91
|
return;
|
|
90
92
|
}
|
|
@@ -107,7 +109,12 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
107
109
|
: options.open === true
|
|
108
110
|
? true
|
|
109
111
|
: !wantsMachine && process.stdout.isTTY === true;
|
|
110
|
-
const
|
|
112
|
+
const decision = followDecision({
|
|
113
|
+
wantsMachine,
|
|
114
|
+
detach: options.detach,
|
|
115
|
+
serve: options.serve,
|
|
116
|
+
});
|
|
117
|
+
const wantsFollow = decision.follow;
|
|
111
118
|
const staticOpen = wantsFollow ? false : shouldOpen;
|
|
112
119
|
const rendered = await renderWatchEvidence(io, command, options, target.requestedParticipantCount, staticOpen);
|
|
113
120
|
if (rendered === undefined)
|
|
@@ -116,6 +123,7 @@ async function handleWatch(io, named, parsed, command) {
|
|
|
116
123
|
follow: wantsFollow,
|
|
117
124
|
open: shouldOpen,
|
|
118
125
|
port: target.port,
|
|
126
|
+
unattended: decision.unattended,
|
|
119
127
|
});
|
|
120
128
|
}
|
|
121
129
|
function refuseWatch(command, io, cwd, refusal) {
|
|
@@ -174,6 +182,8 @@ async function renderWatchEvidence(io, command, options, requestedParticipantCou
|
|
|
174
182
|
async function reportWatch(io, command, rendered, serve) {
|
|
175
183
|
let server = null;
|
|
176
184
|
let result = rendered;
|
|
185
|
+
if (rendered.ok && serve.unattended)
|
|
186
|
+
result = { ...rendered, warnings: [...rendered.warnings, unattendedObserverWarning("watch")] };
|
|
177
187
|
if (rendered.ok && serve.follow) {
|
|
178
188
|
server = await serveObserver(rendered, { open: serve.open, port: serve.port });
|
|
179
189
|
result = {
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type KeyResolutionDeps, type DotenvLoad } from "../keys/key-resolution.js";
|
|
2
|
+
import { type HumanishInstall } from "../substrates/e2b/peer-install.js";
|
|
2
3
|
import { type DetectLocalAgentsOptions } from "../actors/local-agent/cli.js";
|
|
3
4
|
import { type StudySetupCheckArgs } from "../study/doctor.js";
|
|
4
5
|
declare const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
@@ -35,7 +36,9 @@ export interface DoctorResult {
|
|
|
35
36
|
*/
|
|
36
37
|
export declare const DESKTOP_SDK_FLOOR = "2.3.2";
|
|
37
38
|
/** The advisory `doctor` attaches to an installed desktop SDK older than the floor, else undefined. */
|
|
38
|
-
export declare function desktopSdkAdvisory(version: string | undefined): string | undefined;
|
|
39
|
+
export declare function desktopSdkAdvisory(version: string | undefined, install?: () => HumanishInstall): string | undefined;
|
|
40
|
+
/** The doctor line for an absent desktop SDK, with the install command for where humanish runs from. */
|
|
41
|
+
export declare function missingDesktopSdkMessage(install: HumanishInstall): string;
|
|
39
42
|
/** A row as a check writes it. Doctor derives `status`; an advisory row sets "note" itself. */
|
|
40
43
|
export type DoctorCheckDraft = Omit<DoctorResult["checks"][number], "status"> & {
|
|
41
44
|
status?: "note";
|