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
package/dist/cli/doctor.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { probeKeySources, } from "../keys/key-resolution.js";
|
|
6
|
+
import { desktopPeerAdvice, humanishInstall, } from "../substrates/e2b/peer-install.js";
|
|
5
7
|
import { nodeSupportsTui, terminalSurfaceMessage, TUI_BUNDLE_URL } from "../tui/contract.js";
|
|
6
|
-
import { detectLocalAgents, localAgentDoctorMessage, NO_LOCAL_AGENT_MESSAGE, } from "../actors/local-agent/cli.js";
|
|
8
|
+
import { detectLocalAgents, localAgentDoctorMessage, localAgentReady, NO_LOCAL_AGENT_MESSAGE, } from "../actors/local-agent/cli.js";
|
|
9
|
+
import { leftoverClaudeTranscripts } from "../actors/local-agent/claude-participant.js";
|
|
7
10
|
import { studySetupChecks, studiesByRequiredKey, } from "../study/doctor.js";
|
|
8
11
|
import { prepareSelectedOutputDirectory, } from "../run/contained-output.js";
|
|
9
12
|
import { implicitProjectDirectoryExists, readImplicitProjectFile, validateCwd, } from "../run/project.js";
|
|
13
|
+
import { cli } from "./invocation.js";
|
|
10
14
|
const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
11
15
|
/**
|
|
12
16
|
* Version 2.3.1 stopped holding a launched background command's event stream open. Version 2.3.2
|
|
@@ -16,7 +20,7 @@ const DOCTOR_SCHEMA = "humanish.doctor-result.v1";
|
|
|
16
20
|
*/
|
|
17
21
|
export const DESKTOP_SDK_FLOOR = "2.3.2";
|
|
18
22
|
/** The advisory `doctor` attaches to an installed desktop SDK older than the floor, else undefined. */
|
|
19
|
-
export function desktopSdkAdvisory(version) {
|
|
23
|
+
export function desktopSdkAdvisory(version, install = humanishInstall) {
|
|
20
24
|
if (version === undefined)
|
|
21
25
|
return undefined;
|
|
22
26
|
const parse = (value) => value
|
|
@@ -31,9 +35,14 @@ export function desktopSdkAdvisory(version) {
|
|
|
31
35
|
(have[0] === floor[0] &&
|
|
32
36
|
(have[1] < floor[1] || (have[1] === floor[1] && have[2] < floor[2])));
|
|
33
37
|
return older
|
|
34
|
-
? `@e2b/desktop ${version} is older than ${DESKTOP_SDK_FLOOR}, the supported floor for background command cleanup and stdin handles (older releases could keep the CLI alive for minutes after its result).
|
|
38
|
+
? `@e2b/desktop ${version} is older than ${DESKTOP_SDK_FLOOR}, the supported floor for background command cleanup and stdin handles (older releases could keep the CLI alive for minutes after its result). To update it: ${desktopPeerAdvice(install(), "@e2b/desktop@latest").advice}`
|
|
35
39
|
: undefined;
|
|
36
40
|
}
|
|
41
|
+
/** The doctor line for an absent desktop SDK, with the install command for where humanish runs from. */
|
|
42
|
+
export function missingDesktopSdkMessage(install) {
|
|
43
|
+
const { where, advice } = desktopPeerAdvice(install);
|
|
44
|
+
return `optional peer @e2b/desktop is not installed${where}: dry runs work, but a live run with a desktop participant fails when it starts. ${advice}`;
|
|
45
|
+
}
|
|
37
46
|
/** The version of the @e2b/desktop that `import("@e2b/desktop")` resolves to from here, if readable. */
|
|
38
47
|
async function installedDesktopSdkVersion() {
|
|
39
48
|
try {
|
|
@@ -86,12 +95,13 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
86
95
|
await desktopSdkCheck(setup),
|
|
87
96
|
terminalSurfaceCheck(),
|
|
88
97
|
...localAgentChecks(agents),
|
|
98
|
+
await claudeTranscriptCheck(env),
|
|
89
99
|
...keyChecks(probes, receivingKey, setup, keyUsers),
|
|
90
100
|
...(setup?.checks ?? [
|
|
91
101
|
{
|
|
92
102
|
name: "setup route",
|
|
93
103
|
ok: true,
|
|
94
|
-
message:
|
|
104
|
+
message: `General capabilities only. Use ${cli("doctor --study <study>")} for the selected participant's requirements and separate analysis readiness.`,
|
|
95
105
|
},
|
|
96
106
|
]),
|
|
97
107
|
];
|
|
@@ -164,7 +174,7 @@ async function sourceCheck(projectRoot) {
|
|
|
164
174
|
ok,
|
|
165
175
|
message: ok
|
|
166
176
|
? "committed humanish/ source directory is present and safe to read"
|
|
167
|
-
:
|
|
177
|
+
: `no readable humanish/ source directory; run ${cli("init --yes")}`,
|
|
168
178
|
};
|
|
169
179
|
}
|
|
170
180
|
async function runtimeIgnoreCheck(projectRoot) {
|
|
@@ -174,7 +184,7 @@ async function runtimeIgnoreCheck(projectRoot) {
|
|
|
174
184
|
ok,
|
|
175
185
|
message: ok
|
|
176
186
|
? ".gitignore lists .humanish/"
|
|
177
|
-
:
|
|
187
|
+
: `.gitignore does not list .humanish/; run ${cli("init --yes")}`,
|
|
178
188
|
};
|
|
179
189
|
}
|
|
180
190
|
/**
|
|
@@ -205,7 +215,7 @@ async function desktopSdkCheck(setup) {
|
|
|
205
215
|
? `optional peer @e2b/desktop ${version ?? "(version unread)"} is installed; provider access is not tested${advisory === undefined ? "" : `. ${advisory}`}`
|
|
206
216
|
: setup?.desktop === false
|
|
207
217
|
? "optional peer @e2b/desktop is absent; not required by the selected route"
|
|
208
|
-
:
|
|
218
|
+
: missingDesktopSdkMessage(humanishInstall()),
|
|
209
219
|
};
|
|
210
220
|
}
|
|
211
221
|
/**
|
|
@@ -262,7 +272,9 @@ async function probeDoctorKeys(cwd, env, study, keyDeps, dotenv) {
|
|
|
262
272
|
* The operator's own coding agents, one row each, reported as a capability and never a gate: a
|
|
263
273
|
* machine with none is not broken, it just needs a provider key. These rows exist because "go make
|
|
264
274
|
* an API key" is where most people trying humanish stop, and a developer very often already has one
|
|
265
|
-
* of these signed in. An agent that
|
|
275
|
+
* of these signed in. An agent that cannot run participants (not signed in under the participant's
|
|
276
|
+
* environment, sign-in unknown, or a Claude Code below the floor) is a note; `--study` fails on it
|
|
277
|
+
* when the study selects that agent.
|
|
266
278
|
*/
|
|
267
279
|
function localAgentChecks(agents) {
|
|
268
280
|
if (agents.length === 0)
|
|
@@ -270,10 +282,39 @@ function localAgentChecks(agents) {
|
|
|
270
282
|
return agents.map((agent) => ({
|
|
271
283
|
name: `local agent ${agent.bin}`,
|
|
272
284
|
ok: true,
|
|
273
|
-
...(agent
|
|
285
|
+
...(localAgentReady(agent) ? {} : { status: "note" }),
|
|
274
286
|
message: localAgentDoctorMessage(agent),
|
|
275
287
|
}));
|
|
276
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Earlier releases let Claude Code save a transcript for every participant, with each screenshot it
|
|
291
|
+
* read, under its projects folder and outside `.humanish/`. Participants now run with
|
|
292
|
+
* `--no-session-persistence`; this row counts what earlier runs left and prints the command that
|
|
293
|
+
* removes it. doctor never deletes anything.
|
|
294
|
+
*/
|
|
295
|
+
async function claudeTranscriptCheck(env) {
|
|
296
|
+
const found = await leftoverClaudeTranscripts(env, env.HOME ?? homedir(), (directory) => readdir(directory));
|
|
297
|
+
const name = "claude participant transcripts";
|
|
298
|
+
if ("unreadable" in found)
|
|
299
|
+
return {
|
|
300
|
+
name,
|
|
301
|
+
ok: true,
|
|
302
|
+
status: "note",
|
|
303
|
+
message: `could not list ${found.directory} (${found.unreadable}), so transcripts from earlier Claude Code participants were not counted. Check the folder for names ending in -humanish-claude-session-XXXXXX.`,
|
|
304
|
+
};
|
|
305
|
+
const count = found.names.length;
|
|
306
|
+
const folders = `${count} transcript folder${count === 1 ? "" : "s"} from earlier Claude Code participants under ${found.directory} hold every screenshot those runs read, unblurred.`;
|
|
307
|
+
return {
|
|
308
|
+
name,
|
|
309
|
+
ok: true,
|
|
310
|
+
...(count === 0 ? {} : { status: "note" }),
|
|
311
|
+
message: count === 0
|
|
312
|
+
? `no transcript from an earlier Claude Code participant under ${found.directory}`
|
|
313
|
+
: found.removeCommand === undefined
|
|
314
|
+
? `${folders} Remove these folders there: ${found.names.join(", ")}`
|
|
315
|
+
: `${folders} Remove them with: ${found.removeCommand}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
277
318
|
/** "try-live", "try-live and local-browser", "a, b and c". */
|
|
278
319
|
function studyList(studies) {
|
|
279
320
|
return studies.length < 2
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { AutomaticAnalysisOutcome } from "../analysis/job.js";
|
|
3
|
+
import type { AnalysisArtifact, LoadedAnalysis } from "../analysis/types.js";
|
|
4
|
+
import { type CliIo } from "./io.js";
|
|
5
|
+
export declare const ANALYSIS_FINDINGS_SCHEMA = "humanish.analysis-findings.v1";
|
|
6
|
+
type Finding = NonNullable<AnalysisArtifact["result"]>["findings"][number];
|
|
7
|
+
/** `ready` and `stale` carry findings; every other state carries none and says why in `message`. */
|
|
8
|
+
type FindingsState = "ready" | "stale" | "running" | "none" | "skipped" | "failed" | "dry_run" | "unavailable";
|
|
9
|
+
interface FindingEvidence {
|
|
10
|
+
id: string;
|
|
11
|
+
streamId: string;
|
|
12
|
+
kind: string;
|
|
13
|
+
/** How the finding's observations used this item: visual, action, participant_statement, inference. */
|
|
14
|
+
bases: string[];
|
|
15
|
+
/** Index of the latest retained capture at this item, from 0; null before any capture. */
|
|
16
|
+
frame: number | null;
|
|
17
|
+
/** Time since the first retained capture, not a video offset. */
|
|
18
|
+
elapsedMs: number | null;
|
|
19
|
+
at: string | null;
|
|
20
|
+
/** The capture file relative to the project directory, or null for nonvisual evidence. */
|
|
21
|
+
capture: string | null;
|
|
22
|
+
}
|
|
23
|
+
interface FindingView {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
summary: string;
|
|
27
|
+
impact: Finding["impact"];
|
|
28
|
+
confidence: Finding["confidence"];
|
|
29
|
+
recovery: Finding["recovery"];
|
|
30
|
+
affected: {
|
|
31
|
+
streamId: string;
|
|
32
|
+
label: string;
|
|
33
|
+
}[];
|
|
34
|
+
exposedCount: number;
|
|
35
|
+
evidence: FindingEvidence[];
|
|
36
|
+
nextStep: string;
|
|
37
|
+
/** The latest human review note on this finding version (`analyze correct`), or null. */
|
|
38
|
+
correction: {
|
|
39
|
+
status: "confirmed" | "dismissed" | "amended";
|
|
40
|
+
reason: string;
|
|
41
|
+
replacementClaim: string | null;
|
|
42
|
+
} | null;
|
|
43
|
+
}
|
|
44
|
+
export interface AnalysisFindings {
|
|
45
|
+
schema: typeof ANALYSIS_FINDINGS_SCHEMA;
|
|
46
|
+
runId: string;
|
|
47
|
+
state: FindingsState;
|
|
48
|
+
/** The stable code behind a state other than ready, when one is recorded. */
|
|
49
|
+
reason: string | null;
|
|
50
|
+
message: string;
|
|
51
|
+
/** The command that gets findings for this run, or null when none can. */
|
|
52
|
+
next: string | null;
|
|
53
|
+
analysisId: string | null;
|
|
54
|
+
status: AnalysisArtifact["status"] | null;
|
|
55
|
+
provider: AnalysisArtifact["provider"] | null;
|
|
56
|
+
model: string | null;
|
|
57
|
+
completedAt: string | null;
|
|
58
|
+
estimatedCostUsd: number | null;
|
|
59
|
+
/** The run directory relative to the project directory; null when the run was not found. */
|
|
60
|
+
runPath: string | null;
|
|
61
|
+
/** analysis.json relative to the project directory. */
|
|
62
|
+
path: string | null;
|
|
63
|
+
summary: string | null;
|
|
64
|
+
/** Highest priority first, as the analysis ranked them. */
|
|
65
|
+
findings: FindingView[];
|
|
66
|
+
limitations: string[];
|
|
67
|
+
warnings: string[];
|
|
68
|
+
}
|
|
69
|
+
interface FindingsSource {
|
|
70
|
+
runId: string;
|
|
71
|
+
/** The bundle's mode; undefined when run.json could not be read. */
|
|
72
|
+
mode: string | undefined;
|
|
73
|
+
/** The run directory relative to the project directory. */
|
|
74
|
+
runRoot: string;
|
|
75
|
+
loaded: LoadedAnalysis;
|
|
76
|
+
/** ` --cwd <dir>` when the commands this view names need it, or "". */
|
|
77
|
+
cwdFlag: string;
|
|
78
|
+
}
|
|
79
|
+
/** The findings view of a run's selected analysis, or why it has none. */
|
|
80
|
+
export declare function analysisFindings(source: FindingsSource): AnalysisFindings;
|
|
81
|
+
/**
|
|
82
|
+
* The run's bundle mode and its selected analysis: `analysisId`, or the version `analyze show`
|
|
83
|
+
* defaults to. Null when the run is missing or its storage is unsafe.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readRunAnalysis(cwd: string, run: string, analysisId?: string): Promise<FindingsSource | null>;
|
|
86
|
+
/** The view of a run that could not be found. */
|
|
87
|
+
export declare function missingRunFindings(cwd: string, run: string): AnalysisFindings;
|
|
88
|
+
/** The findings of a run's selected analysis, or why it has none. */
|
|
89
|
+
export declare function readRunFindings(cwd: string, run: string, analysisId?: string): Promise<AnalysisFindings>;
|
|
90
|
+
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
91
|
+
export declare function formatFindings(view: AnalysisFindings): string[];
|
|
92
|
+
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
93
|
+
export declare function formatFindingsSummary(view: AnalysisFindings, fullCommand: string, limit?: number): string[];
|
|
94
|
+
/**
|
|
95
|
+
* The end of a live run's human output, after its `analysis:` line: the findings of the analysis
|
|
96
|
+
* the run just completed. Prints nothing for --json, or when the run's analysis did not complete,
|
|
97
|
+
* since that line already says why.
|
|
98
|
+
*/
|
|
99
|
+
export declare function writeRunFindings(command: Command, io: CliIo, cwd: string, result: {
|
|
100
|
+
runId?: string;
|
|
101
|
+
automaticAnalysis?: AutomaticAnalysisOutcome;
|
|
102
|
+
}): Promise<void>;
|
|
103
|
+
export {};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// A run's analysis findings for `review`, `analyze show` and the end of a live `run`: the
|
|
2
|
+
// `humanish.analysis-findings.v1` projection and its text. The analysis artifact stays the record;
|
|
3
|
+
// this reads the version `analyze show` selects and never starts an analysis.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { DEFAULT_ANALYSIS_MAX_COST_USD } from "../analysis/automatic-config.js";
|
|
6
|
+
import { loadAnalysis } from "../analysis/load.js";
|
|
7
|
+
import { projectShareCheckedAnalysis } from "../analysis/sharing.js";
|
|
8
|
+
import { loadRunBundlePrepared, resolveRunPath } from "../run/locate.js";
|
|
9
|
+
import { resolvePhysicalCwd, runIdOf } from "../run/paths.js";
|
|
10
|
+
import { plural } from "../run/text.js";
|
|
11
|
+
import { shellQuote } from "../substrates/shell.js";
|
|
12
|
+
import { analysisOutcomeText, wantsJson } from "./io.js";
|
|
13
|
+
import { cli } from "./invocation.js";
|
|
14
|
+
export const ANALYSIS_FINDINGS_SCHEMA = "humanish.analysis-findings.v1";
|
|
15
|
+
/** ` --cwd <dir>`, or "" when `cwd` is the current directory. */
|
|
16
|
+
function cwdFlag(cwd) {
|
|
17
|
+
if (path.resolve(cwd) === process.cwd())
|
|
18
|
+
return "";
|
|
19
|
+
return ` --cwd ${/^[\w@%+=:,./-]+$/.test(cwd) ? cwd : shellQuote(cwd)}`;
|
|
20
|
+
}
|
|
21
|
+
function analyzeCommand(source, provider) {
|
|
22
|
+
const run = cli(`analyze --run ${source.runId}${source.cwdFlag}`);
|
|
23
|
+
return provider === "codex"
|
|
24
|
+
? `${run} --provider codex`
|
|
25
|
+
: `${run} --max-cost ${DEFAULT_ANALYSIS_MAX_COST_USD}`;
|
|
26
|
+
}
|
|
27
|
+
function evidenceOf(finding, analysis, runRoot) {
|
|
28
|
+
const bases = new Map();
|
|
29
|
+
for (const observation of finding.observations)
|
|
30
|
+
for (const id of observation.evidenceIds) {
|
|
31
|
+
const set = bases.get(id) ?? new Set();
|
|
32
|
+
set.add(observation.basis);
|
|
33
|
+
bases.set(id, set);
|
|
34
|
+
}
|
|
35
|
+
const byId = new Map(analysis.evidence.map((entry) => [entry.id, entry]));
|
|
36
|
+
return [...bases].flatMap(([id, used]) => {
|
|
37
|
+
const entry = byId.get(id);
|
|
38
|
+
if (!entry)
|
|
39
|
+
return [];
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
id,
|
|
43
|
+
streamId: entry.streamId,
|
|
44
|
+
kind: entry.kind,
|
|
45
|
+
bases: [...used],
|
|
46
|
+
frame: entry.frame,
|
|
47
|
+
elapsedMs: entry.elapsedMs,
|
|
48
|
+
at: entry.at,
|
|
49
|
+
capture: entry.capture ? path.join(runRoot, entry.capture.path) : null,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function findingView(finding, loaded, runRoot) {
|
|
55
|
+
const labels = new Map(loaded.analysis.participants.map((p) => [p.streamId, p.label]));
|
|
56
|
+
const correction = loaded.corrections.filter((entry) => entry.findingId === finding.id).at(-1);
|
|
57
|
+
return {
|
|
58
|
+
id: finding.id,
|
|
59
|
+
title: finding.title,
|
|
60
|
+
summary: finding.summary,
|
|
61
|
+
impact: finding.impact,
|
|
62
|
+
confidence: finding.confidence,
|
|
63
|
+
recovery: finding.recovery,
|
|
64
|
+
affected: finding.affectedStreamIds.map((streamId) => ({
|
|
65
|
+
streamId,
|
|
66
|
+
label: labels.get(streamId) ?? streamId,
|
|
67
|
+
})),
|
|
68
|
+
exposedCount: finding.exposedStreamIds.length,
|
|
69
|
+
evidence: evidenceOf(finding, loaded.analysis, runRoot),
|
|
70
|
+
nextStep: finding.nextStep,
|
|
71
|
+
correction: correction
|
|
72
|
+
? {
|
|
73
|
+
status: correction.status,
|
|
74
|
+
reason: correction.reason,
|
|
75
|
+
replacementClaim: correction.replacementClaim,
|
|
76
|
+
}
|
|
77
|
+
: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** A view with no findings: what happened and the command that gets them. */
|
|
81
|
+
function withoutFindings(source, state, reason, message, next) {
|
|
82
|
+
return {
|
|
83
|
+
schema: ANALYSIS_FINDINGS_SCHEMA,
|
|
84
|
+
runId: source.runId,
|
|
85
|
+
state,
|
|
86
|
+
reason,
|
|
87
|
+
message,
|
|
88
|
+
next,
|
|
89
|
+
analysisId: null,
|
|
90
|
+
status: null,
|
|
91
|
+
provider: null,
|
|
92
|
+
model: null,
|
|
93
|
+
completedAt: null,
|
|
94
|
+
estimatedCostUsd: null,
|
|
95
|
+
runPath: source.runRoot || null,
|
|
96
|
+
path: null,
|
|
97
|
+
summary: null,
|
|
98
|
+
findings: [],
|
|
99
|
+
limitations: [],
|
|
100
|
+
warnings: source.loaded.warnings,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** The findings view of a run's selected analysis, or why it has none. */
|
|
104
|
+
export function analysisFindings(source) {
|
|
105
|
+
if (source.mode !== undefined && source.mode !== "live")
|
|
106
|
+
return withoutFindings(source, "dry_run", null, "This is a dry run: no participant used the product, so there is nothing to analyze. Findings come from live runs.", null);
|
|
107
|
+
const loaded = projectShareCheckedAnalysis(source.loaded);
|
|
108
|
+
const analysis = loaded.analysis;
|
|
109
|
+
if (analysis?.result && (loaded.state === "ready" || loaded.state === "stale")) {
|
|
110
|
+
const result = analysis.result;
|
|
111
|
+
const stale = loaded.state === "stale";
|
|
112
|
+
return {
|
|
113
|
+
schema: ANALYSIS_FINDINGS_SCHEMA,
|
|
114
|
+
runId: source.runId,
|
|
115
|
+
state: loaded.state,
|
|
116
|
+
reason: stale ? (loaded.warnings[0] ?? null) : null,
|
|
117
|
+
message: stale
|
|
118
|
+
? `The run's evidence changed after analysis ${analysis.id}, so its findings may not match the run.`
|
|
119
|
+
: `Analysis ${analysis.id} is ${analysis.status}, with ${plural(result.findings.length, "finding")}.`,
|
|
120
|
+
next: stale ? analyzeCommand(source, analysis.provider) : null,
|
|
121
|
+
analysisId: analysis.id,
|
|
122
|
+
status: analysis.status,
|
|
123
|
+
provider: analysis.provider,
|
|
124
|
+
model: analysis.config.model,
|
|
125
|
+
completedAt: analysis.completedAt,
|
|
126
|
+
estimatedCostUsd: analysis.usage.estimatedCostUsd,
|
|
127
|
+
runPath: source.runRoot,
|
|
128
|
+
path: path.join(source.runRoot, "analysis", analysis.id, "analysis.json"),
|
|
129
|
+
summary: result.summary,
|
|
130
|
+
findings: result.findings.map((finding) => findingView(finding, { ...loaded, analysis }, source.runRoot)),
|
|
131
|
+
limitations: result.limitations,
|
|
132
|
+
warnings: loaded.warnings,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (loaded.warnings.includes("ANALYSIS_SENSITIVE_TEXT_QUARANTINED"))
|
|
136
|
+
return withoutFindings(source, "unavailable", "ANALYSIS_SENSITIVE_TEXT_QUARANTINED", `The analysis text matched a sensitive-text pattern, so it is not printed. ${cli(`analyze show --run ${source.runId}${source.cwdFlag} --json`)} prints the raw record.`, null);
|
|
137
|
+
const job = loaded.automatic;
|
|
138
|
+
if (job?.state === "queued" || job?.state === "running")
|
|
139
|
+
return withoutFindings(source, "running", null, "The automatic analysis is still running. Check again when it finishes.", cli(`review --run ${source.runId}${source.cwdFlag}`));
|
|
140
|
+
if (analysis && analysis.result === null)
|
|
141
|
+
return withoutFindings(source, "failed", analysis.error, `Analysis attempt ${analysis.id} ${analysis.status === "cancelled" ? "was cancelled" : "failed"}${analysis.error ? ` (${analysis.error})` : ""} and left no findings.`, analyzeCommand(source, analysis.provider));
|
|
142
|
+
if (job?.state === "failed" || job?.state === "cancelled") {
|
|
143
|
+
const outcome = analysisOutcomeText(job);
|
|
144
|
+
return withoutFindings(source, "failed", job.reason, `The automatic analysis ${outcome.startsWith("failed") ? "" : "was "}${outcome}.`, analyzeCommand(source, job.reason === "AUTOMATIC_ANALYSIS_CODEX_UNAVAILABLE" ? "codex" : undefined));
|
|
145
|
+
}
|
|
146
|
+
if (job?.state === "skipped") {
|
|
147
|
+
const refused = job.reason === "AUTOMATIC_ANALYSIS_ADMISSION_REFUSED";
|
|
148
|
+
return withoutFindings(source, "skipped", job.reason, `The automatic analysis was ${analysisOutcomeText(job)}.${refused ? " The dry run prints its cost estimate; run it again with a --max-cost above the estimate." : ""}`, job.reason === "AUTOMATIC_ANALYSIS_NO_PARTICIPANT_EVIDENCE"
|
|
149
|
+
? null
|
|
150
|
+
: `${analyzeCommand(source, undefined)}${refused ? " --dry-run" : ""}`);
|
|
151
|
+
}
|
|
152
|
+
if (job !== undefined || loaded.state === "invalid")
|
|
153
|
+
return withoutFindings(source, "unavailable", job?.reason ?? loaded.warnings[0] ?? null, "This run's analysis records could not be read or checked, so no findings are shown.", cli(`analyze list --run ${source.runId}${source.cwdFlag}`));
|
|
154
|
+
return withoutFindings(source, "none", null, "No analysis has run for this run.", analyzeCommand(source, undefined));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The run's bundle mode and its selected analysis: `analysisId`, or the version `analyze show`
|
|
158
|
+
* defaults to. Null when the run is missing or its storage is unsafe.
|
|
159
|
+
*/
|
|
160
|
+
export async function readRunAnalysis(cwd, run, analysisId) {
|
|
161
|
+
const physical = await resolvePhysicalCwd(cwd);
|
|
162
|
+
const prepared = await resolveRunPath(physical, run).catch(() => null);
|
|
163
|
+
if (!prepared)
|
|
164
|
+
return null;
|
|
165
|
+
const bundle = await loadRunBundlePrepared(physical, prepared).catch(() => null);
|
|
166
|
+
return {
|
|
167
|
+
runId: runIdOf(prepared),
|
|
168
|
+
mode: bundle?.bundle.mode,
|
|
169
|
+
runRoot: prepared.relativeRunRoot,
|
|
170
|
+
loaded: await loadAnalysis(prepared, analysisId),
|
|
171
|
+
cwdFlag: cwdFlag(cwd),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** The view of a run that could not be found. */
|
|
175
|
+
export function missingRunFindings(cwd, run) {
|
|
176
|
+
const loaded = {
|
|
177
|
+
state: "invalid",
|
|
178
|
+
analysis: null,
|
|
179
|
+
corrections: [],
|
|
180
|
+
warnings: ["ANALYSIS_RUN_NOT_FOUND"],
|
|
181
|
+
};
|
|
182
|
+
const flag = cwdFlag(cwd);
|
|
183
|
+
return withoutFindings({ runId: run, mode: undefined, runRoot: "", loaded, cwdFlag: flag }, "unavailable", "ANALYSIS_RUN_NOT_FOUND", `Run ${run} was not found or its storage is unsafe.`, cli(`runs${flag}`));
|
|
184
|
+
}
|
|
185
|
+
/** The findings of a run's selected analysis, or why it has none. */
|
|
186
|
+
export async function readRunFindings(cwd, run, analysisId) {
|
|
187
|
+
const source = await readRunAnalysis(cwd, run, analysisId);
|
|
188
|
+
return source ? analysisFindings(source) : missingRunFindings(cwd, run);
|
|
189
|
+
}
|
|
190
|
+
const IMPACT_TEXT = {
|
|
191
|
+
blocked_task: "blocked task",
|
|
192
|
+
friction: "friction",
|
|
193
|
+
recovery: "recovery",
|
|
194
|
+
uncertain: "uncertain",
|
|
195
|
+
};
|
|
196
|
+
const RECOVERY_TEXT = {
|
|
197
|
+
recovered: "recovered",
|
|
198
|
+
not_observed: "no recovery observed",
|
|
199
|
+
unknown: "recovery unknown",
|
|
200
|
+
};
|
|
201
|
+
/** `+1:05` from milliseconds since the first retained capture. */
|
|
202
|
+
function elapsedText(ms) {
|
|
203
|
+
const seconds = Math.floor(ms / 1000);
|
|
204
|
+
const minutes = Math.floor(seconds / 60);
|
|
205
|
+
const hours = Math.floor(minutes / 60);
|
|
206
|
+
const ss = String(seconds % 60).padStart(2, "0");
|
|
207
|
+
return hours > 0
|
|
208
|
+
? `+${hours}:${String(minutes % 60).padStart(2, "0")}:${ss}`
|
|
209
|
+
: `+${minutes}:${ss}`;
|
|
210
|
+
}
|
|
211
|
+
/** One participant's cited moments: each frame once, at its earliest cited time. */
|
|
212
|
+
function momentsText(evidence) {
|
|
213
|
+
const frames = new Map();
|
|
214
|
+
let unframed = 0;
|
|
215
|
+
for (const entry of evidence) {
|
|
216
|
+
if (entry.frame === null) {
|
|
217
|
+
unframed++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const earlier = frames.get(entry.frame);
|
|
221
|
+
if (earlier === undefined ||
|
|
222
|
+
(entry.elapsedMs !== null && (earlier ?? Infinity) > entry.elapsedMs))
|
|
223
|
+
frames.set(entry.frame, entry.elapsedMs);
|
|
224
|
+
}
|
|
225
|
+
const parts = [...frames]
|
|
226
|
+
.sort(([a], [b]) => a - b)
|
|
227
|
+
.map(([frame, ms]) => `frame ${frame}${ms === null ? "" : ` at ${elapsedText(ms)}`}`);
|
|
228
|
+
if (unframed > 0)
|
|
229
|
+
parts.push(`${plural(unframed, "item")} with no frame`);
|
|
230
|
+
return parts.join(", ");
|
|
231
|
+
}
|
|
232
|
+
function findingLines(finding, runRoot) {
|
|
233
|
+
const cited = [...new Set(finding.evidence.map((entry) => entry.streamId))];
|
|
234
|
+
const labels = new Map(finding.affected.map((p) => [p.streamId, p.label]));
|
|
235
|
+
const affectedIds = finding.affected.map((p) => p.streamId);
|
|
236
|
+
const streams = [...affectedIds, ...cited.filter((id) => !labels.has(id))];
|
|
237
|
+
const captures = [
|
|
238
|
+
...new Set(finding.evidence
|
|
239
|
+
.filter((entry) => entry.capture !== null)
|
|
240
|
+
.sort((a, b) => (a.frame ?? 0) - (b.frame ?? 0))
|
|
241
|
+
.map((entry) => (runRoot ? path.relative(runRoot, entry.capture) : entry.capture))),
|
|
242
|
+
];
|
|
243
|
+
const correction = finding.correction;
|
|
244
|
+
return [
|
|
245
|
+
`${finding.id} ${finding.title}`,
|
|
246
|
+
` impact: ${IMPACT_TEXT[finding.impact]} · confidence: ${finding.confidence} · recovery: ${RECOVERY_TEXT[finding.recovery]}`,
|
|
247
|
+
` ${finding.summary}`,
|
|
248
|
+
` affected: ${finding.affected.length} of ${plural(finding.exposedCount, "exposed participant")}`,
|
|
249
|
+
...streams.map((streamId) => {
|
|
250
|
+
const moments = momentsText(finding.evidence.filter((entry) => entry.streamId === streamId));
|
|
251
|
+
const label = `${labels.get(streamId) ?? streamId}${labels.has(streamId) ? "" : " (cited, not affected)"}`;
|
|
252
|
+
return ` - ${label}${moments ? `: ${moments}` : ""}`;
|
|
253
|
+
}),
|
|
254
|
+
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
255
|
+
` next step: ${finding.nextStep}`,
|
|
256
|
+
...(correction === null
|
|
257
|
+
? []
|
|
258
|
+
: [
|
|
259
|
+
` human review: ${correction.status}, ${correction.reason}${correction.replacementClaim ? ` Amended claim: ${correction.replacementClaim}` : ""}`,
|
|
260
|
+
]),
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
264
|
+
export function formatFindings(view) {
|
|
265
|
+
if (view.state !== "ready" && view.state !== "stale")
|
|
266
|
+
return [`findings: none. ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
267
|
+
const model = [view.provider, view.model].filter(Boolean).join(" ");
|
|
268
|
+
const cost = view.estimatedCostUsd === null ? "" : `, estimated $${view.estimatedCostUsd.toFixed(2)}`;
|
|
269
|
+
return [
|
|
270
|
+
`findings: ${view.findings.length} from analysis ${view.analysisId} (${view.status}, ${model}${cost})`,
|
|
271
|
+
...(view.state === "stale" ? [`warning: ${view.message}`, `next: ${view.next}`] : []),
|
|
272
|
+
...(view.summary ? [view.summary] : []),
|
|
273
|
+
...view.findings.flatMap((finding) => ["", ...findingLines(finding, view.runPath ?? "")]),
|
|
274
|
+
...(view.limitations.length === 0
|
|
275
|
+
? []
|
|
276
|
+
: ["", "limitations:", ...view.limitations.map((limitation) => `- ${limitation}`)]),
|
|
277
|
+
...view.warnings.map((warning) => `warning: ${warning}`),
|
|
278
|
+
"",
|
|
279
|
+
`analysis: ${view.path}`,
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
283
|
+
export function formatFindingsSummary(view, fullCommand, limit = 3) {
|
|
284
|
+
if (view.state !== "ready" && view.state !== "stale")
|
|
285
|
+
return [`findings: ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
286
|
+
const shown = view.findings.slice(0, limit);
|
|
287
|
+
return [
|
|
288
|
+
`findings: ${view.findings.length === 0 ? "none" : view.findings.length}${view.state === "stale" ? " (stale)" : ""}`,
|
|
289
|
+
...shown.map((finding) => `- ${finding.id} ${IMPACT_TEXT[finding.impact]}, ${finding.confidence} confidence, ${RECOVERY_TEXT[finding.recovery]}: ${finding.title}`),
|
|
290
|
+
...(view.findings.length > limit
|
|
291
|
+
? [`- and ${plural(view.findings.length - limit, "more finding")}`]
|
|
292
|
+
: []),
|
|
293
|
+
`all findings: ${fullCommand}`,
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The end of a live run's human output, after its `analysis:` line: the findings of the analysis
|
|
298
|
+
* the run just completed. Prints nothing for --json, or when the run's analysis did not complete,
|
|
299
|
+
* since that line already says why.
|
|
300
|
+
*/
|
|
301
|
+
export async function writeRunFindings(command, io, cwd, result) {
|
|
302
|
+
const outcome = result.automaticAnalysis;
|
|
303
|
+
const analysisId = outcome?.result?.analysisId;
|
|
304
|
+
if (wantsJson(command) ||
|
|
305
|
+
result.runId === undefined ||
|
|
306
|
+
analysisId === undefined ||
|
|
307
|
+
(outcome?.state !== "complete" && outcome?.state !== "partial"))
|
|
308
|
+
return;
|
|
309
|
+
try {
|
|
310
|
+
const view = await readRunFindings(cwd, result.runId, analysisId);
|
|
311
|
+
const full = cli(`review --run ${view.runId}${cwdFlag(cwd)}`);
|
|
312
|
+
io.writeOut(`${formatFindingsSummary(view, full).join("\n")}\n`);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// The run's own result is already printed; review reads the same record later.
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -15,19 +15,35 @@ export interface FirstRunEnvironment {
|
|
|
15
15
|
*/
|
|
16
16
|
hasDesktopSdk: boolean;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* The command that installs `@e2b/desktop` where this humanish resolves it, ahead of
|
|
19
|
+
* `npx humanish`: beside humanish in its project, or humanish and the peer together in this one
|
|
20
|
+
* when humanish runs from an npx cache, a global install or another directory. Undefined where
|
|
21
|
+
* `npm i` would prune a node_modules that no package.json declares.
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
desktopPeerCommand: string | undefined;
|
|
23
24
|
/** A provider API key for the model. */
|
|
24
25
|
hasProviderKey: boolean;
|
|
25
26
|
/** The coding agents already signed in locally: Codex, Claude Code, or both. */
|
|
26
27
|
localAgents: readonly SignedInAgent[];
|
|
27
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* init's quick read of this host for a local browser study: Linux x64 with `/dev/kvm`,
|
|
30
|
+
* `/dev/net/tun` and Docker,
|
|
31
|
+
* or an M3-or-newer Mac. Undefined when it was not read. `doctor --study local-browser` owns the
|
|
32
|
+
* full check.
|
|
33
|
+
*/
|
|
34
|
+
localBrowserHost?: LocalBrowserHost;
|
|
35
|
+
/** Host shape only, for the prerequisites a local step names. */
|
|
28
36
|
platform?: NodeJS.Platform;
|
|
29
|
-
|
|
37
|
+
/** How a suggested command invokes humanish; `npx humanish` when unset. */
|
|
38
|
+
humanish?: string;
|
|
30
39
|
}
|
|
40
|
+
/** Whether this host passed init's quick checks for a local browser study, and why not. */
|
|
41
|
+
export type LocalBrowserHost = {
|
|
42
|
+
ok: true;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
reason: string;
|
|
46
|
+
};
|
|
31
47
|
export type FirstRunActor = "openai-computer-use" | "local-agent";
|
|
32
48
|
/**
|
|
33
49
|
* Which brain the starter live study should be written for. A machine with a signed-in coding agent
|
|
@@ -40,7 +56,7 @@ export declare function starterActorFor(env: FirstRunEnvironment): FirstRunActor
|
|
|
40
56
|
* refuse to run on a machine where only Claude Code is signed in.
|
|
41
57
|
*/
|
|
42
58
|
export declare function starterLocalAgentFor(env: FirstRunEnvironment): LocalAgentId | undefined;
|
|
43
|
-
/**
|
|
59
|
+
/** Host shapes the local browser route is built for: Linux x64 and Apple Silicon Macs. */
|
|
44
60
|
export declare function supportsLocalBrowser(platform: NodeJS.Platform | undefined, arch: string | undefined): boolean;
|
|
45
61
|
export interface FirstRunStep {
|
|
46
62
|
/** The exact command to run. */
|
|
@@ -49,14 +65,18 @@ export interface FirstRunStep {
|
|
|
49
65
|
why: string;
|
|
50
66
|
}
|
|
51
67
|
/**
|
|
52
|
-
* The next
|
|
53
|
-
*
|
|
68
|
+
* The next two or three commands, in order: the dry run, the hosted try-live study when keys or a
|
|
69
|
+
* signed-in agent allow it, and the local browser study where this host passed its quick checks.
|
|
70
|
+
* Deliberately short: a list of twelve options is the same as no guidance, and the reader here has
|
|
71
|
+
* just been handed a directory of new files.
|
|
54
72
|
*/
|
|
55
73
|
export declare function firstRunSteps(env: FirstRunEnvironment): FirstRunStep[];
|
|
56
74
|
/** The block init prints after its changes. */
|
|
57
75
|
export declare function firstRunGuidance(env: FirstRunEnvironment): string[];
|
|
58
|
-
/**
|
|
76
|
+
/** Starts humanish's section of `AGENTS.md`, on its heading line. */
|
|
59
77
|
export declare const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
78
|
+
/** Ends humanish's section, so init can replace the section and nothing after it. */
|
|
79
|
+
export declare const AGENTS_SECTION_END_MARKER = "<!-- /humanish:agents-guide -->";
|
|
60
80
|
/**
|
|
61
81
|
* What the next coding agent needs to know about humanish in this project.
|
|
62
82
|
*
|
|
@@ -64,6 +84,7 @@ export declare const AGENTS_SECTION_MARKER = "<!-- humanish:agents-guide -->";
|
|
|
64
84
|
* read on arrival. Increasingly the thing that ran `humanish init` was itself an agent working for
|
|
65
85
|
* someone, and the agent that shows up tomorrow finds a `humanish/` directory with no idea what it
|
|
66
86
|
* is for. Deliberately short and command-first, because an agent acts on the commands it is given.
|
|
87
|
+
* Commands use `humanish`, the prefix init chose for where humanish is installed.
|
|
67
88
|
*/
|
|
68
|
-
export declare function agentsSection(): string;
|
|
89
|
+
export declare function agentsSection(humanish?: string): string;
|
|
69
90
|
export {};
|