humanish 0.115.0 → 0.117.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +7 -6
- package/TELEMETRY.md +6 -0
- package/dist/actors/codex/app-server-ui.js +11 -4
- package/dist/actors/codex/restricted-participant-policy.d.ts +0 -2
- package/dist/actors/codex/restricted-participant-policy.js +4 -32
- package/dist/actors/codex/restricted-participant.d.ts +5 -0
- package/dist/actors/codex/restricted-participant.js +3 -2
- package/dist/actors/computer-use/actor.d.ts +4 -0
- package/dist/actors/computer-use/actor.js +2 -0
- package/dist/actors/computer-use/loop/actions.d.ts +4 -4
- package/dist/actors/computer-use/loop/actions.js +25 -18
- package/dist/actors/computer-use/loop/debrief.js +61 -27
- package/dist/actors/computer-use/loop/session.d.ts +3 -0
- package/dist/actors/computer-use/loop/session.js +9 -1
- package/dist/actors/computer-use/loop/trace.js +1 -0
- package/dist/actors/computer-use/loop/types.d.ts +16 -14
- package/dist/actors/computer-use/loop.d.ts +1 -1
- package/dist/actors/computer-use/loop.js +1 -1
- package/dist/actors/computer-use/wait.d.ts +74 -0
- package/dist/actors/computer-use/wait.js +100 -0
- package/dist/actors/contract.d.ts +14 -0
- package/dist/actors/scripted-browser/steps.js +6 -5
- package/dist/analysis/analysis-limits.d.ts +35 -0
- package/dist/analysis/analysis-limits.js +41 -0
- package/dist/analysis/validation.js +14 -21
- package/dist/browser-control/protocol.d.ts +86 -6
- package/dist/browser-control/protocol.js +13 -6
- package/dist/cli/commands/analyze.js +2 -1
- package/dist/cli/commands/notes.js +2 -1
- package/dist/cli/commands/run-signals.js +5 -1
- package/dist/cli/commands/runs.js +2 -1
- package/dist/cli/commands/setup.js +3 -3
- package/dist/cli/commands/study-format.d.ts +5 -2
- package/dist/cli/commands/study-format.js +29 -9
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-scripted.js +1 -1
- package/dist/cli/commands/tui.js +59 -17
- package/dist/cli/doctor.js +9 -2
- package/dist/cli/findings-text.d.ts +5 -0
- package/dist/cli/findings-text.js +169 -0
- package/dist/cli/findings.d.ts +0 -4
- package/dist/cli/findings.js +5 -167
- package/dist/cli/io.js +6 -1
- package/dist/cli/update-check.js +4 -1
- package/dist/comms/connections.js +4 -3
- package/dist/comms/setup.js +18 -12
- package/dist/evidence/redaction.js +9 -1
- package/dist/evidence/secret-scrub.d.ts +15 -5
- package/dist/evidence/secret-scrub.js +67 -9
- package/dist/feedback/draft.js +1 -1
- package/dist/feedback/export-bundle.js +18 -14
- package/dist/feedback/feedback.js +25 -7
- package/dist/guest/desktop-executor.js +3 -1
- package/dist/guest/desktop-keys.js +59 -5
- package/dist/keys/key-resolution.d.ts +9 -0
- package/dist/keys/key-resolution.js +60 -48
- package/dist/keys/key-status.d.ts +2 -0
- package/dist/keys/key-status.js +9 -16
- package/dist/observer/pinned-files.js +1 -1
- package/dist/observer/render.js +3 -2
- package/dist/observer-app.html +5 -5
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -4
- package/dist/routes/computer-use/bundle-parts.js +2 -2
- package/dist/routes/computer-use/bundle.d.ts +4 -5
- package/dist/routes/computer-use/bundle.js +1 -3
- package/dist/routes/computer-use/e2b-desktop/prepare.js +40 -31
- package/dist/routes/computer-use/fanout-records.js +10 -11
- package/dist/routes/computer-use/live-phase.d.ts +1 -1
- package/dist/routes/computer-use/live-phase.js +0 -1
- package/dist/routes/computer-use/local-vm.js +5 -2
- package/dist/routes/computer-use/participant-execution.d.ts +3 -2
- package/dist/routes/computer-use/participant-model.js +9 -0
- package/dist/routes/computer-use/participant-runs.d.ts +6 -2
- package/dist/routes/computer-use/participant-runs.js +9 -5
- package/dist/routes/computer-use/plan.d.ts +3 -0
- package/dist/routes/computer-use/plan.js +18 -13
- package/dist/routes/computer-use/rerun-selection.js +2 -2
- package/dist/routes/computer-use/result.js +3 -6
- package/dist/routes/computer-use/setup.d.ts +4 -6
- package/dist/routes/computer-use/setup.js +2 -6
- package/dist/routes/computer-use/single-bundle.d.ts +4 -5
- package/dist/routes/computer-use/single-bundle.js +22 -25
- package/dist/routes/computer-use/subject-projection.d.ts +4 -8
- package/dist/routes/computer-use/subject-projection.js +9 -31
- package/dist/routes/computer-use/types.d.ts +5 -42
- package/dist/routes/scripted/scenario.js +28 -12
- package/dist/routes/scripted/session-result.js +6 -3
- package/dist/routes/shared-world/bundle-records.js +2 -8
- package/dist/routes/shared-world/participant-specs.d.ts +0 -2
- package/dist/routes/shared-world/participant-specs.js +1 -20
- package/dist/routes/shared-world/plan.d.ts +3 -0
- package/dist/routes/shared-world/plan.js +25 -4
- package/dist/routes/shared-world/setup.js +2 -2
- package/dist/routes/terminal/ledger.d.ts +9 -9
- package/dist/routes/terminal/ledger.js +63 -40
- package/dist/routes/terminal/live-finish.js +7 -10
- package/dist/routes/terminal/live-sandbox.d.ts +1 -1
- package/dist/routes/terminal/live-sandbox.js +14 -11
- package/dist/routes/terminal/plan.d.ts +3 -0
- package/dist/routes/terminal/plan.js +8 -5
- package/dist/routes/terminal/sandbox.d.ts +4 -3
- package/dist/routes/terminal/sandbox.js +5 -5
- package/dist/routes/terminal/token-usage.d.ts +1 -2
- package/dist/routes/terminal/token-usage.js +4 -6
- package/dist/routes/terminal/types.d.ts +9 -1
- package/dist/run/bundle-shape.js +6 -1
- package/dist/run/bundle.d.ts +5 -3
- package/dist/run/contained-output.d.ts +53 -5
- package/dist/run/contained-output.js +141 -42
- package/dist/run/detail.d.ts +2 -1
- package/dist/run/detail.js +12 -7
- package/dist/run/escape-sequences.d.ts +11 -0
- package/dist/run/escape-sequences.js +93 -0
- package/dist/run/evidence-files.d.ts +8 -8
- package/dist/run/evidence-files.js +19 -23
- package/dist/run/locate.d.ts +32 -3
- package/dist/run/locate.js +45 -40
- package/dist/run/notes.d.ts +0 -8
- package/dist/run/notes.js +5 -51
- package/dist/run/outcomes.d.ts +24 -0
- package/dist/run/outcomes.js +19 -0
- package/dist/run/paths.d.ts +2 -0
- package/dist/run/paths.js +5 -1
- package/dist/run/preflight-receipts.js +13 -8
- package/dist/run/project.js +7 -4
- package/dist/run/reclaim-outcomes.d.ts +8 -6
- package/dist/run/reclaim-outcomes.js +10 -6
- package/dist/run/reclaim.d.ts +5 -0
- package/dist/run/reclaim.js +43 -16
- package/dist/run/run-clock.d.ts +50 -0
- package/dist/run/run-clock.js +66 -0
- package/dist/run/run.js +5 -2
- package/dist/run/sandbox-ids.d.ts +7 -2
- package/dist/run/sandbox-ids.js +27 -11
- package/dist/run/secrets.d.ts +2 -1
- package/dist/run/secrets.js +18 -22
- package/dist/run/stored-runs.js +14 -7
- package/dist/run/transient-comms-secrets.js +6 -17
- package/dist/run-study.js +22 -13
- package/dist/study/adapter-scorer-loader.js +6 -4
- package/dist/study/config.js +4 -1
- package/dist/study/discover.js +9 -6
- package/dist/study/init-paths.js +7 -5
- package/dist/study/keys.d.ts +2 -0
- package/dist/study/keys.js +2 -0
- package/dist/study/migrate/v2.js +2 -1
- package/dist/study/parse/actors.js +8 -0
- package/dist/study/persona-resolve.d.ts +2 -2
- package/dist/study/persona-resolve.js +9 -5
- package/dist/study/plan-participants.d.ts +4 -0
- package/dist/study/plan-participants.js +4 -0
- package/dist/study/plan-types.d.ts +2 -2
- package/dist/study/plan.d.ts +5 -3
- package/dist/study/plan.js +18 -9
- package/dist/study/preflight-probes.js +11 -3
- package/dist/study/preflight.js +9 -5
- package/dist/study/summary.d.ts +8 -2
- package/dist/study/summary.js +20 -22
- package/dist/study/types.d.ts +14 -0
- package/dist/study/validation.d.ts +5 -0
- package/dist/study/validation.js +8 -0
- package/dist/study/warnings.d.ts +19 -12
- package/dist/study/warnings.js +50 -17
- package/dist/subject/serve.d.ts +3 -0
- package/dist/subject/serve.js +4 -1
- package/dist/substrates/detached.js +14 -7
- package/dist/substrates/e2b/connection.d.ts +12 -0
- package/dist/substrates/e2b/connection.js +35 -0
- package/dist/substrates/e2b/desktop-executor.d.ts +1 -5
- package/dist/substrates/e2b/desktop-executor.js +45 -12
- package/dist/substrates/e2b/lifetime.d.ts +24 -4
- package/dist/substrates/e2b/lifetime.js +41 -4
- package/dist/substrates/e2b/sandbox.d.ts +4 -3
- package/dist/substrates/e2b/sandbox.js +13 -18
- package/dist/substrates/e2b/sdk.d.ts +20 -9
- package/dist/substrates/local/capacity.d.ts +18 -2
- package/dist/substrates/local/capacity.js +21 -2
- package/dist/substrates/local/runtime-host.js +21 -14
- package/dist/substrates/local/runtime.js +3 -11
- package/dist/tui/contract.d.ts +30 -5
- package/dist/tui/contract.js +4 -1
- package/dist/tui-app.js +150 -147
- package/dist/verify/artifacts.d.ts +9 -2
- package/dist/verify/artifacts.js +37 -13
- package/dist/verify/liveness.js +19 -5
- package/dist/verify/verify.js +54 -27
- package/docs/architecture/browser-control.md +10 -4
- package/docs/architecture/guest-desktop.md +7 -2
- package/docs/architecture/state-driven-executor.md +4 -2
- package/docs/contracts/run-bundle.md +18 -10
- package/docs/contracts/schemas.md +46 -29
- package/docs/contracts/study-analysis.md +10 -1
- package/docs/product/automatic-analysis.md +7 -1
- package/package.json +2 -2
|
@@ -4,48 +4,21 @@ import { COST_CATEGORIES } from "../../run/terminal-contract.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Build the spend ledger from the captured session. The null discipline:
|
|
6
6
|
* - The `provider` line is populated from the actor trace's tokenUsage.costUsd when the trace
|
|
7
|
-
* carries it (a measured value, incl. a measured 0).
|
|
8
|
-
*
|
|
7
|
+
* carries it (a measured value, incl. a measured 0). Otherwise its `usd` is null = not
|
|
8
|
+
* measured, and when the trace's `estimatedCost` prices the tokens, the line carries that
|
|
9
|
+
* price as `estimatedUsd`: the figure run.json's model-tokens line gives, from the same
|
|
10
|
+
* object. Tokens with no rate, and a run with no token count, say so in the note.
|
|
9
11
|
* - product/media/payment are `null` by default: core has no signal for those categories, and
|
|
10
12
|
* only a test can supply one, through `StudyDeps.costProbe`.
|
|
11
13
|
* `injectedLines` lets a test supply known spend for a category,
|
|
12
14
|
* exercising the fail-closed cap enforcement deterministically without a real billable run.
|
|
13
15
|
*/
|
|
14
16
|
export function buildCostLedger(args) {
|
|
15
|
-
const providerLine = typeof args.tokenCostUsd === "number"
|
|
16
|
-
? {
|
|
17
|
-
usd: args.tokenCostUsd,
|
|
18
|
-
source: "provider-token-usage",
|
|
19
|
-
note: `Provider spend metered from the actor trace tokenUsage.costUsd (${args.tokenCostUsd} USD).`,
|
|
20
|
-
}
|
|
21
|
-
: args.tokenUsage
|
|
22
|
-
? {
|
|
23
|
-
// Tokens counted, no rate to price them. This stays `usd: null` because a guessed
|
|
24
|
-
// dollar figure would be worse than none, but the note carries the measured fact so a
|
|
25
|
-
// reader never mistakes "no charge recorded" for "nothing was consumed".
|
|
26
|
-
usd: null,
|
|
27
|
-
source: "unpriced-token-usage",
|
|
28
|
-
note: `Provider spend unpriced: the run consumed ${describeTokenUsage(args.tokenUsage)}, ` +
|
|
29
|
-
"but the terminal participant records the model as `codex` and humanish has no rate " +
|
|
30
|
-
"for it, so no dollar figure is given. The token count is measured; the price is " +
|
|
31
|
-
"not known. Recorded as null, never guessed as 0.",
|
|
32
|
-
}
|
|
33
|
-
: {
|
|
34
|
-
usd: null,
|
|
35
|
-
source: "unmeasured",
|
|
36
|
-
note: "Provider spend not measured: the actor trace carried no tokenUsage.costUsd in this run, so it is recorded as null and not guessed as 0.",
|
|
37
|
-
};
|
|
38
|
-
const unmeasured = (category) => ({
|
|
39
|
-
usd: null,
|
|
40
|
-
count: null,
|
|
41
|
-
source: "unmeasured",
|
|
42
|
-
note: `${category} spend not measured: humanish has no ${category} spend signal, and a study cannot supply one. Recorded as null, never guessed as 0.`,
|
|
43
|
-
});
|
|
44
17
|
const lines = {
|
|
45
18
|
product: args.injectedLines?.product ?? unmeasured("product"),
|
|
46
19
|
media: args.injectedLines?.media ?? unmeasured("media"),
|
|
47
20
|
payment: args.injectedLines?.payment ?? unmeasured("payment"),
|
|
48
|
-
provider: args.injectedLines?.provider ?? providerLine,
|
|
21
|
+
provider: args.injectedLines?.provider ?? providerLine(args.trace),
|
|
49
22
|
};
|
|
50
23
|
// knownTotalUsd sums only the non-null lines. A null line contributes nothing and is never
|
|
51
24
|
// coerced to 0 (that would let an unmeasured category masquerade as a measured zero).
|
|
@@ -68,6 +41,51 @@ export function buildCostLedger(args) {
|
|
|
68
41
|
fullyMeasured,
|
|
69
42
|
};
|
|
70
43
|
}
|
|
44
|
+
function unmeasured(category) {
|
|
45
|
+
return {
|
|
46
|
+
usd: null,
|
|
47
|
+
count: null,
|
|
48
|
+
source: "unmeasured",
|
|
49
|
+
note: `${category} spend not measured: humanish has no ${category} spend signal, and a study cannot supply one. Recorded as null, never guessed as 0.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The participant's model tokens: a measured charge, an estimate, unpriced, or not counted. */
|
|
53
|
+
function providerLine(trace) {
|
|
54
|
+
const { tokenUsage, estimatedCost } = trace;
|
|
55
|
+
if (typeof tokenUsage?.costUsd === "number")
|
|
56
|
+
return {
|
|
57
|
+
usd: tokenUsage.costUsd,
|
|
58
|
+
source: "provider-token-usage",
|
|
59
|
+
note: `Provider spend metered from the actor trace tokenUsage.costUsd (${tokenUsage.costUsd} USD).`,
|
|
60
|
+
};
|
|
61
|
+
if (tokenUsage === undefined)
|
|
62
|
+
return {
|
|
63
|
+
usd: null,
|
|
64
|
+
source: "unmeasured",
|
|
65
|
+
note: "Provider spend not measured: the actor trace carried no tokenUsage.costUsd in this run, so it is recorded as null and not guessed as 0.",
|
|
66
|
+
};
|
|
67
|
+
if (estimatedCost !== undefined && estimatedCost.estimatedCostUsd !== null)
|
|
68
|
+
return {
|
|
69
|
+
usd: null,
|
|
70
|
+
estimatedUsd: estimatedCost.estimatedCostUsd,
|
|
71
|
+
source: "estimated-token-usage",
|
|
72
|
+
note: `Provider spend estimated at ${estimatedCost.estimatedCostUsd} USD: the run consumed ` +
|
|
73
|
+
`${describeTokenUsage(tokenUsage)}, priced at ${estimatedCost.modelId ?? "the participant model's"} ` +
|
|
74
|
+
`rates as of ${estimatedCost.ratesAsOf}, as run.json's cost summary prices them. No provider ` +
|
|
75
|
+
"charge was measured, so usd stays null and caps.maxUsd does not count the estimate.",
|
|
76
|
+
};
|
|
77
|
+
// Tokens counted, no rate to price them. A guessed dollar figure would be worse than none, but
|
|
78
|
+
// the note carries the measured fact so a reader never mistakes "no charge recorded" for
|
|
79
|
+
// "nothing was consumed".
|
|
80
|
+
return {
|
|
81
|
+
usd: null,
|
|
82
|
+
source: "unpriced-token-usage",
|
|
83
|
+
note: `Provider spend unpriced: the run consumed ${describeTokenUsage(tokenUsage)}, but humanish ` +
|
|
84
|
+
`has no rate for ${estimatedCost?.modelId === undefined ? "the participant's model" : `the model ${estimatedCost.modelId}`}, ` +
|
|
85
|
+
"so no dollar figure is given. The token count is measured; the price is not known. " +
|
|
86
|
+
"Recorded as null, never guessed as 0.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
71
89
|
/** True when no cost line carries a dollar value, so a no-spend proof has nothing to stand on. */
|
|
72
90
|
export function noSpendLineMeasured(proof) {
|
|
73
91
|
return proof.knownZeroLines.length + proof.knownNonZeroLines.length === 0;
|
|
@@ -80,19 +98,24 @@ export function noSpendNotEstablished(maxUsd) {
|
|
|
80
98
|
return `No-spend proof not established for maxUsd=${maxUsd}: no spend line was measured.`;
|
|
81
99
|
}
|
|
82
100
|
/**
|
|
83
|
-
* What the ledger measured, in words: the dollar lines it knows, provider tokens it counted
|
|
84
|
-
*
|
|
85
|
-
* an all-null ledger for a proven $0.
|
|
101
|
+
* What the ledger measured, in words: the dollar lines it knows, the provider tokens it counted
|
|
102
|
+
* with their estimate or as unpriced, and the lines it has no signal for. A reader of a maxUsd=0
|
|
103
|
+
* run must not take an all-null ledger for a proven $0.
|
|
86
104
|
*/
|
|
87
105
|
export function describeMeasuredSpend(ledger, tokenUsage) {
|
|
88
106
|
const measured = COST_CATEGORIES.filter((c) => ledger.lines[c].usd !== null).map((c) => `${c} ${ledger.lines[c].usd} USD`);
|
|
89
|
-
const
|
|
90
|
-
const
|
|
107
|
+
const provider = ledger.lines.provider;
|
|
108
|
+
const countedProvider = provider.usd === null &&
|
|
109
|
+
(provider.source === "unpriced-token-usage" || provider.source === "estimated-token-usage");
|
|
110
|
+
const unmeasured = COST_CATEGORIES.filter((c) => ledger.lines[c].usd === null && !(c === "provider" && countedProvider));
|
|
111
|
+
const counts = tokenUsage === undefined ? "" : ` (${describeTokenUsage(tokenUsage)})`;
|
|
91
112
|
return [
|
|
92
113
|
measured.length > 0 ? `Measured: ${measured.join(", ")}.` : "",
|
|
93
|
-
|
|
94
|
-
?
|
|
95
|
-
:
|
|
114
|
+
!countedProvider
|
|
115
|
+
? ""
|
|
116
|
+
: provider.estimatedUsd === undefined
|
|
117
|
+
? `Provider tokens were consumed${counts} and are unpriced, not zero.`
|
|
118
|
+
: `Provider tokens${counts} are estimated at ${provider.estimatedUsd} USD, the participant's model cost, which caps.maxUsd does not count.`,
|
|
96
119
|
unmeasured.length > 0 ? `Not measured (null, not claimed zero): ${unmeasured.join(", ")}.` : "",
|
|
97
120
|
]
|
|
98
121
|
.filter((part) => part.length > 0)
|
|
@@ -81,17 +81,13 @@ async function settleLiveLedgers(inputs, trace, normalizedTranscript) {
|
|
|
81
81
|
const { recordLifecycle, lifecycle, commandLog, interventions, terminalEvents } = inputs.recorder;
|
|
82
82
|
// --- Spend ledger + no-spend proof + full caps enforcement (fail-closed). ---
|
|
83
83
|
// The cost ledger is derived, with the null discipline: provider spend from the trace's
|
|
84
|
-
// tokenUsage.costUsd when present
|
|
85
|
-
// default (core has no signal). The costProbe hook lets tests
|
|
86
|
-
// spend to exercise the fail-closed cap without a real billable run.
|
|
84
|
+
// tokenUsage.costUsd when present, else null with the trace's token estimate beside it, and
|
|
85
|
+
// product/media/payment null by default (core has no signal). The costProbe hook lets tests
|
|
86
|
+
// inject known spend to exercise the fail-closed cap without a real billable run.
|
|
87
87
|
const injectedLines = costProbe?.(trace.tokenUsage?.costUsd === undefined ? {} : { tokenCostUsd: trace.tokenUsage.costUsd });
|
|
88
88
|
if (costProbe)
|
|
89
89
|
await validatePreparedRunArtifactPaths(runPaths);
|
|
90
|
-
const cost = buildCostLedger({
|
|
91
|
-
...(trace.tokenUsage?.costUsd === undefined ? {} : { tokenCostUsd: trace.tokenUsage.costUsd }),
|
|
92
|
-
...(trace.tokenUsage === undefined ? {} : { tokenUsage: trace.tokenUsage }),
|
|
93
|
-
...(injectedLines ? { injectedLines } : {}),
|
|
94
|
-
});
|
|
90
|
+
const cost = buildCostLedger({ trace, ...(injectedLines ? { injectedLines } : {}) });
|
|
95
91
|
const noSpendProof = buildNoSpendProof(cost, maxUsd ?? null, trace.tokenUsage);
|
|
96
92
|
const proofVerdict = !noSpendProof.satisfied
|
|
97
93
|
? `No-spend proof not satisfied for maxUsd=${maxUsd ?? "null"}.`
|
|
@@ -138,8 +134,9 @@ export async function finishLiveTerminalSession(inputs) {
|
|
|
138
134
|
const participant = terminalParticipantFacts(trace, session.error);
|
|
139
135
|
const judgment = judgeTerminal({ dryRun: false, participant });
|
|
140
136
|
// The run cost summary, as the computer-use route records it: the sandbox's compute time from
|
|
141
|
-
// its span and observed size, and the participant's tokens
|
|
142
|
-
// of the cap ledger above, whose lines sum
|
|
137
|
+
// its span and observed size, and the participant's tokens at trace.estimatedCost, the price the
|
|
138
|
+
// ledger's provider line also carries. It is not part of the cap ledger above, whose lines sum
|
|
139
|
+
// against caps.maxUsd.
|
|
143
140
|
const desktops = session.runCostDesktops();
|
|
144
141
|
const runCost = buildRunCostSummary({
|
|
145
142
|
participants: [{ trace }],
|
|
@@ -11,6 +11,7 @@ import { extractLocalActorVerdict, normalizeLocalActorTranscript, } from "../../
|
|
|
11
11
|
import { NODE_BOOTSTRAP_COMMAND, NODE_BOOTSTRAP_TIMEOUT_MS } from "../../subject/node-bootstrap.js";
|
|
12
12
|
import { observeDesktopResources, } from "../../substrates/e2b/desktop-resources.js";
|
|
13
13
|
import { acquireE2BShellSandbox } from "../../substrates/e2b/sandbox.js";
|
|
14
|
+
import { e2bAccount } from "../../substrates/e2b/connection.js";
|
|
14
15
|
import { E2BDesktopStartupError, loadE2BDesktopModule, } from "../../substrates/e2b/sdk.js";
|
|
15
16
|
import { shellQuote } from "../../substrates/shell.js";
|
|
16
17
|
import { buildOpenAiEgressNetwork } from "./runtime-auth.js";
|
|
@@ -36,7 +37,7 @@ export class LiveTerminalSandbox {
|
|
|
36
37
|
wallClockMs;
|
|
37
38
|
sandboxTimeoutMs;
|
|
38
39
|
sandbox;
|
|
39
|
-
|
|
40
|
+
account;
|
|
40
41
|
allocation;
|
|
41
42
|
startupCleanup;
|
|
42
43
|
startupCleanupDetail;
|
|
@@ -60,7 +61,6 @@ export class LiveTerminalSandbox {
|
|
|
60
61
|
// Declared egress allowlist, or undefined for the historical unrestricted default.
|
|
61
62
|
const egressAllow = plan.egressAllow;
|
|
62
63
|
const sandboxModule = await (deps.desktopModule ?? loadE2BDesktopModule)();
|
|
63
|
-
this.module = sandboxModule;
|
|
64
64
|
await validatePreparedRunArtifactPaths(runPaths);
|
|
65
65
|
// No sandbox-global env in either mode. In openai-egress, only this host-side SDK request
|
|
66
66
|
// carries the real runtime key; participant commands receive an inert placeholder. The proxy
|
|
@@ -71,16 +71,19 @@ export class LiveTerminalSandbox {
|
|
|
71
71
|
const network = runtimeEnv.mode === "openai-egress"
|
|
72
72
|
? buildOpenAiEgressNetwork(runtimeEnv.keyValue, routing)
|
|
73
73
|
: routing;
|
|
74
|
+
const options = {
|
|
75
|
+
apiKey: e2bApiKey,
|
|
76
|
+
requestTimeoutMs,
|
|
77
|
+
timeoutMs: sandboxTimeoutMs,
|
|
78
|
+
metadata,
|
|
79
|
+
...(network === undefined ? {} : { network }),
|
|
80
|
+
lifecycle: { onTimeout: "kill" },
|
|
81
|
+
};
|
|
82
|
+
// Teardown's check by id reaches the account the create used, whatever process.env names.
|
|
83
|
+
this.account = e2bAccount(sandboxModule, options);
|
|
74
84
|
const acquired = await acquireE2BShellSandbox({
|
|
75
85
|
module: sandboxModule,
|
|
76
|
-
options
|
|
77
|
-
apiKey: e2bApiKey,
|
|
78
|
-
requestTimeoutMs,
|
|
79
|
-
timeoutMs: sandboxTimeoutMs,
|
|
80
|
-
metadata,
|
|
81
|
-
...(network === undefined ? {} : { network }),
|
|
82
|
-
lifecycle: { onTimeout: "kill" },
|
|
83
|
-
},
|
|
86
|
+
options,
|
|
84
87
|
retry: {
|
|
85
88
|
// A failed first attempt may have allocated a sandbox whose id never reached this run;
|
|
86
89
|
// its own kill-on-timeout reclaims it.
|
|
@@ -418,7 +421,7 @@ export class LiveTerminalSandbox {
|
|
|
418
421
|
// --- Proven cleanup, by exact id, never Sandbox.list. ---
|
|
419
422
|
this.cleanup = await teardownSandbox({
|
|
420
423
|
allocation: this.allocation,
|
|
421
|
-
|
|
424
|
+
account: this.account,
|
|
422
425
|
...(this.startupCleanup === undefined ? {} : { startupCleanup: this.startupCleanup }),
|
|
423
426
|
startupCleanupDetail: this.startupCleanupDetail,
|
|
424
427
|
requestTimeoutMs,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RoutePlanResult, RouteRefusal, TerminalPlan } from "../../study/plan-types.js";
|
|
2
2
|
import type { StudyConfig } from "../../study/types.js";
|
|
3
|
+
import { type SandboxCeiling } from "../../substrates/e2b/lifetime.js";
|
|
3
4
|
import { type TerminalProductStudyResult } from "./types.js";
|
|
4
5
|
/** The error a terminal study returns before a run starts. `actor` names the registered actor. */
|
|
5
6
|
export interface TerminalRefusal extends RouteRefusal<"terminal", NonNullable<TerminalProductStudyResult["error"]>["code"]> {
|
|
@@ -14,4 +15,6 @@ export declare function planTerminalStudy(config: StudyConfig, input: {
|
|
|
14
15
|
readonly dryRun: boolean;
|
|
15
16
|
/** A test's costProbe can measure spend lines, so a positive maxUsd can trip. */
|
|
16
17
|
readonly hasCostProbe?: boolean;
|
|
18
|
+
/** The longest sandbox lifetime the operator's E2B plan allows (sandboxCeiling). */
|
|
19
|
+
readonly sandboxCeiling: SandboxCeiling;
|
|
17
20
|
}): TerminalPlanResult;
|
|
@@ -8,7 +8,7 @@ import { actorRegistry, isTerminalActorDescriptor } from "../../actors/registry.
|
|
|
8
8
|
import { isNonEmpty, planBase } from "../../study/plan-base.js";
|
|
9
9
|
import { studyUrlCredentialReason } from "../../study/url-credentials.js";
|
|
10
10
|
import { desktopMediaValidationReason, taskProtocolValidationReason, } from "../../study/validation.js";
|
|
11
|
-
import {
|
|
11
|
+
import { ceilingAdvice } from "../../substrates/e2b/lifetime.js";
|
|
12
12
|
import { NODE_BOOTSTRAP_TIMEOUT_MS } from "../../subject/node-bootstrap.js";
|
|
13
13
|
import { terminalSandboxTimeoutMs } from "./lifetime.js";
|
|
14
14
|
import { isExactRuntimeVersion, TERMINAL_RUNTIME_VERSION_TIMEOUT_MS } from "./runtime.js";
|
|
@@ -105,13 +105,16 @@ export function planTerminalStudy(config, input) {
|
|
|
105
105
|
if (maxUsd > 0 && input.hasCostProbe !== true)
|
|
106
106
|
return refuse("HUMANISH_TERMINAL_UNPRICED_CAP", `caps.maxUsd=${maxUsd} cannot be enforced: Codex reports no provider cost for the participant (its token cost is only estimated after the run) and no product, media or payment spend is measured, so a positive dollar cap can never trip. Set caps.maxUsd to 0 and bound the run with caps.maxMinutes, the codex command's wall-clock kill. No sandbox was created and the runtime key was not used.`, descriptor.id);
|
|
107
107
|
// The sandbox's timeout covers the steps before the codex command, maxMinutes and the teardown
|
|
108
|
-
// buffer, and E2B refuses a sandbox
|
|
109
|
-
// after the plan prints, from a provider 400 that names neither knob.
|
|
108
|
+
// buffer, and E2B refuses a sandbox past the plan's ceiling. Refuse here, with the arithmetic,
|
|
109
|
+
// rather than after the plan prints, from a provider 400 that names neither knob.
|
|
110
|
+
const ceiling = input.sandboxCeiling;
|
|
111
|
+
if (!ceiling.ok)
|
|
112
|
+
return refuse("HUMANISH_TERMINAL_CAPS_INVALID", ceiling.message, descriptor.id);
|
|
110
113
|
const productInstall = shared.product.install !== undefined;
|
|
111
114
|
const sandboxTimeoutMs = terminalSandboxTimeoutMs({ maxMinutes, productInstall });
|
|
112
|
-
if (sandboxTimeoutMs >
|
|
115
|
+
if (sandboxTimeoutMs > ceiling.ms) {
|
|
113
116
|
const headroomMinutes = (sandboxTimeoutMs - maxMinutes * 60_000) / 60_000;
|
|
114
|
-
return refuse("HUMANISH_TERMINAL_CAPS_INVALID", `caps.maxMinutes ${maxMinutes} derives a ${sandboxTimeoutMs / 60_000}m sandbox deadline, and a sandbox may not live longer than ${
|
|
117
|
+
return refuse("HUMANISH_TERMINAL_CAPS_INVALID", `caps.maxMinutes ${maxMinutes} derives a ${sandboxTimeoutMs / 60_000}m sandbox deadline, and a sandbox may not live longer than ${ceiling.ms / 60_000}m. The deadline is maxMinutes plus ${headroomMinutes}m: the Node bootstrap (${NODE_BOOTSTRAP_TIMEOUT_MS / 60_000}m), the runtime version check (${TERMINAL_RUNTIME_VERSION_TIMEOUT_MS / 60_000}m)${productInstall ? `, the product setup (${PRODUCT_SETUP_TIMEOUT_MS / 60_000}m)` : ""} and the teardown buffer (${TERMINAL_SANDBOX_TIMEOUT_BUFFER_MS / 60_000}m). Lower caps.maxMinutes to at most ${ceiling.ms / 60_000 - headroomMinutes}.${ceilingAdvice(sandboxTimeoutMs)} No sandbox was created and the runtime key was not used.`, descriptor.id);
|
|
115
118
|
}
|
|
116
119
|
return {
|
|
117
120
|
ok: true,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OwnedDesktopAllocation } from "../../substrates/desktop-session.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { E2BAccount } from "../../substrates/e2b/connection.js";
|
|
3
|
+
import { E2BDesktopStartupError } from "../../substrates/e2b/sdk.js";
|
|
3
4
|
import type { TerminalLedgers } from "./types.js";
|
|
4
5
|
/**
|
|
5
6
|
* Tear the sandbox down and prove it by exact id, never by Sandbox.list (humanish must never
|
|
@@ -14,8 +15,8 @@ import type { TerminalLedgers } from "./types.js";
|
|
|
14
15
|
export declare function teardownSandbox(args: {
|
|
15
16
|
/** The allocation from acquisition; its release kills the exact id it captured. */
|
|
16
17
|
allocation: OwnedDesktopAllocation | undefined;
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/** The account the create used, for the Sandbox.getInfo(id) re-check only. */
|
|
19
|
+
account: E2BAccount | undefined;
|
|
19
20
|
startupCleanup?: E2BDesktopStartupError["cleanup"];
|
|
20
21
|
/** Why the startup guard's cleanup is unconfirmed, when it knows. */
|
|
21
22
|
startupCleanupDetail?: string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toErrorMessage } from "../../evidence/redaction.js";
|
|
2
|
-
import { E2BDesktopStartupError, isSandboxNotFoundError
|
|
2
|
+
import { E2BDesktopStartupError, isSandboxNotFoundError } from "../../substrates/e2b/sdk.js";
|
|
3
3
|
import { releaseUnavailableDetail } from "../../substrates/e2b/sandbox.js";
|
|
4
4
|
/**
|
|
5
5
|
* Tear the sandbox down and prove it by exact id, never by Sandbox.list (humanish must never
|
|
@@ -12,8 +12,8 @@ import { releaseUnavailableDetail } from "../../substrates/e2b/sandbox.js";
|
|
|
12
12
|
* fails closed on an unproven teardown.
|
|
13
13
|
*/
|
|
14
14
|
export async function teardownSandbox(args) {
|
|
15
|
-
const { allocation,
|
|
16
|
-
if (allocation === undefined ||
|
|
15
|
+
const { allocation, account, startupCleanup, startupCleanupDetail, requestTimeoutMs, sanitize, recordLifecycle, warnings, } = args;
|
|
16
|
+
if (allocation === undefined || account === undefined) {
|
|
17
17
|
// create() can reject after its constructor acquired a handle. The default loader retains
|
|
18
18
|
// that authority and reclaims it before rejecting; the route itself never receives its ID.
|
|
19
19
|
if (startupCleanup === "killed" || startupCleanup === "already_gone") {
|
|
@@ -58,7 +58,7 @@ export async function teardownSandbox(args) {
|
|
|
58
58
|
const killNote = released.reason === "terminated"
|
|
59
59
|
? "kill(id) returned true (found and killed)"
|
|
60
60
|
: "kill(id) found the exact sandbox already gone (404)";
|
|
61
|
-
if (
|
|
61
|
+
if (account.getInfo === undefined) {
|
|
62
62
|
recordLifecycle("terminal-lab.cleanup.killed", `Sandbox ${sandboxId} reclaimed: ${killNote}; the installed SDK has no getInfo(id) to re-verify, so kill(id)'s own result is the proof.`);
|
|
63
63
|
return {
|
|
64
64
|
killed: true,
|
|
@@ -67,7 +67,7 @@ export async function teardownSandbox(args) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
try {
|
|
70
|
-
const info = await
|
|
70
|
+
const info = await account.getInfo(sandboxId, { requestTimeoutMs });
|
|
71
71
|
const state = info.state ?? "unknown";
|
|
72
72
|
recordLifecycle("terminal-lab.cleanup.unconfirmed", `Sandbox ${sandboxId} ${killNote}, but getInfo(id) still reports state=${state} (not confirmed reclaimed by id).`);
|
|
73
73
|
return {
|
|
@@ -13,6 +13,5 @@ export declare function terminalUsageRecords(lines: string): string[];
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function parseTerminalTokenUsage(transcript: string): ActorTokenUsage | undefined;
|
|
15
15
|
/** Human-readable token statement for the cost ledger and the no-spend proof. Reports what was
|
|
16
|
-
* counted
|
|
17
|
-
* "nothing was consumed". */
|
|
16
|
+
* counted, so a reader never reads "no charge recorded" as "nothing was consumed". */
|
|
18
17
|
export declare function describeTokenUsage(usage: ActorTokenUsage): string;
|
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
// {"type":"turn.completed","usage":{"input_tokens":201536,"cached_input_tokens":170558,
|
|
10
10
|
// "cache_write_input_tokens":30951,"output_tokens":2283,"reasoning_output_tokens":902}}
|
|
11
11
|
//
|
|
12
|
-
// So the route
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// tokens-known-rate-unknown rather than a guessed dollar figure.
|
|
12
|
+
// So the route records a measured token fact whether or not a rate prices it. Rates stay out of this
|
|
13
|
+
// module: the route prices these counts once, from the model it passed to Codex, with
|
|
14
|
+
// src/run/pricing.ts (live-finish.ts), and the ledger and run.json both read that estimate.
|
|
16
15
|
// Bounded to a single line on purpose. `[^}]*` would run past a truncated record's missing brace
|
|
17
16
|
// and swallow the next, valid record's body, silently dropping a real turn from the count.
|
|
18
17
|
const USAGE_RE = /"type"\s*:\s*"turn\.completed"\s*,\s*"usage"\s*:\s*(\{[^}\n]*\})/g;
|
|
@@ -81,8 +80,7 @@ export function parseTerminalTokenUsage(transcript) {
|
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
/** Human-readable token statement for the cost ledger and the no-spend proof. Reports what was
|
|
84
|
-
* counted
|
|
85
|
-
* "nothing was consumed". */
|
|
83
|
+
* counted, so a reader never reads "no charge recorded" as "nothing was consumed". */
|
|
86
84
|
export function describeTokenUsage(usage) {
|
|
87
85
|
const parts = [];
|
|
88
86
|
if (usage.input !== undefined)
|
|
@@ -232,6 +232,11 @@ export interface InterventionRecord {
|
|
|
232
232
|
export interface CostLine {
|
|
233
233
|
/** known zero (0) | not measured (null). The key is always present when the line is applicable. */
|
|
234
234
|
usd: number | null;
|
|
235
|
+
/** The provider line's price for its counted tokens, from the trace's `estimatedCost` (the
|
|
236
|
+
* figure run.json's model-tokens line gives), when no charge was measured. An estimate from a
|
|
237
|
+
* rate table: `usd` stays null, and neither `knownTotalUsd` nor caps.maxUsd counts it. Present
|
|
238
|
+
* only with source `estimated-token-usage`. */
|
|
239
|
+
estimatedUsd?: number;
|
|
235
240
|
/** Optional billable-unit count, same discipline: a known count, or null = not measured. */
|
|
236
241
|
count?: number | null;
|
|
237
242
|
/** How this line's value was established (provenance for the verifier + the human reviewer). */
|
|
@@ -239,7 +244,10 @@ export interface CostLine {
|
|
|
239
244
|
/** Tokens were counted but no rate could price them, so `usd` stays null while the note
|
|
240
245
|
* carries the measured token totals. Distinct from "unmeasured", which means no
|
|
241
246
|
* signal at all. */
|
|
242
|
-
| "unpriced-token-usage"
|
|
247
|
+
| "unpriced-token-usage"
|
|
248
|
+
/** Tokens were counted and priced from a rate table: `estimatedUsd` holds the estimate and
|
|
249
|
+
* `usd` stays null, since no charge was measured. */
|
|
250
|
+
| "estimated-token-usage";
|
|
243
251
|
/** A short, public-safe note (never a secret value). */
|
|
244
252
|
note: string;
|
|
245
253
|
}
|
package/dist/run/bundle-shape.js
CHANGED
|
@@ -213,10 +213,15 @@ export const ARCHIVE_SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
|
213
213
|
function isRunSubjectProvenance(value) {
|
|
214
214
|
if (!isRecord(value))
|
|
215
215
|
return false;
|
|
216
|
-
if (value.source !== "clone" &&
|
|
216
|
+
if (value.source !== "clone" &&
|
|
217
|
+
value.source !== "app-url" &&
|
|
218
|
+
value.source !== "local-tree" &&
|
|
219
|
+
value.source !== "desktop-cli")
|
|
217
220
|
return false;
|
|
218
221
|
if (value.repo !== undefined && typeof value.repo !== "string")
|
|
219
222
|
return false;
|
|
223
|
+
if (value.product !== undefined && typeof value.product !== "string")
|
|
224
|
+
return false;
|
|
220
225
|
if (value.commit !== undefined && typeof value.commit !== "string")
|
|
221
226
|
return false;
|
|
222
227
|
if (value.archiveSha256 !== undefined &&
|
package/dist/run/bundle.d.ts
CHANGED
|
@@ -158,9 +158,11 @@ export interface RunSubjectStateStepRecord {
|
|
|
158
158
|
* pre-existing bundles).
|
|
159
159
|
*/
|
|
160
160
|
export interface RunSubjectProvenance {
|
|
161
|
-
source: "clone" | "app-url" | "local-tree";
|
|
161
|
+
source: "clone" | "app-url" | "local-tree" | "desktop-cli";
|
|
162
162
|
/** Clone-route only. Honors policies.redactRepos exactly as the provenance event does. */
|
|
163
163
|
repo?: string;
|
|
164
|
+
/** Desktop-cli only: the product the study declares in `subject.product.name`. */
|
|
165
|
+
product?: string;
|
|
164
166
|
/** Clone-route: the cloned commit SHA. Local-tree route: the host-side HEAD at pack time,
|
|
165
167
|
* when the packed root was a git work tree. */
|
|
166
168
|
commit?: string;
|
|
@@ -184,8 +186,8 @@ export interface RunSubjectProvenance {
|
|
|
184
186
|
* an external DB is still unpinned overall).
|
|
185
187
|
* declared-not-run: steps declared but not (all) executed ok: dry-run contract bundles
|
|
186
188
|
* and failed live provisioning.
|
|
187
|
-
* undeclared: no subject.state block (stateless apps, app-url subjects),
|
|
188
|
-
* "absence declared" marker that provenance requires when there is none.
|
|
189
|
+
* undeclared: no subject.state block (stateless apps, app-url and desktop-cli subjects),
|
|
190
|
+
* the explicit "absence declared" marker that provenance requires when there is none.
|
|
189
191
|
* external-public: an operator-declared, operator-owned public deployment used
|
|
190
192
|
* directly as the shared plane; humanish neither provisioned nor seeded it (no getHost, no
|
|
191
193
|
* clone, no in-sandbox filesystem). It is not "seeded" (nothing was seeded) and not "unpinned" (this is
|
|
@@ -46,13 +46,61 @@ export declare function writeContainedOutputFile(rootInput: PreparedOutputRoot,
|
|
|
46
46
|
* writeContainedOutputFile renames a finished file into place instead, replacing what is there.
|
|
47
47
|
*/
|
|
48
48
|
export declare function createContainedOutputFile(rootInput: PreparedOutputRoot, relativePath: string, data: string): Promise<void>;
|
|
49
|
-
/** Read one regular file only when both lexical and physical paths stay in root. */
|
|
50
|
-
export declare function readContainedRegularFile(rootInput: PreparedOutputRoot, relativePath: string): Promise<Buffer | null>;
|
|
51
49
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
50
|
+
* A file in a run directory or a preflight journal. verify reads every run file whole to scan it,
|
|
51
|
+
* and a frame whole to check it, so this covers the largest screenshot src/evidence/image.ts
|
|
52
|
+
* accepts (32 MiB). Every other reader of a run file uses the same limit, so no command reads a
|
|
53
|
+
* run file that verify could not scan.
|
|
54
54
|
*/
|
|
55
|
-
export declare
|
|
55
|
+
export declare const RUN_ARTIFACT_MAX_BYTES: number;
|
|
56
|
+
/** `.humanish/runs/latest.json`: a run id and a path. */
|
|
57
|
+
export declare const LATEST_POINTER_MAX_BYTES: number;
|
|
58
|
+
/** A file a person writes in the project: study, persona and scenario YAML, package.json, agent instructions. */
|
|
59
|
+
export declare const PROJECT_FILE_MAX_BYTES: number;
|
|
60
|
+
/**
|
|
61
|
+
* Why a contained read did not read a file that is there. `too-large`: it holds more than the
|
|
62
|
+
* limit. `changed`: it, its folder or the root changed while it was opened or read, which
|
|
63
|
+
* includes growing past the limit. `directory`: a folder is at the path. `not-regular`: a link, a
|
|
64
|
+
* hard link or a special file. `unsafe-path`: the path leaves the root or passes through a link.
|
|
65
|
+
* `unreadable`: the system refused the open or the read.
|
|
66
|
+
*/
|
|
67
|
+
export type ContainedRefusalReason = "too-large" | "changed" | "directory" | "not-regular" | "unsafe-path" | "unreadable";
|
|
68
|
+
/** A file that is there and was not read: why, and the most the read would have taken. */
|
|
69
|
+
export interface ContainedRefusal {
|
|
70
|
+
readonly status: "refused";
|
|
71
|
+
readonly reason: ContainedRefusalReason;
|
|
72
|
+
readonly limit: number;
|
|
73
|
+
}
|
|
74
|
+
/** What a contained read found: the bytes, nothing at the path, or a file it refused. */
|
|
75
|
+
export type ContainedRead = {
|
|
76
|
+
readonly status: "read";
|
|
77
|
+
readonly bytes: Buffer;
|
|
78
|
+
} | {
|
|
79
|
+
readonly status: "missing";
|
|
80
|
+
} | ContainedRefusal;
|
|
81
|
+
/** `relativePath` and why it was refused, as a clause: "x.yaml is larger than 4194304 bytes, ...". */
|
|
82
|
+
export declare function refusalText(relativePath: string, refusal: ContainedRefusal): string;
|
|
83
|
+
/** A refused contained read, for a caller that stops on one. The message names the file. */
|
|
84
|
+
export declare class ContainedReadRefusedError extends Error {
|
|
85
|
+
readonly relativePath: string;
|
|
86
|
+
readonly refusal: ContainedRefusal;
|
|
87
|
+
constructor(relativePath: string, refusal: ContainedRefusal);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read one regular file only when both lexical and physical paths stay in root and it holds at
|
|
91
|
+
* most `maxBytes`. A larger file is refused without being read, and one that grows past
|
|
92
|
+
* `maxBytes` while it is read is refused after at most `maxBytes + 1` bytes. `missing` means
|
|
93
|
+
* nothing is at the path, or a folder on it is missing.
|
|
94
|
+
*/
|
|
95
|
+
export declare function readContainedRegularFile(rootInput: PreparedOutputRoot, relativePath: string, maxBytes: number): Promise<ContainedRead>;
|
|
96
|
+
/**
|
|
97
|
+
* At most `maxBytes` from the start of an opened file, or null when it holds more. It reads by
|
|
98
|
+
* position, in chunks of up to 64 KiB, and stops one byte past the limit, so a file that grew
|
|
99
|
+
* after it was opened is never read whole.
|
|
100
|
+
*/
|
|
101
|
+
export declare function readOpenedAtMost(handle: FileHandle, maxBytes: number): Promise<Buffer | null>;
|
|
56
102
|
/** The caller owns this checked descriptor and must close it after reading/streaming. */
|
|
57
103
|
export declare function openContainedRegularFile(rootInput: PreparedOutputRoot, relativePath: string): Promise<FileHandle | null>;
|
|
58
104
|
export declare function assertSafeOutputPathSegment(value: string, label?: string): void;
|
|
105
|
+
/** A non-empty relative path with no empty, `.` or `..` segment: the names a contained read accepts. */
|
|
106
|
+
export declare function isSafeRelativeFilePath(value: string): boolean;
|