humanish 0.110.1 → 0.110.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +11 -16
- package/CHANGELOG.md +26 -0
- package/CONTEXT.md +1 -1
- package/README.md +8 -7
- package/dist/actors/computer-use/actor.js +2 -2
- package/dist/actors/computer-use/loop/ending.js +1 -1
- package/dist/actors/local-agent/cli.d.ts +6 -0
- package/dist/actors/local-agent/cli.js +8 -0
- package/dist/actors/local-agent/readiness.js +2 -1
- package/dist/actors/registry.js +1 -1
- package/dist/analysis/automatic-completion.js +8 -0
- package/dist/cli/commands/observe.js +18 -4
- package/dist/cli/commands/run-signals.js +13 -4
- package/dist/cli/commands/runs.js +36 -13
- package/dist/cli/commands/setup.js +23 -14
- package/dist/cli/commands/study-route-computer-use.js +7 -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-shared-world.js +4 -2
- package/dist/cli/commands/study-run.js +9 -19
- package/dist/cli/commands/watch.js +14 -4
- package/dist/cli/doctor.js +9 -6
- package/dist/cli/findings.d.ts +5 -0
- package/dist/cli/findings.js +28 -6
- package/dist/cli/first-run-path.d.ts +27 -7
- package/dist/cli/first-run-path.js +112 -75
- package/dist/cli/invocation.d.ts +7 -0
- package/dist/cli/invocation.js +32 -0
- package/dist/cli/io.d.ts +5 -2
- package/dist/cli/io.js +9 -16
- package/dist/cli/observer-follow.d.ts +24 -0
- package/dist/cli/observer-follow.js +48 -3
- package/dist/cli/orientation.js +16 -14
- 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 +134 -21
- package/dist/feedback/export-bundle.js +2 -2
- package/dist/keys/key-resolution.js +6 -5
- package/dist/observer/exposure.js +1 -1
- package/dist/observer/library.js +3 -2
- package/dist/observer/render.js +2 -1
- package/dist/observer/run-routes.js +8 -1
- package/dist/observer/serve.js +2 -1
- package/dist/routes/computer-use/participant-runs.d.ts +6 -9
- package/dist/routes/computer-use/participant-runs.js +16 -36
- package/dist/routes/computer-use/plan.d.ts +2 -2
- package/dist/routes/computer-use/plan.js +6 -1
- package/dist/routes/computer-use/route.d.ts +5 -18
- package/dist/routes/computer-use/route.js +17 -78
- package/dist/routes/computer-use/setup.d.ts +3 -2
- package/dist/routes/computer-use/types.d.ts +6 -10
- package/dist/routes/scripted/route.d.ts +4 -17
- package/dist/routes/scripted/route.js +10 -42
- package/dist/routes/scripted/types.d.ts +1 -6
- package/dist/routes/shared-world/plan.js +5 -1
- package/dist/routes/shared-world/route.d.ts +5 -11
- package/dist/routes/shared-world/route.js +24 -63
- package/dist/routes/shared-world/types.d.ts +2 -6
- package/dist/routes/terminal/bundle.js +2 -1
- package/dist/routes/terminal/credentials.d.ts +5 -5
- package/dist/routes/terminal/credentials.js +14 -6
- package/dist/routes/terminal/plan.js +8 -0
- package/dist/routes/terminal/route.d.ts +3 -13
- package/dist/routes/terminal/route.js +24 -63
- package/dist/routes/terminal/runtime-auth.d.ts +6 -0
- package/dist/routes/terminal/runtime-auth.js +5 -0
- package/dist/routes/terminal/session.d.ts +2 -3
- package/dist/routes/terminal/session.js +2 -3
- package/dist/routes/terminal/types.d.ts +2 -7
- package/dist/run/dry-run.js +2 -1
- package/dist/run/judge.js +2 -1
- package/dist/run/local-only-files.d.ts +13 -0
- package/dist/run/local-only-files.js +35 -0
- package/dist/run/reclaim.d.ts +5 -0
- package/dist/run/reclaim.js +34 -2
- package/dist/run/route-shell.d.ts +60 -0
- package/dist/run/route-shell.js +51 -0
- package/dist/run/run-not-found.js +3 -2
- package/dist/run/run.d.ts +6 -1
- package/dist/run/run.js +11 -2
- package/dist/run/sandbox-creates.js +5 -3
- package/dist/run/sandbox-receipts.d.ts +6 -5
- package/dist/run/sandbox-receipts.js +12 -7
- package/dist/run/status.d.ts +2 -0
- package/dist/run/status.js +5 -0
- package/dist/study/agents-section.d.ts +21 -0
- package/dist/study/agents-section.js +86 -0
- package/dist/study/composition-rules.js +1 -1
- package/dist/study/doctor.js +5 -4
- package/dist/study/init-templates.d.ts +1 -1
- package/dist/study/init-templates.js +43 -129
- package/dist/study/init.js +32 -35
- 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 +13 -1
- package/dist/study/parse/url-credentials.d.ts +9 -0
- package/dist/study/parse/url-credentials.js +50 -0
- 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/sandbox.js +33 -15
- 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/tui/contract.js +5 -4
- package/dist/verify/liveness.js +2 -1
- package/dist/verify/verify.js +2 -1
- package/docs/architecture/actor-contract.md +1 -1
- package/docs/architecture/external-public-shared-world.md +1 -1
- package/docs/architecture/terminal-product-route.md +26 -24
- package/docs/contracts/run-bundle.md +20 -4
- package/docs/contracts/schemas.md +6 -5
- package/docs/contracts/study-analysis.md +5 -2
- package/docs/product/automatic-analysis.md +1 -1
- package/package.json +4 -2
- package/skills/humanish/SKILL.md +22 -18
|
@@ -1,20 +1,10 @@
|
|
|
1
|
+
import { type RefusedStudy } from "../../run/route-shell.js";
|
|
1
2
|
import { type TerminalRefusal } from "./plan.js";
|
|
2
3
|
import type { TerminalPlan } from "../../study/plan-types.js";
|
|
3
4
|
import type { AdmittedPlan } from "../../run-study.js";
|
|
4
|
-
import { type
|
|
5
|
-
/**
|
|
6
|
-
* The config-taking entry point. It plans, returns a refusal with the same envelope and analysis
|
|
7
|
-
* record the route has always returned, and otherwise runs the plan.
|
|
8
|
-
*/
|
|
9
|
-
export declare function runTerminalProductStudy(options: RunTerminalProductStudyOptions): Promise<TerminalProductStudyResult>;
|
|
5
|
+
import { type TerminalProductStudyResult, type TerminalRunInput } from "./types.js";
|
|
10
6
|
/** A refused terminal study's result: the route's envelope, and the analysis record a refusal gets. */
|
|
11
|
-
export declare function terminalStudyRefusal(options:
|
|
12
|
-
/**
|
|
13
|
-
* Run a terminal plan. The run scope gives a direct library caller the same run lifetime the CLI
|
|
14
|
-
* gets: whichever of its fail-closed exits the run takes, the run it started is closed, and only a
|
|
15
|
-
* run that published its final bundle reaches automatic analysis.
|
|
16
|
-
*/
|
|
17
|
-
export declare function runTerminalPlan(plan: TerminalPlan, input: TerminalRunInput): Promise<TerminalProductStudyResult>;
|
|
7
|
+
export declare function terminalStudyRefusal(options: TerminalRunInput & RefusedStudy, refusal: TerminalRefusal): Promise<TerminalProductStudyResult>;
|
|
18
8
|
/**
|
|
19
9
|
* runStudyWith's step for a terminal plan. It runs a live plan's local checks (checkLiveTerminalMachine)
|
|
20
10
|
* before any run scope opens, so the CLI can present their refusal before it loads a declared
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
// The safety contract (`docs/history/goals/terminal-product-lane/goal.md`) is enforced by construction in
|
|
12
12
|
// the files each item names, and checked by the verifier (verify/actor.ts
|
|
13
13
|
// validateTerminalProductEvidence):
|
|
14
|
-
// 1. Explicit key placement. openai-
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
14
|
+
// 1. Explicit key placement. openai-egress (default) sends the raw runtime key only in the
|
|
15
|
+
// host-side E2B header transform and passes an inert command placeholder. The proxy is
|
|
16
|
+
// spendable by every sandbox process from creation; this protects the raw key, not provider
|
|
17
|
+
// spending. Opt-in openai-env injects the raw key command-scoped, never through
|
|
18
|
+
// Sandbox.create({envs}).
|
|
18
19
|
// Enforced in credentials.ts and runtime-auth.ts.
|
|
19
20
|
// 2. Fail-closed cap. The live key is never exercised without scenario.caps in force: maxUsd
|
|
20
21
|
// (default/require 0 = no-spend) + maxMinutes (wall-clock kill of the codex command).
|
|
@@ -43,31 +44,16 @@
|
|
|
43
44
|
// humanish never calls Sandbox.list to prove cleanup, so a shared operator key never reaches a
|
|
44
45
|
// sandbox it did not create. A live run that cannot prove teardown fails closed.
|
|
45
46
|
// Enforced in sandbox.ts.
|
|
46
|
-
import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
|
|
47
|
-
import { completeAutomaticAnalysis } from "../../analysis/automatic-completion.js";
|
|
48
47
|
import path from "node:path";
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
48
|
+
import {} from "../../run/run.js";
|
|
49
|
+
import { admitRoute, completeRefusalAnalysis, } from "../../run/route-shell.js";
|
|
50
|
+
import {} from "./plan.js";
|
|
51
51
|
import { runDryTerminalStudy } from "./dry-run.js";
|
|
52
52
|
import { checkLiveTerminalMachine, runLiveTerminalSession } from "./session.js";
|
|
53
53
|
import { terminalRouteScorer } from "../../study/adapter-scorer-loader.js";
|
|
54
54
|
import { withLateScorer } from "../../study/route-inputs.js";
|
|
55
55
|
import {} from "./types.js";
|
|
56
56
|
import { studyResultIdentity } from "../../run/study-result.js";
|
|
57
|
-
/**
|
|
58
|
-
* The config-taking entry point. It plans, returns a refusal with the same envelope and analysis
|
|
59
|
-
* record the route has always returned, and otherwise runs the plan.
|
|
60
|
-
*/
|
|
61
|
-
export async function runTerminalProductStudy(options) {
|
|
62
|
-
const { config, dryRun, ...input } = options;
|
|
63
|
-
const planned = planTerminalStudy(config, {
|
|
64
|
-
dryRun,
|
|
65
|
-
hasCostProbe: input.deps?.costProbe !== undefined,
|
|
66
|
-
});
|
|
67
|
-
if (planned.ok)
|
|
68
|
-
return runTerminalPlan(planned.plan, input);
|
|
69
|
-
return terminalStudyRefusal(options, planned.refusal);
|
|
70
|
-
}
|
|
71
57
|
/** A refused terminal study's result: the route's envelope, and the analysis record a refusal gets. */
|
|
72
58
|
export function terminalStudyRefusal(options, refusal) {
|
|
73
59
|
const { config, dryRun } = options;
|
|
@@ -82,60 +68,35 @@ export function terminalStudyRefusal(options, refusal) {
|
|
|
82
68
|
warnings: [],
|
|
83
69
|
error: { code: refusal.code, message: refusal.message },
|
|
84
70
|
};
|
|
85
|
-
|
|
86
|
-
const analysis = resolveAutomaticAnalysis(config.review?.analysis);
|
|
87
|
-
return completeAutomaticAnalysis(refused, undefined, analysis.ok ? analysis.config : undefined, options, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Run a terminal plan. The run scope gives a direct library caller the same run lifetime the CLI
|
|
91
|
-
* gets: whichever of its fail-closed exits the run takes, the run it started is closed, and only a
|
|
92
|
-
* run that published its final bundle reaches automatic analysis.
|
|
93
|
-
*/
|
|
94
|
-
export async function runTerminalPlan(plan, input) {
|
|
95
|
-
const admission = await admitTerminalRun(plan, input);
|
|
96
|
-
return admission.ok ? runAdmittedTerminalRun(admission.admitted, input) : admission.result;
|
|
71
|
+
return completeRefusalAnalysis(refused, config, options);
|
|
97
72
|
}
|
|
98
73
|
/**
|
|
99
74
|
* runStudyWith's step for a terminal plan. It runs a live plan's local checks (checkLiveTerminalMachine)
|
|
100
75
|
* before any run scope opens, so the CLI can present their refusal before it loads a declared
|
|
101
76
|
* scorer, and returns the run that continues from them with that scorer.
|
|
102
77
|
*/
|
|
103
|
-
export
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
function terminalOutcome(result) {
|
|
113
|
-
return { route: "terminal", result };
|
|
78
|
+
export function admitTerminalPlan(plan, input) {
|
|
79
|
+
return admitRoute({
|
|
80
|
+
route: "terminal",
|
|
81
|
+
analysis: plan.analysis,
|
|
82
|
+
input,
|
|
83
|
+
admit: () => admitTerminalRun(plan, input),
|
|
84
|
+
withScorer: (base, scorer) => withLateScorer(base, scorer, terminalRouteScorer),
|
|
85
|
+
runInScope: runTerminalPlanInScope,
|
|
86
|
+
});
|
|
114
87
|
}
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
* returns for it, with the analysis record of a run that never started.
|
|
118
|
-
*/
|
|
119
|
-
async function admitTerminalRun(plan, input) {
|
|
88
|
+
/** A live plan's local checks, made outside any run scope. */
|
|
89
|
+
function admitTerminalRun(plan, input) {
|
|
120
90
|
const warnings = [];
|
|
121
91
|
if (plan.dryRun)
|
|
122
92
|
return { ok: true, admitted: { plan, warnings } };
|
|
123
93
|
const checked = checkLiveTerminalMachine(plan, input, warnings);
|
|
124
94
|
if (checked.ok)
|
|
125
95
|
return { ok: true, admitted: { plan, warnings, runtimeEnv: checked.runtimeEnv } };
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
async function runAdmittedTerminalRun(admitted, input) {
|
|
131
|
-
const { result, finished } = await runScope((scope) => runTerminalPlanInScope(admitted, input, scope));
|
|
132
|
-
return completeTerminalAnalysis(admitted.plan, input, result, finished);
|
|
133
|
-
}
|
|
134
|
-
function completeTerminalAnalysis(plan, input, result, finished) {
|
|
135
|
-
return completeAutomaticAnalysis(result, finished, plan.analysis?.config, input, {
|
|
136
|
-
...(plan.analysis === undefined ? {} : { trigger: plan.analysis.trigger }),
|
|
137
|
-
preferLargerOutput: plan.analysis?.preferLargerOutput === true,
|
|
138
|
-
});
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
result: terminalFailure(plan, input, warnings)(checked.code, checked.message),
|
|
99
|
+
};
|
|
139
100
|
}
|
|
140
101
|
/** The route's envelope for a run that stops before its bundle. */
|
|
141
102
|
function terminalFailure(plan, input, warnings) {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import type { StudyRuntimeAuth } from "../../study/types.js";
|
|
1
2
|
import type { E2BNetworkOptions } from "../../substrates/e2b/sdk.js";
|
|
3
|
+
/**
|
|
4
|
+
* The runtime auth a study gets when it declares none. A shell agent can read its own command env,
|
|
5
|
+
* so the raw key stays in the host-side E2B request unless a study declares openai-env.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_RUNTIME_AUTH: StudyRuntimeAuth;
|
|
2
8
|
/** E2B envd installs the sandbox-specific proxy CA into this system bundle before routing. */
|
|
3
9
|
export declare const E2B_SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
|
|
4
10
|
/** An inert nonsecret value satisfies Codex exec's auth prerequisite. E2B replaces the header. */
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime auth a study gets when it declares none. A shell agent can read its own command env,
|
|
3
|
+
* so the raw key stays in the host-side E2B request unless a study declares openai-env.
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_RUNTIME_AUTH = "openai-egress";
|
|
1
6
|
const OPENAI_EGRESS_HOST = "api.openai.com";
|
|
2
7
|
/** E2B envd installs the sandbox-specific proxy CA into this system bundle before routing. */
|
|
3
8
|
export const E2B_SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
|
|
@@ -13,9 +13,8 @@ export declare function checkLiveTerminalMachine(plan: LiveTerminalPlan, input:
|
|
|
13
13
|
readonly message: string;
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
|
-
* The live in-sandbox agent session orchestrator
|
|
17
|
-
*
|
|
18
|
-
* precondition miss. Persists the substrate-lifecycle/command-log/interventions/cleanup ledgers,
|
|
16
|
+
* The live in-sandbox agent session orchestrator. Enforces the 8-point safety contract by
|
|
17
|
+
* construction; fails closed before any sandbox/key/spend on any precondition miss. Persists the substrate-lifecycle/command-log/interventions/cleanup ledgers,
|
|
19
18
|
* the redacted terminal event stream + normalized transcript, the agent report, and the
|
|
20
19
|
* provider-neutral actor trace; tears the sandbox down in a finally and proves the teardown.
|
|
21
20
|
*/
|
|
@@ -38,9 +38,8 @@ export function checkLiveTerminalMachine(plan, input, warnings) {
|
|
|
38
38
|
return { ok: true, runtimeEnv };
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* The live in-sandbox agent session orchestrator
|
|
42
|
-
*
|
|
43
|
-
* precondition miss. Persists the substrate-lifecycle/command-log/interventions/cleanup ledgers,
|
|
41
|
+
* The live in-sandbox agent session orchestrator. Enforces the 8-point safety contract by
|
|
42
|
+
* construction; fails closed before any sandbox/key/spend on any precondition miss. Persists the substrate-lifecycle/command-log/interventions/cleanup ledgers,
|
|
44
43
|
* the redacted terminal event stream + normalized transcript, the agent report, and the
|
|
45
44
|
* provider-neutral actor trace; tears the sandbox down in a finally and proves the teardown.
|
|
46
45
|
*/
|
|
@@ -6,7 +6,6 @@ import type { RunScope } from "../../run/run.js";
|
|
|
6
6
|
import type { buildRuntimeAuth } from "./credentials.js";
|
|
7
7
|
import type { TerminalPlan } from "../../study/plan-types.js";
|
|
8
8
|
import type { StudyDeps } from "../../study/study-deps.js";
|
|
9
|
-
import type { StudyConfig } from "../../study/types.js";
|
|
10
9
|
import type { ObserverResult } from "../../observer/render.js";
|
|
11
10
|
import { type RunAdapterScore, type RunBundle, type RunFeedbackCandidate, type RunScorerProvenance } from "../../run/bundle.js";
|
|
12
11
|
import { type StudyResultIdentity } from "../../run/study-result.js";
|
|
@@ -97,15 +96,13 @@ export interface TerminalScorer {
|
|
|
97
96
|
*/
|
|
98
97
|
deriveFeedback?: (ctx: TerminalProductScoringContext) => RunFeedbackCandidate[] | Promise<RunFeedbackCandidate[]>;
|
|
99
98
|
}
|
|
100
|
-
|
|
99
|
+
/** What a terminal run takes besides its plan. The plan carries the config, dry run and study. */
|
|
100
|
+
export interface TerminalRunInput {
|
|
101
101
|
/** Cancels post-run analysis only. */
|
|
102
102
|
analysisSignal?: AbortSignal;
|
|
103
103
|
/** Reports the analysis window to onEvent; built by normalizeRunStudyOptions. */
|
|
104
104
|
emit?: (event: StudyEvent) => void;
|
|
105
105
|
cwd: string;
|
|
106
|
-
config: StudyConfig;
|
|
107
|
-
/** Resolved upstream (scenario.mode + CLI override); defaults safe (dry-run). */
|
|
108
|
-
dryRun: boolean;
|
|
109
106
|
open?: boolean;
|
|
110
107
|
runId?: string;
|
|
111
108
|
/**
|
|
@@ -312,8 +309,6 @@ export interface TerminalLedgers {
|
|
|
312
309
|
/** The no-spend proof derived from `cost`. Never an independent assertion. */
|
|
313
310
|
noSpendProof: NoSpendProof;
|
|
314
311
|
}
|
|
315
|
-
/** What a terminal run takes besides its plan. The plan carries the config, dry run and study. */
|
|
316
|
-
export type TerminalRunInput = Omit<RunTerminalProductStudyOptions, "config" | "dryRun">;
|
|
317
312
|
/** A live terminal plan: its caps are the fail-closed ones planTerminalStudy required. */
|
|
318
313
|
export type LiveTerminalPlan = Extract<TerminalPlan, {
|
|
319
314
|
readonly dryRun: false;
|
package/dist/run/dry-run.js
CHANGED
|
@@ -11,6 +11,7 @@ import { implicitProjectDirectoryExists, readPackageName, validateCwd } from "./
|
|
|
11
11
|
import { loadDryRunInputs } from "./dry-run-inputs.js";
|
|
12
12
|
import { judgeExecution, judgePreview, OUTCOME_POLICIES, resultOk, verdictText, } from "./judge.js";
|
|
13
13
|
import { reviewOutcome } from "./display.js";
|
|
14
|
+
import { cli } from "../cli/invocation.js";
|
|
14
15
|
/**
|
|
15
16
|
* The preview route's run. The run scope closes the run it started on every exit, including
|
|
16
17
|
* the fail-closed ones. It renders an Observer only when `options.observer` asks for one.
|
|
@@ -49,7 +50,7 @@ async function runDryRunInScope(options, scope) {
|
|
|
49
50
|
if (!options.dryRun) {
|
|
50
51
|
return refused(requestedCwd, warnings, {
|
|
51
52
|
code: "HUMANISH_LIVE_RUN_UNIMPLEMENTED",
|
|
52
|
-
message:
|
|
53
|
+
message: `humanish run needs a study. List studies with ${cli("study list")}, or run ${cli("run --dry-run")} for a sample bundle.`,
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
const createdAt = new Date().toISOString();
|
package/dist/run/judge.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// 4. foldScorerFailures, which every route with a scorer applies before the run finishes;
|
|
13
13
|
// 5. the outcome: judgeExecution applies the route's policy to its execution failures, and
|
|
14
14
|
// resultOk gives the result's ok.
|
|
15
|
+
import { cli } from "../cli/invocation.js";
|
|
15
16
|
/**
|
|
16
17
|
* The participant said it reached the goal having taken no action and said nothing, and no stop
|
|
17
18
|
* condition ended the session. It most likely saw a blank or loading screen, so it is not a pass.
|
|
@@ -126,7 +127,7 @@ export const EXECUTION_FAILURE_KINDS = [
|
|
|
126
127
|
export function sandboxCleanupFailure(owner, warning, runId, recovery) {
|
|
127
128
|
return {
|
|
128
129
|
kind: "sandbox-cleanup",
|
|
129
|
-
message: `${owner}: ${warning ?? "Sandbox release is unconfirmed."} ${recovery ?? `Reclaim it by recorded id with
|
|
130
|
+
message: `${owner}: ${warning ?? "Sandbox release is unconfirmed."} ${recovery ?? `Reclaim it by recorded id with \`${cli(`reclaim --run ${runId}`)}\`.`}`,
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
/** Each route's outcome policy, in one place. */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Each local-only file, relative to the run directory, with why a shared copy omits it. */
|
|
2
|
+
export declare const LOCAL_ONLY_RUN_FILES: ReadonlyMap<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* Whether a run-relative posix path names a local-only file. Case is ignored, so a request that
|
|
5
|
+
* spells the name differently cannot reach the file on a case-insensitive filesystem.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isLocalOnlyRunFile(relativePath: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Whether `filePath` is one of the local-only files of the run at `runRoot` under another
|
|
10
|
+
* spelling: the same device and inode. A case-insensitive filesystem also folds Unicode, so a name
|
|
11
|
+
* check alone cannot rule every spelling out.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isLocalOnlyRunFileIdentity(runRoot: string, filePath: string): Promise<boolean>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Run files that mean something only on the machine that recorded the run. sandbox-receipts.ndjson
|
|
2
|
+
// holds the raw sandbox ids reclaim kills by, and status.json holds the recording process's pid.
|
|
3
|
+
// Bundle export leaves both out of a shared copy, and the Observer servers never hand them out.
|
|
4
|
+
import { lstat } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { SANDBOX_RECEIPTS_ARTIFACT } from "./sandbox-receipts.js";
|
|
7
|
+
import { RUN_STATUS_FILE } from "./status.js";
|
|
8
|
+
/** Each local-only file, relative to the run directory, with why a shared copy omits it. */
|
|
9
|
+
export const LOCAL_ONLY_RUN_FILES = new Map([
|
|
10
|
+
[RUN_STATUS_FILE, "local process status is not a new attempt"],
|
|
11
|
+
[SANDBOX_RECEIPTS_ARTIFACT, "operational journal does not confer a derivative resource lease"],
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Whether a run-relative posix path names a local-only file. Case is ignored, so a request that
|
|
15
|
+
* spells the name differently cannot reach the file on a case-insensitive filesystem.
|
|
16
|
+
*/
|
|
17
|
+
export function isLocalOnlyRunFile(relativePath) {
|
|
18
|
+
return LOCAL_ONLY_RUN_FILES.has(relativePath.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Whether `filePath` is one of the local-only files of the run at `runRoot` under another
|
|
22
|
+
* spelling: the same device and inode. A case-insensitive filesystem also folds Unicode, so a name
|
|
23
|
+
* check alone cannot rule every spelling out.
|
|
24
|
+
*/
|
|
25
|
+
export async function isLocalOnlyRunFileIdentity(runRoot, filePath) {
|
|
26
|
+
const requested = await lstat(filePath, { bigint: true }).catch(() => null);
|
|
27
|
+
if (requested === null)
|
|
28
|
+
return false;
|
|
29
|
+
for (const name of LOCAL_ONLY_RUN_FILES.keys()) {
|
|
30
|
+
const local = await lstat(path.join(runRoot, name), { bigint: true }).catch(() => null);
|
|
31
|
+
if (local !== null && local.dev === requested.dev && local.ino === requested.ino)
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
package/dist/run/reclaim.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export interface ReclaimResult {
|
|
|
18
18
|
tagSearch: ReclaimTagSearch;
|
|
19
19
|
/** Creates that had not returned when reclaim finished; only the signal handler sees any. */
|
|
20
20
|
createsInFlight: number;
|
|
21
|
+
/**
|
|
22
|
+
* Why there was nothing to look for: `dry-run` when run.json or status.json records the run as
|
|
23
|
+
* a dry run, which creates no sandboxes. Reclaim then contacts E2B for nothing.
|
|
24
|
+
*/
|
|
25
|
+
reason?: "dry-run";
|
|
21
26
|
warnings: string[];
|
|
22
27
|
error?: {
|
|
23
28
|
code: "HUMANISH_RECLAIM_RUN_NOT_FOUND" | "HUMANISH_RECLAIM_E2B_DEBUG" | "HUMANISH_RECLAIM_MODULE_UNAVAILABLE" | "HUMANISH_RECLAIM_RECEIPTS_UNREADABLE";
|
package/dist/run/reclaim.js
CHANGED
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
import { E2B_DEBUG_ENV, e2bDebugMode, loadE2BDesktopModule, } from "../substrates/e2b/sdk.js";
|
|
12
12
|
import { containedPathAbsent, readContainedRegularFile, } from "./contained-output.js";
|
|
13
13
|
import { discardPreflightJournal, listPreflightJournals, preflightReclaimDecision, } from "./preflight-receipts.js";
|
|
14
|
-
import { resolveRunPath } from "./locate.js";
|
|
14
|
+
import { readRunJsonIfExists, resolveRunPath } from "./locate.js";
|
|
15
|
+
import { RUN_BUNDLE_FILE } from "./bundle.js";
|
|
16
|
+
import { RUN_STATUS_FILE } from "./status.js";
|
|
17
|
+
import { isRecord } from "./type-guards.js";
|
|
15
18
|
import { parseSandboxOwners, parseSandboxReceipts, SANDBOX_RECEIPTS_ARTIFACT, } from "./sandbox-receipts.js";
|
|
16
19
|
import { sandboxOwnerTags, } from "./sandbox-creates.js";
|
|
17
20
|
import { earlierOutcomes, GONE, RECLAIM_RECEIPT_ARTIFACT, RECLAIM_RESULT_SCHEMA, receiptWriter, rootState, searchTagSets, settledWithin, targetSet, } from "./reclaim-outcomes.js";
|
|
18
21
|
import { redactText, toErrorMessage } from "../evidence/redaction.js";
|
|
19
22
|
import { runIdOf } from "./paths.js";
|
|
23
|
+
import { cli } from "../cli/invocation.js";
|
|
20
24
|
export { RECLAIM_RECEIPT_ARTIFACT };
|
|
21
25
|
export function reclaimRunSandboxes(cwd, runInput, hooks = {}) {
|
|
22
26
|
return reclaimRun(cwd, runInput, () => resolveRunPath(cwd, runInput), hooks);
|
|
@@ -54,11 +58,14 @@ async function reclaimRun(cwd, runInput, locate, hooks) {
|
|
|
54
58
|
...base,
|
|
55
59
|
error: {
|
|
56
60
|
code: "HUMANISH_RECLAIM_RUN_NOT_FOUND",
|
|
57
|
-
message: `No run found for "${runInput}" (use
|
|
61
|
+
message: `No run found for "${runInput}" (use \`${cli("runs")}\` to list runs).`,
|
|
58
62
|
},
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
65
|
const runId = runIdOf(runPaths);
|
|
66
|
+
// The signal handler passes the creates it stopped and decides from what they reported.
|
|
67
|
+
if (hooks.creates === undefined && (await recordedDryRun(runPaths)))
|
|
68
|
+
return { ...base, ok: true, state: "clean", runId, reason: "dry-run" };
|
|
62
69
|
const reclaimed = await reclaimRoot(runPaths, runId, hooks, warnings);
|
|
63
70
|
if (reclaimed.kind === "module-unavailable")
|
|
64
71
|
return {
|
|
@@ -85,6 +92,31 @@ async function reclaimRun(cwd, runInput, locate, hooks) {
|
|
|
85
92
|
createsInFlight,
|
|
86
93
|
};
|
|
87
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether the run records that it was a dry run: run.json and status.json, each one present, say
|
|
97
|
+
* `dry-run`. A run with neither record is not taken for one. A journal with any line in it, or an
|
|
98
|
+
* earlier reclaim receipt that names a sandbox, means a create ran, so that run is reclaimed as a
|
|
99
|
+
* live one whatever its records say.
|
|
100
|
+
*/
|
|
101
|
+
async function recordedDryRun(runPaths) {
|
|
102
|
+
const modes = [];
|
|
103
|
+
for (const file of [RUN_BUNDLE_FILE, RUN_STATUS_FILE]) {
|
|
104
|
+
const record = await readRunJsonIfExists(runPaths, file);
|
|
105
|
+
if (isRecord(record))
|
|
106
|
+
modes.push(record.mode);
|
|
107
|
+
}
|
|
108
|
+
if (modes.length === 0 || modes.some((mode) => mode !== "dry-run"))
|
|
109
|
+
return false;
|
|
110
|
+
if (!(await containedPathAbsent(runPaths, RECLAIM_RECEIPT_ARTIFACT))) {
|
|
111
|
+
const earlier = await readRunJsonIfExists(runPaths, RECLAIM_RECEIPT_ARTIFACT);
|
|
112
|
+
if (!isRecord(earlier) || !Array.isArray(earlier.outcomes) || earlier.outcomes.length > 0)
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (await containedPathAbsent(runPaths, SANDBOX_RECEIPTS_ARTIFACT))
|
|
116
|
+
return true;
|
|
117
|
+
const journal = await readContainedRegularFile(runPaths, SANDBOX_RECEIPTS_ARTIFACT);
|
|
118
|
+
return journal !== null && journal.toString("utf8").trim() === "";
|
|
119
|
+
}
|
|
88
120
|
/**
|
|
89
121
|
* Kill the sandboxes left by interrupted `humanish study check` probes. A probe journals under
|
|
90
122
|
* .humanish/preflight/<probe-id>/ and removes the journal after a confirmed kill, so what is left
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type AnalysisInput } from "../analysis/automatic-completion.js";
|
|
2
|
+
import type { AutomaticAnalysisDeps } from "../analysis/automatic.js";
|
|
3
|
+
import type { AdmittedPlan, StudyResult } from "../run-study.js";
|
|
4
|
+
import type { PlannedAnalysis } from "../study/plan-types.js";
|
|
5
|
+
import type { LateScorer } from "../study/route-inputs.js";
|
|
6
|
+
import type { StudyRoute } from "../study/routing.js";
|
|
7
|
+
import type { StudyConfig } from "../study/types.js";
|
|
8
|
+
import { type RunScope } from "./run.js";
|
|
9
|
+
type ShellRoute = Exclude<StudyRoute, "preview">;
|
|
10
|
+
/** A route's local checks: the refused result, or what its run continues from. */
|
|
11
|
+
export type RouteAdmission<R extends ShellRoute, A> = {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly result: StudyResult<R>;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly admitted: A;
|
|
17
|
+
};
|
|
18
|
+
/** A route's own parts of its run. Everything around them is admitRoute's. */
|
|
19
|
+
export interface RouteShell<R extends ShellRoute, I extends AnalysisInput, A> {
|
|
20
|
+
readonly route: R;
|
|
21
|
+
/** The plan's automatic analysis, which both the refused result and the run complete. */
|
|
22
|
+
readonly analysis: PlannedAnalysis | undefined;
|
|
23
|
+
/** The route's input as the checks admitted it. */
|
|
24
|
+
readonly input: I;
|
|
25
|
+
/** The checks that need no scorer, made before any run scope opens. */
|
|
26
|
+
admit(): Promise<RouteAdmission<R, A>> | RouteAdmission<R, A>;
|
|
27
|
+
/** The run inside its run scope, with the input a late scorer joined. */
|
|
28
|
+
runInScope(admitted: A, input: I, scope: RunScope): Promise<StudyResult<R>>;
|
|
29
|
+
/** Layers a scorer the CLI loaded after the checks over the input. Scripted takes no scorer. */
|
|
30
|
+
readonly withScorer?: (input: I, scorer: LateScorer | undefined) => I;
|
|
31
|
+
/** Computer use's reason to skip analysis: a local VM study whose cleanup is unconfirmed. */
|
|
32
|
+
readonly analysisRefusal?: AutomaticAnalysisDeps["refusal"];
|
|
33
|
+
/**
|
|
34
|
+
* Computer use and shared world receive email. The run and its analysis share one scope for the
|
|
35
|
+
* secrets it registers. The other routes run outside any scope, where scrubbing changes nothing.
|
|
36
|
+
*/
|
|
37
|
+
readonly commsSecrets?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Runs a route's checks and returns the refusal they made or the route's run. A refusal carries the
|
|
41
|
+
* analysis record of a run that never started. The run opens its own run scope, so whichever exit
|
|
42
|
+
* it takes closes the run it started, and only a run that published its final bundle is analyzed.
|
|
43
|
+
*/
|
|
44
|
+
export declare function admitRoute<R extends ShellRoute, I extends AnalysisInput, A>(shell: RouteShell<R, I, A>): Promise<AdmittedPlan<R>>;
|
|
45
|
+
/**
|
|
46
|
+
* A refused study's result with the analysis record a refusal gets: it starts no run, so a declared
|
|
47
|
+
* or default analysis is recorded as skipped.
|
|
48
|
+
*/
|
|
49
|
+
export declare function completeRefusalAnalysis<T extends {
|
|
50
|
+
cwd: string;
|
|
51
|
+
runId: string;
|
|
52
|
+
dryRun: boolean;
|
|
53
|
+
}>(refused: T, config: StudyConfig, input: AnalysisInput): Promise<T>;
|
|
54
|
+
/** What a refused study's envelope reads besides the route's input. */
|
|
55
|
+
export interface RefusedStudy {
|
|
56
|
+
readonly config: StudyConfig;
|
|
57
|
+
/** The run's resolved dry run. */
|
|
58
|
+
readonly dryRun: boolean;
|
|
59
|
+
}
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The shell the computer-use, scripted, terminal and shared-world routes share. A route supplies
|
|
2
|
+
// its local checks and the body it runs inside the run scope. admitRoute completes the automatic
|
|
3
|
+
// analysis of a refusal and of a run, and wraps the result as the route's outcome, so a change to
|
|
4
|
+
// either is made here once. The preview route records no run and has no shell.
|
|
5
|
+
import { completeAutomaticAnalysis } from "../analysis/automatic-completion.js";
|
|
6
|
+
import { resolveAutomaticAnalysis } from "../analysis/automatic-config.js";
|
|
7
|
+
import { runScope } from "./run.js";
|
|
8
|
+
import { withTransientCommsSecrets } from "./transient-comms-secrets.js";
|
|
9
|
+
/**
|
|
10
|
+
* Runs a route's checks and returns the refusal they made or the route's run. A refusal carries the
|
|
11
|
+
* analysis record of a run that never started. The run opens its own run scope, so whichever exit
|
|
12
|
+
* it takes closes the run it started, and only a run that published its final bundle is analyzed.
|
|
13
|
+
*/
|
|
14
|
+
export async function admitRoute(shell) {
|
|
15
|
+
const complete = (input, result, finished) => completeAutomaticAnalysis(result, finished, shell.analysis?.config, input, {
|
|
16
|
+
...(shell.analysis === undefined ? {} : { trigger: shell.analysis.trigger }),
|
|
17
|
+
preferLargerOutput: shell.analysis?.preferLargerOutput === true,
|
|
18
|
+
...(shell.analysisRefusal === undefined ? {} : { refusal: shell.analysisRefusal }),
|
|
19
|
+
});
|
|
20
|
+
const admission = await shell.admit();
|
|
21
|
+
if (!admission.ok)
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
outcome: routeOutcome(shell.route, await complete(shell.input, admission.result, undefined)),
|
|
25
|
+
};
|
|
26
|
+
const { admitted } = admission;
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
run: async (scorer) => {
|
|
30
|
+
// The scorer joins the input only; whatever the checks admitted stays what the run uses.
|
|
31
|
+
const input = shell.withScorer?.(shell.input, scorer) ?? shell.input;
|
|
32
|
+
const run = async () => {
|
|
33
|
+
const { result, finished } = await runScope((scope) => shell.runInScope(admitted, input, scope));
|
|
34
|
+
return complete(input, result, finished);
|
|
35
|
+
};
|
|
36
|
+
return routeOutcome(shell.route, await (shell.commsSecrets ? withTransientCommsSecrets(run) : run()));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A refused study's result with the analysis record a refusal gets: it starts no run, so a declared
|
|
42
|
+
* or default analysis is recorded as skipped.
|
|
43
|
+
*/
|
|
44
|
+
export function completeRefusalAnalysis(refused, config, input) {
|
|
45
|
+
const analysis = resolveAutomaticAnalysis(config.review?.analysis);
|
|
46
|
+
return completeAutomaticAnalysis(refused, undefined, analysis.ok ? analysis.config : undefined, input, { trigger: config.review?.analysis === undefined ? "default" : "explicit" });
|
|
47
|
+
}
|
|
48
|
+
/** A result as its route's outcome. StudyOutcome pairs each route with its result type. */
|
|
49
|
+
function routeOutcome(route, result) {
|
|
50
|
+
return { route, result };
|
|
51
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveRunsRoot } from "./paths.js";
|
|
4
|
+
import { cli } from "../cli/invocation.js";
|
|
4
5
|
/**
|
|
5
6
|
* The message for a run that is not there: how to start one when the project has no runs, or how
|
|
6
7
|
* to list them when it has. Only the runs directory's entry names are read.
|
|
@@ -10,6 +11,6 @@ export async function runNotFoundMessage(cwdInput, runInput) {
|
|
|
10
11
|
const entries = await readdir(resolveRunsRoot(cwd)).catch(() => []);
|
|
11
12
|
const hasRun = entries.some((name) => !name.startsWith(".") && !name.endsWith(".json"));
|
|
12
13
|
return hasRun
|
|
13
|
-
? `No run ${runInput};
|
|
14
|
-
: `No runs in ${cwd} yet; start one with
|
|
14
|
+
? `No run ${runInput}; ${cli("runs")} lists them.`
|
|
15
|
+
: `No runs in ${cwd} yet; start one with ${cli("run first-run")}.`;
|
|
15
16
|
}
|
package/dist/run/run.d.ts
CHANGED
|
@@ -92,7 +92,12 @@ export declare class FinishedRun {
|
|
|
92
92
|
readonly runId: string;
|
|
93
93
|
/** The paths created by startRun and validated at the start of `finish`. */
|
|
94
94
|
readonly paths: PreparedRunArtifactPaths;
|
|
95
|
-
constructor(key: typeof issueKey, runId: string, paths: PreparedRunArtifactPaths, observer: ObserverTarget | undefined, outcome: RecordedOutcome, recordFailure: (failure: ExecutionFailure) => Promise<RecordedOutcome
|
|
95
|
+
constructor(key: typeof issueKey, runId: string, paths: PreparedRunArtifactPaths, observer: ObserverTarget | undefined, outcome: RecordedOutcome, recordFailure: (failure: ExecutionFailure) => Promise<RecordedOutcome>, interrupted: () => boolean);
|
|
96
|
+
/**
|
|
97
|
+
* A signal stopped this run's process: status.json and run.json record it `interrupted`. Read
|
|
98
|
+
* when asked, since the signal can arrive after the bundle was published.
|
|
99
|
+
*/
|
|
100
|
+
get interrupted(): boolean;
|
|
96
101
|
static isIssued(value: unknown): value is FinishedRun;
|
|
97
102
|
/**
|
|
98
103
|
* The run's ok and execution outcome as run.json records them: what the route passed to
|
package/dist/run/run.js
CHANGED
|
@@ -27,11 +27,12 @@ const issueKey = Symbol("FinishedRun");
|
|
|
27
27
|
export class FinishedRun {
|
|
28
28
|
#observer;
|
|
29
29
|
#recordFailure;
|
|
30
|
+
#interrupted;
|
|
30
31
|
#outcome;
|
|
31
32
|
runId;
|
|
32
33
|
/** The paths created by startRun and validated at the start of `finish`. */
|
|
33
34
|
paths;
|
|
34
|
-
constructor(key, runId, paths, observer, outcome, recordFailure) {
|
|
35
|
+
constructor(key, runId, paths, observer, outcome, recordFailure, interrupted) {
|
|
35
36
|
if (key !== issueKey)
|
|
36
37
|
throw new Error("Only Run.finish issues a FinishedRun.");
|
|
37
38
|
this.runId = runId;
|
|
@@ -39,6 +40,14 @@ export class FinishedRun {
|
|
|
39
40
|
this.#observer = observer;
|
|
40
41
|
this.#outcome = outcome;
|
|
41
42
|
this.#recordFailure = recordFailure;
|
|
43
|
+
this.#interrupted = interrupted;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A signal stopped this run's process: status.json and run.json record it `interrupted`. Read
|
|
47
|
+
* when asked, since the signal can arrive after the bundle was published.
|
|
48
|
+
*/
|
|
49
|
+
get interrupted() {
|
|
50
|
+
return this.#interrupted();
|
|
42
51
|
}
|
|
43
52
|
static isIssued(value) {
|
|
44
53
|
return typeof value === "object" && value !== null && #observer in value;
|
|
@@ -167,7 +176,7 @@ function runPublisher(args) {
|
|
|
167
176
|
await writeBundleFiles(bundle, finishedOutcome(current), (publicBundle) => runStatus.restate(runStatusOutcome(publicBundle))).catch(() => undefined);
|
|
168
177
|
return current;
|
|
169
178
|
}));
|
|
170
|
-
return new FinishedRun(issueKey, runId, paths, observer, current, recordFailure);
|
|
179
|
+
return new FinishedRun(issueKey, runId, paths, observer, current, recordFailure, () => runStatus.interrupted);
|
|
171
180
|
};
|
|
172
181
|
const interrupt = async (signal) => {
|
|
173
182
|
interrupted = { state: "interrupted", ok: false, signal, at: new Date(now()).toISOString() };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// The sandboxes this process is creating for each run directory, and the E2B tags that name the
|
|
2
|
-
// directory.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// directory. E2B creates a sandbox before its id reaches this process: on @e2b/desktop the startup
|
|
3
|
+
// guard reports the id, and its receipt is written, only once the SDK has the handle, and a
|
|
4
|
+
// create from an SDK without the guard reports it when `Sandbox.create` resolves, after desktop
|
|
5
|
+
// startup. The CLI's signal handler (src/cli/commands/run-signals.ts) uses this registry to cover
|
|
6
|
+
// the window before a receipt lands:
|
|
5
7
|
// it stops new creates, kills every id a create has reported, and kills each in-flight create's
|
|
6
8
|
// sandbox as soon as its id arrives. The tags let `humanish reclaim` find a sandbox whose id
|
|
7
9
|
// never reached this process at all.
|
|
@@ -23,12 +23,13 @@ export interface ParsedSandboxReceipt extends Omit<SandboxReceipt, "provider"> {
|
|
|
23
23
|
/** The ids this process has receipted for `root`, whether or not the journal write landed. */
|
|
24
24
|
export declare function appendedSandboxIds(root: PreparedOutputRoot): string[];
|
|
25
25
|
/**
|
|
26
|
-
* Append one receipt. Best-effort by design: the receipt exists to
|
|
27
|
-
* receipt write must never fail the participant. The only cost of a
|
|
28
|
-
* this id and the TTL backstop covers it instead. Containment is
|
|
29
|
-
* artifact write uses; append (not atomic-replace) keeps racing
|
|
26
|
+
* Append one receipt; false when the write failed. Best-effort by design: the receipt exists to
|
|
27
|
+
* protect the run, so a failed receipt write must never fail the participant. The only cost of a
|
|
28
|
+
* miss is that `reclaim` cannot see this id and the TTL backstop covers it instead. Containment is
|
|
29
|
+
* the same prepare step every artifact write uses; append (not atomic-replace) keeps racing
|
|
30
|
+
* participants' receipts intact.
|
|
30
31
|
*/
|
|
31
|
-
export declare function appendSandboxReceipt(root: PreparedOutputRoot, receipt: SandboxReceipt): Promise<
|
|
32
|
+
export declare function appendSandboxReceipt(root: PreparedOutputRoot, receipt: SandboxReceipt): Promise<boolean>;
|
|
32
33
|
/** Append one owner line; false when the write failed, which never fails the create. */
|
|
33
34
|
export declare function appendSandboxOwner(root: PreparedOutputRoot, owner: SandboxOwnerTags): Promise<boolean>;
|
|
34
35
|
/** The owner tags every sandbox carries; sandboxOwnerTags in src/run/sandbox-creates.ts. */
|