humanish 0.110.0 → 0.110.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +14 -11
- package/CHANGELOG.md +13 -0
- package/CONTEXT.md +8 -6
- package/README.md +96 -47
- 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 +11 -0
- package/dist/actors/local-agent/cli.js +103 -24
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +18 -11
- package/dist/cli/commands/analyze.js +21 -5
- package/dist/cli/commands/run-signals.d.ts +2 -0
- package/dist/cli/commands/run-signals.js +96 -44
- package/dist/cli/commands/runs.d.ts +5 -0
- package/dist/cli/commands/runs.js +86 -70
- package/dist/cli/commands/study-route-computer-use.js +2 -0
- package/dist/cli/commands/study-route-scripted.js +2 -0
- package/dist/cli/commands/study-route-shared-world.js +2 -0
- package/dist/cli/commands/study-route-terminal.js +2 -0
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.js +42 -4
- package/dist/cli/findings.d.ts +103 -0
- package/dist/cli/findings.js +316 -0
- package/dist/cli/first-run-path.d.ts +5 -4
- package/dist/cli/first-run-path.js +14 -7
- package/dist/cli/io.js +2 -1
- package/dist/cli/product-sentence.d.ts +3 -3
- package/dist/cli/product-sentence.js +3 -3
- package/dist/keys/env-file.d.ts +4 -0
- package/dist/keys/env-file.js +17 -0
- package/dist/observer/data.d.ts +14 -1
- package/dist/observer/data.js +19 -9
- package/dist/observer/library.d.ts +4 -0
- package/dist/observer/library.js +18 -15
- package/dist/observer/render.js +8 -2
- package/dist/observer/run-routes.d.ts +2 -0
- package/dist/observer/run-routes.js +43 -34
- 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 +4 -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 +1 -1
- package/dist/routes/shared-world/result.js +22 -22
- package/dist/routes/terminal/bundle.js +4 -2
- 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 +4 -4
- package/dist/routes/terminal/result.d.ts +6 -7
- package/dist/routes/terminal/result.js +6 -22
- 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 +11 -7
- package/dist/run/judge.d.ts +3 -2
- package/dist/run/judge.js +10 -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 +18 -17
- package/dist/run/reclaim.js +180 -113
- 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.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 +138 -0
- package/dist/run/sandbox-receipts.d.ts +14 -0
- package/dist/run/sandbox-receipts.js +44 -0
- 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/doctor.d.ts +1 -1
- package/dist/study/doctor.js +5 -2
- package/dist/study/init.js +14 -3
- package/dist/study/keys.d.ts +12 -0
- package/dist/study/keys.js +32 -0
- package/dist/study/persona.d.ts +4 -0
- package/dist/study/persona.js +4 -2
- 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 +128 -35
- package/dist/substrates/e2b/sdk.d.ts +33 -0
- package/dist/substrates/e2b/sdk.js +21 -21
- package/dist/substrates/shell.d.ts +2 -0
- package/dist/substrates/shell.js +4 -0
- package/dist/tui-app.js +133 -133
- package/dist/verify/liveness.d.ts +19 -3
- package/dist/verify/liveness.js +46 -25
- package/dist/verify/verify.d.ts +3 -0
- package/dist/verify/verify.js +6 -2
- 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 +1 -1
- package/docs/contracts/run-bundle.md +75 -6
- package/docs/contracts/schemas.md +39 -22
- 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 +4 -3
- package/skills/humanish/SKILL.md +16 -2
package/dist/run/status.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ export type RunInterruptSignal = "SIGINT" | "SIGTERM" | "SIGHUP";
|
|
|
20
20
|
interface RunStatusOutcome {
|
|
21
21
|
/** `review.verdict` verbatim. */
|
|
22
22
|
verdict?: string;
|
|
23
|
-
/**
|
|
23
|
+
/** run.json's `outcome.ok`: the route result's ok. */
|
|
24
24
|
ok?: boolean;
|
|
25
|
-
/**
|
|
25
|
+
/** run.json's `outcome.execution`: whether the run worked as an execution, apart from its verdict. */
|
|
26
26
|
execution?: ExecutionOutcome;
|
|
27
27
|
/** `review.participants` counts, when the run recorded any. */
|
|
28
28
|
participants?: {
|
|
@@ -72,14 +72,11 @@ export interface RunStatusHandle {
|
|
|
72
72
|
/** Finalize: state `finished`, `completedAt`, and the derived outcome. Stops the cadence.
|
|
73
73
|
* Idempotent: a second call is a no-op, so a route with several exit paths is safe. */
|
|
74
74
|
finish(outcome?: RunStatusOutcome): Promise<void>;
|
|
75
|
-
/** After finish,
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
ok: boolean;
|
|
79
|
-
execution: ExecutionOutcome;
|
|
80
|
-
}): Promise<void>;
|
|
75
|
+
/** After finish, replace the finished record's outcome with one read from a rewritten run.json,
|
|
76
|
+
* so the two agree. Before finish, and after an interrupt, it does nothing. */
|
|
77
|
+
restate(outcome: RunStatusOutcome): Promise<void>;
|
|
81
78
|
/** The process is being stopped by `signal`: state `interrupted`, `completedAt` and the signal.
|
|
82
|
-
* It stops the cadence and makes a later finish or
|
|
79
|
+
* It stops the cadence and makes a later finish or restate a no-op, so the route cannot write
|
|
83
80
|
* `running` or `finished` over it. When the run had finished it writes nothing, waits for the
|
|
84
81
|
* writes already queued and resolves false. */
|
|
85
82
|
interrupt(signal: RunInterruptSignal): Promise<boolean>;
|
package/dist/run/status.js
CHANGED
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
//
|
|
5
5
|
// run.json remains the evidence of record. This file is a derived index and liveness record:
|
|
6
6
|
// `verify` never gates on it, nothing here is a claim about what a participant did, and when the
|
|
7
|
-
// two disagree run.json wins
|
|
7
|
+
// two disagree run.json wins. A finished record copies run.json's outcome, so it can be rebuilt
|
|
8
|
+
// from it; a record written before run.json carried an outcome holds the only copy of the run's ok.
|
|
8
9
|
//
|
|
9
10
|
// Public safety: it holds only the run id, the study id/path/origin (the strings `humanish study list`
|
|
10
11
|
// prints), the mode, a local pid and timestamps. It holds no hostname or user/path identity: an
|
|
11
12
|
// operator may share the run directory, so a share-safety gate must have nothing to strip here.
|
|
12
13
|
import { isProvenanceField, studyFields } from "./study-provenance.js";
|
|
13
14
|
import { writeContainedOutputFile } from "./contained-output.js";
|
|
14
|
-
import { redactText } from "../evidence/redaction.js";
|
|
15
|
-
import { readRunSandboxIds, scrubSandboxIds } from "./sandbox-ids.js";
|
|
16
15
|
export const RUN_STATUS_SCHEMA = "humanish.run-status.v1";
|
|
17
16
|
/** The file, relative to the run directory. */
|
|
18
17
|
export const RUN_STATUS_FILE = "status.json";
|
|
@@ -46,6 +45,9 @@ export function runStatusOutcome(bundle) {
|
|
|
46
45
|
...(typeof bundle.cost?.fullyEstimated === "boolean"
|
|
47
46
|
? { estimatedCostComplete: bundle.cost.fullyEstimated }
|
|
48
47
|
: {}),
|
|
48
|
+
...(bundle.outcome?.state === "finished"
|
|
49
|
+
? { ok: bundle.outcome.ok, execution: bundle.outcome.execution }
|
|
50
|
+
: {}),
|
|
49
51
|
};
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
@@ -103,28 +105,10 @@ export function beginRunStatus(runPaths, options) {
|
|
|
103
105
|
};
|
|
104
106
|
await write(finishedRecord);
|
|
105
107
|
},
|
|
106
|
-
async
|
|
108
|
+
async restate(outcome) {
|
|
107
109
|
if (finishedRecord === undefined)
|
|
108
110
|
return;
|
|
109
|
-
|
|
110
|
-
// again even though the routes scrubbed it. A route settles with its own result, so an SDK
|
|
111
|
-
// error here can still name a sandbox; the receipts give the ids to replace.
|
|
112
|
-
const ids = await readRunSandboxIds(runPaths);
|
|
113
|
-
const redacted = (failure) => ({
|
|
114
|
-
kind: failure.kind,
|
|
115
|
-
message: scrubSandboxIds(redactText(failure.message), ids),
|
|
116
|
-
});
|
|
117
|
-
const execution = {
|
|
118
|
-
succeeded: result.execution.succeeded,
|
|
119
|
-
failures: result.execution.failures.map(redacted),
|
|
120
|
-
...(result.execution.warnings === undefined
|
|
121
|
-
? {}
|
|
122
|
-
: { warnings: result.execution.warnings.map(redacted) }),
|
|
123
|
-
};
|
|
124
|
-
finishedRecord = {
|
|
125
|
-
...finishedRecord,
|
|
126
|
-
outcome: { ...finishedRecord.outcome, ok: result.ok, execution },
|
|
127
|
-
};
|
|
111
|
+
finishedRecord = { ...finishedRecord, outcome };
|
|
128
112
|
await write(finishedRecord);
|
|
129
113
|
},
|
|
130
114
|
async interrupt(signal) {
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type
|
|
3
|
-
import { type CleanupAdapterResult, type CleanupResult } from "./results.js";
|
|
1
|
+
import { type ReviewSummary } from "./bundle.js";
|
|
2
|
+
import { type RunDisplay } from "./display.js";
|
|
4
3
|
import { type VerifyResult } from "../verify/verify.js";
|
|
5
4
|
declare const RUNS_SCHEMA = "humanish.runs-result.v1";
|
|
6
|
-
export interface RunCleanupHooks {
|
|
7
|
-
/** @deprecated Ignored. Stored provider ids are not authority to load or mutate a provider. */
|
|
8
|
-
loadDesktopModule?: () => Promise<E2BDesktopModule>;
|
|
9
|
-
cleanupAdapterResources?: (ctx: {
|
|
10
|
-
cwd: string;
|
|
11
|
-
runDir: string;
|
|
12
|
-
bundle: RunBundle;
|
|
13
|
-
}) => Promise<CleanupAdapterResult[]>;
|
|
14
|
-
now?: () => Date;
|
|
15
|
-
}
|
|
16
5
|
export interface RunsResult {
|
|
17
6
|
schema: typeof RUNS_SCHEMA;
|
|
18
7
|
ok: boolean;
|
|
@@ -22,6 +11,8 @@ export interface RunsResult {
|
|
|
22
11
|
createdAt: string | null;
|
|
23
12
|
mode: string | null;
|
|
24
13
|
path: string;
|
|
14
|
+
/** How the run reads (runDisplay in src/run/display.ts); absent when it has no readable run.json. */
|
|
15
|
+
display?: RunDisplay;
|
|
25
16
|
}>;
|
|
26
17
|
latest: string | null;
|
|
27
18
|
error?: {
|
|
@@ -29,10 +20,10 @@ export interface RunsResult {
|
|
|
29
20
|
message: string;
|
|
30
21
|
};
|
|
31
22
|
}
|
|
32
|
-
export declare function cleanupRun(cwdInput: string, runInput: string, hooks?: RunCleanupHooks): Promise<CleanupResult>;
|
|
33
23
|
export declare function listRuns(cwdInput: string): Promise<RunsResult>;
|
|
34
24
|
export declare function readReview(cwdInput: string, runInput: string): Promise<VerifyResult | (ReviewSummary & {
|
|
35
25
|
path: string;
|
|
36
26
|
runId: string;
|
|
27
|
+
display?: RunDisplay;
|
|
37
28
|
})>;
|
|
38
29
|
export {};
|
package/dist/run/stored-runs.js
CHANGED
|
@@ -1,157 +1,17 @@
|
|
|
1
|
-
import { REDACTED_SANDBOX_ID, sandboxIdDigest } from "../evidence/redaction.js";
|
|
2
1
|
import { lstat, readdir } from "node:fs/promises";
|
|
3
|
-
import { runNotFoundMessage } from "./run-not-found.js";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import { redactText } from "../evidence/redaction.js";
|
|
6
|
-
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot,
|
|
7
|
-
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory,
|
|
8
|
-
import { RUN_BUNDLE_FILE
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { bindExistingRunArtifactPaths, RUNS_RELATIVE_ROOT, isSafeRunIdSegment, resolveRunsRoot, } from "./paths.js";
|
|
5
|
+
import { assertPreparedSelectedOutputDirectory, bindExistingManagedHumanishOutputDirectory, } from "./contained-output.js";
|
|
6
|
+
import { RUN_BUNDLE_FILE } from "./bundle.js";
|
|
7
|
+
import { isReviewSummary, isRunOutcome } from "./bundle-shape.js";
|
|
8
|
+
import { bundleDisplayFacts, runDisplay } from "./display.js";
|
|
9
|
+
import { RUN_STATUS_FILE } from "./status.js";
|
|
11
10
|
import { readLatest, readRunJsonIfExists, resolveRunPath } from "./locate.js";
|
|
12
11
|
import { withCuaReviewProvenance } from "./outcomes.js";
|
|
13
12
|
import { isNodeError, isRecord } from "./type-guards.js";
|
|
14
13
|
import { invalidRunStorageVerifyResult, verifyResolvedRun, } from "../verify/verify.js";
|
|
15
14
|
const RUNS_SCHEMA = "humanish.runs-result.v1";
|
|
16
|
-
/** A cleanup result that refused before inspecting any resource. */
|
|
17
|
-
function cleanupRefusal(cwd, runInput, checkedAt, error, bundlePath) {
|
|
18
|
-
return {
|
|
19
|
-
schema: CLEANUP_SCHEMA,
|
|
20
|
-
ok: false,
|
|
21
|
-
cwd,
|
|
22
|
-
run: runInput,
|
|
23
|
-
...(bundlePath === undefined ? {} : { bundlePath }),
|
|
24
|
-
checkedAt,
|
|
25
|
-
summary: { resources: 0, killed: 0, alreadyClean: 0, failed: 0, skipped: 0 },
|
|
26
|
-
resources: [],
|
|
27
|
-
adapterResults: [],
|
|
28
|
-
warnings: [],
|
|
29
|
-
error,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** What the recorded evidence says about one provider resource; nothing is killed from here. */
|
|
33
|
-
function recordedResourceResult(resource) {
|
|
34
|
-
// Named by digest: a run recorded before 0.110 holds the raw id here, which is digested.
|
|
35
|
-
const raw = resource.id === REDACTED_SANDBOX_ID ? undefined : resource.id;
|
|
36
|
-
const idDigest = resource.idDigest ?? (raw === undefined ? undefined : sandboxIdDigest(raw));
|
|
37
|
-
const base = {
|
|
38
|
-
provider: resource.provider,
|
|
39
|
-
kind: resource.kind,
|
|
40
|
-
id: REDACTED_SANDBOX_ID,
|
|
41
|
-
...(idDigest === undefined ? {} : { idDigest }),
|
|
42
|
-
};
|
|
43
|
-
if (resource.provider !== "e2b-desktop" || resource.kind !== "sandbox") {
|
|
44
|
-
return {
|
|
45
|
-
...base,
|
|
46
|
-
status: "skipped",
|
|
47
|
-
message: "cleanup only supports e2b-desktop sandbox resources",
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
if (resource.status === "killed" || resource.cleanup?.killed === true) {
|
|
51
|
-
return {
|
|
52
|
-
...base,
|
|
53
|
-
status: "already_clean",
|
|
54
|
-
message: "resource was already recorded as killed",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
...base,
|
|
59
|
-
status: "failed",
|
|
60
|
-
message: "automatic provider cleanup requires a verified resource lease",
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
export async function cleanupRun(cwdInput, runInput, hooks = {}) {
|
|
64
|
-
const cwd = path.resolve(cwdInput);
|
|
65
|
-
const checkedAt = (hooks.now ?? (() => new Date()))().toISOString();
|
|
66
|
-
let resolved;
|
|
67
|
-
try {
|
|
68
|
-
resolved = await resolveRunPath(cwd, runInput);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
72
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
73
|
-
message: "Run storage failed containment validation.",
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (!resolved) {
|
|
77
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
78
|
-
code: "HUMANISH_RUN_NOT_FOUND",
|
|
79
|
-
message: await runNotFoundMessage(cwd, runInput),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
const runPaths = resolved;
|
|
83
|
-
const bundlePath = path.join(runPaths.absoluteRunRoot, RUN_BUNDLE_FILE);
|
|
84
|
-
const cleanupPath = path.join(runPaths.absoluteRunRoot, "cleanup.json");
|
|
85
|
-
await prepareContainedOutputFile(runPaths, "cleanup.json");
|
|
86
|
-
const bundleBytes = await readContainedRegularFile(runPaths, RUN_BUNDLE_FILE);
|
|
87
|
-
let bundle = null;
|
|
88
|
-
if (bundleBytes) {
|
|
89
|
-
try {
|
|
90
|
-
bundle = JSON.parse(bundleBytes.toString("utf8"));
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
bundle = null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (!isRunBundle(bundle)) {
|
|
97
|
-
return cleanupRefusal(cwd, runInput, checkedAt, {
|
|
98
|
-
code: "HUMANISH_INVALID_RUN_BUNDLE",
|
|
99
|
-
message: "Run bundle failed cleanup shape validation.",
|
|
100
|
-
}, path.relative(cwd, bundlePath));
|
|
101
|
-
}
|
|
102
|
-
const warnings = [];
|
|
103
|
-
const providerResources = bundle.providerResources ?? [];
|
|
104
|
-
const resources = providerResources.map(recordedResourceResult);
|
|
105
|
-
let adapterResults = [];
|
|
106
|
-
if (hooks.cleanupAdapterResources) {
|
|
107
|
-
try {
|
|
108
|
-
adapterResults = await hooks.cleanupAdapterResources({
|
|
109
|
-
cwd,
|
|
110
|
-
runDir: runPaths.physicalRunRoot,
|
|
111
|
-
bundle,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
adapterResults = [
|
|
116
|
-
{
|
|
117
|
-
id: "adapter-cleanup",
|
|
118
|
-
ok: false,
|
|
119
|
-
message: error instanceof Error ? error.message : String(error),
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
}
|
|
123
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
124
|
-
}
|
|
125
|
-
if (providerResources.length === 0 && adapterResults.length === 0) {
|
|
126
|
-
warnings.push("Run bundle recorded no provider resource evidence; nothing to inspect.");
|
|
127
|
-
}
|
|
128
|
-
const summary = {
|
|
129
|
-
resources: resources.length,
|
|
130
|
-
killed: 0,
|
|
131
|
-
alreadyClean: resources.filter((resource) => resource.status === "already_clean").length,
|
|
132
|
-
failed: resources.filter((resource) => resource.status === "failed").length +
|
|
133
|
-
adapterResults.filter((result) => !result.ok).length,
|
|
134
|
-
skipped: resources.filter((resource) => resource.status === "skipped").length,
|
|
135
|
-
};
|
|
136
|
-
const ok = summary.failed === 0;
|
|
137
|
-
const result = {
|
|
138
|
-
schema: CLEANUP_SCHEMA,
|
|
139
|
-
ok,
|
|
140
|
-
cwd: PUBLIC_TARGET_CWD,
|
|
141
|
-
run: runInput,
|
|
142
|
-
runId: bundle.runId,
|
|
143
|
-
bundlePath: path.relative(cwd, bundlePath),
|
|
144
|
-
cleanupPath: path.relative(cwd, cleanupPath),
|
|
145
|
-
checkedAt,
|
|
146
|
-
summary,
|
|
147
|
-
resources,
|
|
148
|
-
adapterResults,
|
|
149
|
-
warnings,
|
|
150
|
-
};
|
|
151
|
-
await validatePreparedRunArtifactPaths(runPaths);
|
|
152
|
-
await writeContainedOutputFile(runPaths, "cleanup.json", `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
15
|
export async function listRuns(cwdInput) {
|
|
156
16
|
const cwd = path.resolve(cwdInput);
|
|
157
17
|
const runsRootPath = resolveRunsRoot(cwd);
|
|
@@ -216,6 +76,7 @@ export async function listRuns(cwdInput) {
|
|
|
216
76
|
createdAt: isRecord(bundle) && typeof bundle.createdAt === "string" ? bundle.createdAt : null,
|
|
217
77
|
mode: isRecord(bundle) && typeof bundle.mode === "string" ? bundle.mode : null,
|
|
218
78
|
path: path.join(RUNS_RELATIVE_ROOT, entryName),
|
|
79
|
+
...(await listedRunDisplay(entryRunPaths, bundle)),
|
|
219
80
|
});
|
|
220
81
|
}
|
|
221
82
|
if (runsRoot) {
|
|
@@ -234,6 +95,29 @@ export async function listRuns(cwdInput) {
|
|
|
234
95
|
latest: latest?.runId ?? null,
|
|
235
96
|
};
|
|
236
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A listed run's display, from its run.json and, for a bundle without an outcome, its status
|
|
100
|
+
* record. Like the listing's other fields it reads the file without verifying it; a run.json
|
|
101
|
+
* that is not an object naming a run gets none.
|
|
102
|
+
*/
|
|
103
|
+
async function listedRunDisplay(runPaths, bundle) {
|
|
104
|
+
if (!isRecord(bundle) || typeof bundle.runId !== "string")
|
|
105
|
+
return {};
|
|
106
|
+
const outcome = isRunOutcome(bundle.outcome) ? bundle.outcome : undefined;
|
|
107
|
+
const status = outcome === undefined ? await readRunJsonIfExists(runPaths, RUN_STATUS_FILE) : undefined;
|
|
108
|
+
const facts = bundleDisplayFacts({
|
|
109
|
+
runId: bundle.runId,
|
|
110
|
+
...(typeof bundle.mode === "string" ? { mode: bundle.mode } : {}),
|
|
111
|
+
...(isRecord(bundle.review) && typeof bundle.review.verdict === "string"
|
|
112
|
+
? { review: { verdict: bundle.review.verdict } }
|
|
113
|
+
: {}),
|
|
114
|
+
...(Array.isArray(bundle.simulations)
|
|
115
|
+
? { simulations: bundle.simulations.filter(isRecord) }
|
|
116
|
+
: {}),
|
|
117
|
+
...(outcome === undefined ? {} : { outcome }),
|
|
118
|
+
}, status);
|
|
119
|
+
return { display: runDisplay(facts) };
|
|
120
|
+
}
|
|
237
121
|
function runsUnavailableResult(cwd, error) {
|
|
238
122
|
return {
|
|
239
123
|
schema: RUNS_SCHEMA,
|
|
@@ -279,5 +163,6 @@ export async function readReview(cwdInput, runInput) {
|
|
|
279
163
|
...projected,
|
|
280
164
|
path: path.relative(cwd, path.join(runPaths.absoluteRunRoot, "review.json")),
|
|
281
165
|
runId: path.basename(runPaths.absoluteRunRoot),
|
|
166
|
+
...(runPaths ? await listedRunDisplay(runPaths, bundle) : {}),
|
|
282
167
|
};
|
|
283
168
|
}
|
package/dist/run/streams.d.ts
CHANGED
|
@@ -171,8 +171,9 @@ export interface RunStream {
|
|
|
171
171
|
/**
|
|
172
172
|
* The judge's status for this participant (`judgedStatus` in src/run/judge.ts). It differs from
|
|
173
173
|
* the trace status when a goal_satisfied session reported a blocker or never engaged. Computer-use
|
|
174
|
-
*
|
|
175
|
-
* and
|
|
174
|
+
* and shared-world runs write it for a participant whose session finished; rerun selection and
|
|
175
|
+
* the Observer read it. Absent on the terminal and scripted routes, whose judge has no blocker
|
|
176
|
+
* rule, and in bundles written before it existed.
|
|
176
177
|
*/
|
|
177
178
|
judgedStatus?: ActorStatus;
|
|
178
179
|
transport: "snapshot" | "polling" | "sse" | "pty" | "app-server";
|
package/dist/study/doctor.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type LocalRuntimeStatus } from "../substrates/local/runtime.js";
|
|
2
2
|
import type { StudyConfig } from "./types.js";
|
|
3
3
|
import type { PlanResult } from "./plan-types.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type DetectedLocalAgent } from "../actors/local-agent/cli.js";
|
|
5
5
|
import type { ReasoningEffort } from "../actors/reasoning-effort.js";
|
|
6
6
|
import type { DoctorCheckDraft } from "../cli/doctor.js";
|
|
7
7
|
import { type CodexInstallation } from "../actors/codex/codex-admission.js";
|
package/dist/study/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { isLocalBrowserStudy } from "../substrates/local/runtime-config.js";
|
|
3
3
|
import { localRuntimeStatus } from "../substrates/local/runtime.js";
|
|
4
4
|
import { keyNamesOf, requiredKeys, requiredSubjectEnv } from "./requirements.js";
|
|
5
|
+
import { localAgentVersionRefusal } from "../actors/local-agent/cli.js";
|
|
5
6
|
import { protocolAdditionsWarning, protocolIncompatibilityMessage, } from "../actors/codex/protocol-compat.js";
|
|
6
7
|
import { automaticAnalysisBudget } from "../analysis/automatic-config.js";
|
|
7
8
|
import { externalCatchHealthy } from "../comms/sandbox-catch.js";
|
|
@@ -254,12 +255,14 @@ async function participantChecks(config, route, keys, local, args, checkAccount)
|
|
|
254
255
|
// that the operator's config, release and model admit a launch.
|
|
255
256
|
if (choice === "codex" && agent?.authStatus === "authenticated")
|
|
256
257
|
return [await hostedCodexParticipantCheck(args, config.actors[0])];
|
|
258
|
+
const outdated = agent === undefined ? undefined : localAgentVersionRefusal(agent);
|
|
257
259
|
return [
|
|
258
260
|
{
|
|
259
261
|
name: "local participant authentication",
|
|
260
|
-
ok: agent?.authStatus === "authenticated",
|
|
262
|
+
ok: agent?.authStatus === "authenticated" && outdated === undefined,
|
|
261
263
|
message: agent?.authStatus === "authenticated"
|
|
262
|
-
?
|
|
264
|
+
? (outdated ??
|
|
265
|
+
`${agent.label}${agent.version === undefined ? "" : ` ${agent.version}`} reports authenticated on the host. E2B supplies the desktop; no OpenAI API key is required for this participant.`)
|
|
263
266
|
: agent
|
|
264
267
|
? `${agent.label} ${agent.authStatus === "unauthenticated" ? "reports not signed in" : "authentication could not be checked"}. Run \`${choice === "codex" ? "codex login status" : "claude auth status"}\`; sign in or update the CLI before running.`
|
|
265
268
|
: `${choice} is not on this process's PATH. Install and sign in to that CLI, or choose openai-computer-use with OPENAI_API_KEY.`,
|
package/dist/study/init.js
CHANGED
|
@@ -5,6 +5,7 @@ import { detectLocalAgents } from "../actors/local-agent/cli.js";
|
|
|
5
5
|
import { probeKeySources } from "../keys/key-resolution.js";
|
|
6
6
|
import { DEFAULT_LOCAL_BROWSER_STARTER, runtimeDirectories, starterFilesFor, } from "./init-templates.js";
|
|
7
7
|
import { assertPreparedSelectedOutputDirectory, prepareContainedOutputDirectory, prepareSelectedOutputDirectory, writeContainedOutputFile, } from "../run/contained-output.js";
|
|
8
|
+
import { desktopPeerAdvice, humanishInstall, } from "../substrates/e2b/peer-install.js";
|
|
8
9
|
import { validateCwd } from "../run/project.js";
|
|
9
10
|
import { pathExists, readTextIfExists, validateInitProjectPaths } from "./init-paths.js";
|
|
10
11
|
import { planGitignore, planPackageJson } from "./init-plan.js";
|
|
@@ -270,6 +271,18 @@ function validateLocalBrowserStarter(input) {
|
|
|
270
271
|
return { ok: false, message: "--local-mission must be 4,000 characters or fewer." };
|
|
271
272
|
return { ok: true, value };
|
|
272
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* The first-run hint's peer install, ahead of `npx humanish`: beside humanish in its project, else
|
|
276
|
+
* humanish and the peer together here, where `npx humanish` then finds them. None where `npm i`
|
|
277
|
+
* would prune a node_modules that no package.json declares.
|
|
278
|
+
*/
|
|
279
|
+
function initDesktopPeerCommand(install) {
|
|
280
|
+
if (install.kind === "project")
|
|
281
|
+
return desktopPeerAdvice(install).command;
|
|
282
|
+
if ((install.kind === "one-shot" || install.kind === "other") && install.unmanagedCwd)
|
|
283
|
+
return undefined;
|
|
284
|
+
return "npm i -D humanish @e2b/desktop";
|
|
285
|
+
}
|
|
273
286
|
/**
|
|
274
287
|
* What this machine can run, for the starter study and the next steps. Local CLI status is
|
|
275
288
|
* classified without returning its output or reading its credential file. Provider keys go
|
|
@@ -297,10 +310,8 @@ async function firstRunEnvironment(env, cwd) {
|
|
|
297
310
|
catch {
|
|
298
311
|
hasDesktopSdk = false;
|
|
299
312
|
}
|
|
300
|
-
const { fileURLToPath } = await import("node:url");
|
|
301
|
-
const here = fileURLToPath(import.meta.url);
|
|
302
313
|
return {
|
|
303
|
-
|
|
314
|
+
desktopPeerCommand: initDesktopPeerCommand(humanishInstall()),
|
|
304
315
|
hasDesktopSdk,
|
|
305
316
|
hasE2bKey: present("E2B_API_KEY"),
|
|
306
317
|
hasProviderKey: present("OPENAI_API_KEY"),
|
package/dist/study/keys.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
3
|
+
* humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
|
|
4
|
+
* is a string, and `review.analysis.question`, whose keys resolveAutomaticAnalysis checks.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isStudyKeyPath(path: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The dotted path of every key a humanish.study.v3 file can set, down to `depth` levels:
|
|
9
|
+
* `route`, `caps.maxUsd`, `participants.persona`. scripts/check-doc-study-fields.ts holds the
|
|
10
|
+
* study file reference to these paths.
|
|
11
|
+
*/
|
|
12
|
+
export declare function studyKeyPaths(depth: number): string[];
|
|
1
13
|
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
2
14
|
export declare function findUnknownV2Key(raw: unknown): string | undefined;
|
|
3
15
|
/** The first key in a humanish.study.v3 document that the format does not have; undefined if none. */
|
package/dist/study/keys.js
CHANGED
|
@@ -230,6 +230,38 @@ const STUDY_KEYS = {
|
|
|
230
230
|
defaults: V2_KEYS.defaults,
|
|
231
231
|
comms: V2_KEYS.comms,
|
|
232
232
|
};
|
|
233
|
+
/**
|
|
234
|
+
* Whether a dotted key path, such as `caps.maxUsd` or `participants[0].persona`, names a key a
|
|
235
|
+
* humanish.study.v3 file can set. A path below a leaf is not one: `scenario.ref`, where `scenario`
|
|
236
|
+
* is a string, and `review.analysis.question`, whose keys resolveAutomaticAnalysis checks.
|
|
237
|
+
*/
|
|
238
|
+
export function isStudyKeyPath(path) {
|
|
239
|
+
let shape = STUDY_KEYS;
|
|
240
|
+
for (const segment of path.replaceAll(/\[\d*\]/g, "").split(".")) {
|
|
241
|
+
if (shape === true || !Object.hasOwn(shape, segment))
|
|
242
|
+
return false;
|
|
243
|
+
shape = shape[segment];
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The dotted path of every key a humanish.study.v3 file can set, down to `depth` levels:
|
|
249
|
+
* `route`, `caps.maxUsd`, `participants.persona`. scripts/check-doc-study-fields.ts holds the
|
|
250
|
+
* study file reference to these paths.
|
|
251
|
+
*/
|
|
252
|
+
export function studyKeyPaths(depth) {
|
|
253
|
+
const paths = [];
|
|
254
|
+
const visit = (shape, prefix, level) => {
|
|
255
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
256
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
257
|
+
paths.push(path);
|
|
258
|
+
if (child !== true && level < depth)
|
|
259
|
+
visit(child, path, level + 1);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
visit(STUDY_KEYS, "", 1);
|
|
263
|
+
return paths;
|
|
264
|
+
}
|
|
233
265
|
/** The first key in `raw` that V2_KEYS does not list, as an error message; undefined if none. */
|
|
234
266
|
export function findUnknownV2Key(raw) {
|
|
235
267
|
return walk(raw, V2_KEYS, "", "study");
|
package/dist/study/persona.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export interface PersonaDirectives {
|
|
|
26
26
|
/** Directives included in the prompt, not proof of behavioral adherence. */
|
|
27
27
|
traitsApplied: string[];
|
|
28
28
|
}
|
|
29
|
+
/** The fields a persona file can set; any other warns and is not sent. The study file reference lists each. */
|
|
30
|
+
export declare const PERSONA_FIELDS: Set<string>;
|
|
31
|
+
/** The traits under `traits:` that become directives; any other warns and is not sent. */
|
|
32
|
+
export declare const TRAIT_FIELDS: Set<string>;
|
|
29
33
|
/** Legacy short fields keep their bounds, but every discarded value is diagnosed. */
|
|
30
34
|
export declare function parseResolvedPersona(raw: unknown, fallback: {
|
|
31
35
|
id: string;
|
package/dist/study/persona.js
CHANGED
|
@@ -9,7 +9,8 @@ export class PersonaConfigError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
const LEVELS = ["low", "medium", "high"];
|
|
12
|
-
|
|
12
|
+
/** The fields a persona file can set; any other warns and is not sent. The study file reference lists each. */
|
|
13
|
+
export const PERSONA_FIELDS = new Set([
|
|
13
14
|
"schema",
|
|
14
15
|
"id",
|
|
15
16
|
"name",
|
|
@@ -18,7 +19,8 @@ const PERSONA_FIELDS = new Set([
|
|
|
18
19
|
"traits",
|
|
19
20
|
"constraints",
|
|
20
21
|
]);
|
|
21
|
-
|
|
22
|
+
/** The traits under `traits:` that become directives; any other warns and is not sent. */
|
|
23
|
+
export const TRAIT_FIELDS = new Set(["patience", "technical_confidence", "accessibility_needs"]);
|
|
22
24
|
function cleanText(value) {
|
|
23
25
|
return value
|
|
24
26
|
.replace(/\r\n?/g, "\n")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
type Manager = "npm" | "pnpm";
|
|
2
|
+
/** Where the running humanish is installed, read from its own file's real path. */
|
|
3
|
+
export type HumanishInstall =
|
|
4
|
+
/** The nearest package.json declaring humanish: the working directory's, or `up` levels above. */
|
|
5
|
+
{
|
|
6
|
+
kind: "project";
|
|
7
|
+
up: number;
|
|
8
|
+
manager: Manager;
|
|
9
|
+
}
|
|
10
|
+
/** A one-shot runner's cache: `npx humanish` or `pnpm dlx humanish`. */
|
|
11
|
+
| {
|
|
12
|
+
kind: "one-shot";
|
|
13
|
+
unmanagedCwd: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** The global directory npm installs to from here, or pnpm's global directory. */
|
|
16
|
+
| {
|
|
17
|
+
kind: "global";
|
|
18
|
+
manager: Manager;
|
|
19
|
+
}
|
|
20
|
+
/** A source checkout of humanish, run with node or tsx. */
|
|
21
|
+
| {
|
|
22
|
+
kind: "checkout";
|
|
23
|
+
}
|
|
24
|
+
/** Anywhere else, such as another project or a global prefix npm is not configured for here. */
|
|
25
|
+
| {
|
|
26
|
+
kind: "other";
|
|
27
|
+
unmanagedCwd: boolean;
|
|
28
|
+
};
|
|
29
|
+
export interface InstallContext {
|
|
30
|
+
cwd: string;
|
|
31
|
+
/** The directory npm's global installs go to from this environment. */
|
|
32
|
+
npmGlobalRoot: string;
|
|
33
|
+
/** Whether the package.json in `directory` declares humanish as a dependency. */
|
|
34
|
+
declaresHumanish: (directory: string) => boolean;
|
|
35
|
+
/** Whether `npm i` from `directory` installs into a node_modules no package.json declares. */
|
|
36
|
+
unmanaged: (directory: string) => boolean;
|
|
37
|
+
}
|
|
38
|
+
/** The install `modulePath` belongs to. */
|
|
39
|
+
export declare function classifyHumanishInstall(modulePath: string, context: InstallContext): HumanishInstall;
|
|
40
|
+
/** The running humanish's install, from this file's real path. */
|
|
41
|
+
export declare function humanishInstall(cwd?: string): HumanishInstall;
|
|
42
|
+
/**
|
|
43
|
+
* Where this humanish is installed, the command that puts `spec` (`@e2b/desktop`, or a version of
|
|
44
|
+
* it) where it will resolve, and that command as one or two sentences of advice.
|
|
45
|
+
*/
|
|
46
|
+
export declare function desktopPeerAdvice(install: HumanishInstall, spec?: string): {
|
|
47
|
+
where: string;
|
|
48
|
+
command: string;
|
|
49
|
+
advice: string;
|
|
50
|
+
};
|
|
51
|
+
export {};
|