humanish 0.111.3 → 0.112.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 +33 -0
- package/dist/actors/computer-use/openai-conversation.d.ts +91 -0
- package/dist/actors/computer-use/openai-conversation.js +209 -0
- package/dist/actors/computer-use/openai-provider.js +25 -41
- package/dist/actors/contract.d.ts +2 -0
- package/dist/actors/scripted-browser/actor.d.ts +5 -0
- package/dist/actors/scripted-browser/actor.js +40 -19
- package/dist/actors/scripted-browser/journey.d.ts +2 -0
- package/dist/actors/scripted-browser/journey.js +2 -1
- package/dist/actors/scripted-browser/steps.d.ts +2 -0
- package/dist/actors/scripted-browser/steps.js +17 -0
- package/dist/analysis/diagnostics.js +7 -12
- package/dist/analysis/execute.d.ts +1 -1
- package/dist/analysis/execute.js +35 -5
- package/dist/analysis/service.js +7 -1
- package/dist/analysis/types.d.ts +23 -0
- package/dist/analysis/validation.d.ts +80 -35
- package/dist/analysis/validation.js +58 -11
- package/dist/cli/commands/runs.js +4 -10
- package/dist/cli/findings.d.ts +49 -4
- package/dist/cli/findings.js +179 -23
- package/dist/cli/io.d.ts +2 -1
- package/dist/cli/io.js +3 -2
- package/dist/evidence/secret-scrub.d.ts +7 -0
- package/dist/evidence/secret-scrub.js +21 -12
- package/dist/feedback/draft.js +14 -4
- package/dist/observer/run-routes.js +8 -4
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle.d.ts +1 -1
- package/dist/routes/computer-use/bundle.js +6 -16
- package/dist/routes/computer-use/fanout-bundle.js +40 -37
- package/dist/routes/computer-use/fanout-records.d.ts +2 -0
- package/dist/routes/computer-use/fanout-records.js +18 -26
- package/dist/routes/computer-use/participant-execution.js +4 -11
- package/dist/routes/computer-use/participant-facts.d.ts +2 -2
- package/dist/routes/computer-use/participant-facts.js +10 -1
- package/dist/routes/computer-use/result.js +7 -15
- package/dist/routes/computer-use/single-bundle.d.ts +2 -0
- package/dist/routes/computer-use/single-bundle.js +81 -37
- package/dist/routes/scripted/bundle.d.ts +1 -1
- package/dist/routes/scripted/bundle.js +9 -19
- package/dist/routes/scripted/route.js +1 -0
- package/dist/routes/scripted/setup.js +53 -10
- package/dist/routes/scripted/surface-records.d.ts +0 -1
- package/dist/routes/scripted/surface-records.js +27 -13
- package/dist/routes/scripted/surface-sessions.d.ts +1 -1
- package/dist/routes/scripted/surface-sessions.js +2 -0
- package/dist/routes/shared-world/bundle-records.d.ts +2 -2
- package/dist/routes/shared-world/bundle-records.js +21 -28
- package/dist/routes/shared-world/bundle.d.ts +1 -1
- package/dist/routes/shared-world/bundle.js +70 -78
- package/dist/routes/shared-world/participant-specs.d.ts +1 -2
- package/dist/routes/shared-world/participant-specs.js +0 -8
- package/dist/routes/shared-world/result.js +8 -12
- package/dist/routes/terminal/bundle.d.ts +1 -1
- package/dist/routes/terminal/bundle.js +10 -19
- package/dist/routes/terminal/credentials.js +14 -1
- package/dist/routes/terminal/dry-run.js +1 -0
- package/dist/routes/terminal/live-finish.js +7 -4
- package/dist/routes/terminal/live-sandbox.js +9 -2
- package/dist/routes/terminal/plan.js +1 -0
- package/dist/routes/terminal/runtime.d.ts +1 -0
- package/dist/routes/terminal/runtime.js +1 -0
- package/dist/routes/terminal/session.js +2 -1
- package/dist/routes/terminal/trace.d.ts +4 -2
- package/dist/routes/terminal/trace.js +30 -35
- package/dist/run/costs.js +9 -20
- package/dist/run/detail.d.ts +1 -1
- package/dist/run/detail.js +10 -1
- package/dist/run/display.d.ts +2 -2
- package/dist/run/display.js +15 -18
- package/dist/run/dry-run.js +10 -25
- package/dist/run/judge.d.ts +24 -0
- package/dist/run/judge.js +54 -0
- package/dist/run/liveness.d.ts +18 -0
- package/dist/run/liveness.js +24 -0
- package/dist/run/outcomes.d.ts +8 -1
- package/dist/run/outcomes.js +58 -12
- package/dist/run/participant-caption.d.ts +30 -0
- package/dist/run/participant-caption.js +50 -0
- package/dist/run/participant-records.d.ts +1 -1
- package/dist/run/review-markdown.d.ts +11 -0
- package/dist/run/review-markdown.js +22 -0
- package/dist/run/run-cost.d.ts +7 -0
- package/dist/run/run-cost.js +25 -5
- package/dist/run/run-index.d.ts +4 -50
- package/dist/run/run-index.js +57 -87
- package/dist/run/run.d.ts +2 -2
- package/dist/run/run.js +5 -3
- package/dist/run/secrets.d.ts +15 -2
- package/dist/run/secrets.js +134 -9
- package/dist/run/stats.js +2 -12
- package/dist/run/transient-comms-secrets.d.ts +8 -3
- package/dist/run/transient-comms-secrets.js +21 -1
- package/dist/study/keys.d.ts +1 -0
- package/dist/study/keys.js +1 -1
- package/dist/study/parse/execution.js +7 -1
- package/dist/study/plan-types.d.ts +1 -0
- package/dist/study/requirements.js +2 -1
- package/dist/study/types.d.ts +4 -1
- package/dist/study/warnings.d.ts +2 -1
- package/dist/study/warnings.js +3 -2
- package/dist/tui-app.js +73 -73
- package/dist/verify/liveness.d.ts +2 -2
- package/dist/verify/liveness.js +7 -4
- package/docs/architecture/terminal-product-route.md +14 -0
- package/docs/contracts/run-bundle.md +10 -0
- package/docs/contracts/schemas.md +9 -1
- package/docs/contracts/study-analysis.md +76 -23
- package/package.json +2 -1
- package/dist/actors/computer-use/openai-requests.d.ts +0 -62
- package/dist/actors/computer-use/openai-requests.js +0 -144
package/dist/run/secrets.js
CHANGED
|
@@ -1,33 +1,158 @@
|
|
|
1
1
|
// The literal values one run scrubs from what it writes: the provider keys and subject env values
|
|
2
2
|
// its route reads, and the values the run provisions while it runs, such as the addresses email
|
|
3
3
|
// receiving registers. These have no secret shape for pattern redaction to find, so each is
|
|
4
|
-
// replaced as written
|
|
4
|
+
// replaced as written and in the encoded forms a URL, a JSON body or a log line gives it;
|
|
5
|
+
// redactText runs after the scrub and finds secrets by shape.
|
|
5
6
|
import { scrubLiterals } from "../evidence/redaction.js";
|
|
7
|
+
import { encodedForms } from "../evidence/secret-scrub.js";
|
|
6
8
|
export class RunSecrets {
|
|
7
9
|
// Private fields stay out of JSON.stringify and object spread, so a Run that reaches a writer
|
|
8
10
|
// whole cannot carry the values with it.
|
|
9
|
-
#held;
|
|
11
|
+
#held = [];
|
|
12
|
+
/** Every held value as written and encoded, longest first. */
|
|
13
|
+
#forms = [];
|
|
10
14
|
#minLength;
|
|
11
15
|
/**
|
|
12
|
-
* Replaces every held value with the marker
|
|
13
|
-
*
|
|
16
|
+
* Replaces every held value with the marker, as written, in each encoded form, and where a URL
|
|
17
|
+
* carries it partly percent-encoded or a terminal escape sequence splits it. It reads the values
|
|
18
|
+
* on each call, so a value added after the scrub was handed to a participant or a subject is
|
|
19
|
+
* scrubbed from then on.
|
|
14
20
|
*/
|
|
15
21
|
scrub;
|
|
16
22
|
/** `seed` keeps its order; values shorter than the floor are dropped. */
|
|
17
23
|
constructor(seed, options = {}) {
|
|
18
24
|
// An empty value would match between every character.
|
|
19
25
|
this.#minLength = Math.max(1, options.minLength ?? 4);
|
|
20
|
-
this
|
|
21
|
-
|
|
26
|
+
this.add(seed);
|
|
27
|
+
const marker = options.marker ?? "[REDACTED_SECRET]";
|
|
28
|
+
// Longest first, so a form that holds a shorter one, such as a JSON-escaped value ending in a
|
|
29
|
+
// backslash, is replaced whole and leaves no stray escape behind.
|
|
30
|
+
const literal = scrubLiterals(this.#forms, marker);
|
|
31
|
+
this.scrub = (text) => {
|
|
32
|
+
const scrubbed = literal(text);
|
|
33
|
+
return replaceSpans(scrubbed, viewSpans(scrubbed, this.#forms), marker);
|
|
34
|
+
};
|
|
22
35
|
}
|
|
23
36
|
/** Holds each value the run learns while it runs, unless it is under the floor or already held. */
|
|
24
37
|
add(values) {
|
|
25
|
-
for (const value of values)
|
|
26
|
-
if (value.length
|
|
27
|
-
|
|
38
|
+
for (const value of values) {
|
|
39
|
+
if (value.length < this.#minLength || this.#held.includes(value))
|
|
40
|
+
continue;
|
|
41
|
+
this.#held.push(value);
|
|
42
|
+
for (const form of encodedForms(value))
|
|
43
|
+
if (!this.#forms.includes(form))
|
|
44
|
+
this.#forms.push(form);
|
|
45
|
+
}
|
|
46
|
+
this.#forms.sort((left, right) => right.length - left.length);
|
|
28
47
|
}
|
|
29
48
|
/** The held values, seed first, in one array that later `add` calls extend. */
|
|
30
49
|
values() {
|
|
31
50
|
return this.#held;
|
|
32
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Every held value as written and in each encoded form, longest first, in one array that later
|
|
54
|
+
* `add` calls extend. The terminal recorder sizes the output it keeps past its cap from these.
|
|
55
|
+
*/
|
|
56
|
+
forms() {
|
|
57
|
+
return this.#forms;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Where `text` holds a held value: each encoded form as written, and each form in the text's
|
|
61
|
+
* view without terminal escape sequences and with percent escapes decoded. Sorted, with
|
|
62
|
+
* overlapping spans merged. A scrub that replaces across chunk boundaries reads these.
|
|
63
|
+
*/
|
|
64
|
+
spans(text) {
|
|
65
|
+
const found = viewSpans(text, this.#forms);
|
|
66
|
+
for (const form of this.#forms)
|
|
67
|
+
for (let at = text.indexOf(form); at !== -1; at = text.indexOf(form, at + form.length))
|
|
68
|
+
found.push([at, at + form.length]);
|
|
69
|
+
return mergeSpans(found);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// What a terminal draws with (operating-system commands, control sequences and two-byte escapes,
|
|
73
|
+
// as written or JSON-escaped, as a JSON event carries a command's colored output) and what a URL
|
|
74
|
+
// encodes with (runs of percent escapes). The view a value is looked for in drops the first and
|
|
75
|
+
// decodes the second.
|
|
76
|
+
const VIEW_SEQUENCE = new RegExp([
|
|
77
|
+
"\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)",
|
|
78
|
+
"\\x1b\\[[0-?]*[ -/]*[@-~]",
|
|
79
|
+
"\\x1b[78=>]",
|
|
80
|
+
"\\\\u001b\\][^\\\\]*(?:\\\\u0007|\\\\u001b\\\\\\\\)",
|
|
81
|
+
"\\\\u001b\\[[0-?]*[ -/]*[@-~]",
|
|
82
|
+
"(?:%[0-9A-Fa-f]{2})+",
|
|
83
|
+
].join("|"), "g");
|
|
84
|
+
/**
|
|
85
|
+
* Where a value is found in the text's view. A browser encodes a space or a quote in a URL path
|
|
86
|
+
* and leaves a `/` or a `:` as written, so a value with both matches no single encoded form, and a
|
|
87
|
+
* color code inside a value splits it. Each found value maps back to the span of `text` it came
|
|
88
|
+
* from, so the text keeps every other character as written.
|
|
89
|
+
*/
|
|
90
|
+
function viewSpans(text, values) {
|
|
91
|
+
if (!text.includes("%") && !text.includes("\x1b") && !text.includes("\\u001b"))
|
|
92
|
+
return [];
|
|
93
|
+
// The view, and for each of its characters the span of `text` it came from. A character decoded
|
|
94
|
+
// from an escape run maps to the whole run.
|
|
95
|
+
let view = "";
|
|
96
|
+
const starts = [];
|
|
97
|
+
const ends = [];
|
|
98
|
+
const copy = (from, to) => {
|
|
99
|
+
for (let at = from; at < to; at += 1) {
|
|
100
|
+
starts.push(at);
|
|
101
|
+
ends.push(at + 1);
|
|
102
|
+
}
|
|
103
|
+
view += text.slice(from, to);
|
|
104
|
+
};
|
|
105
|
+
let cursor = 0;
|
|
106
|
+
let changed = false;
|
|
107
|
+
for (const sequence of text.matchAll(VIEW_SEQUENCE)) {
|
|
108
|
+
let plain = "";
|
|
109
|
+
if (sequence[0].startsWith("%")) {
|
|
110
|
+
try {
|
|
111
|
+
plain = decodeURIComponent(sequence[0]);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Not UTF-8: the run stays as written and is copied with the text after it.
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
copy(cursor, sequence.index);
|
|
119
|
+
cursor = sequence.index + sequence[0].length;
|
|
120
|
+
for (let unit = 0; unit < plain.length; unit += 1) {
|
|
121
|
+
starts.push(sequence.index);
|
|
122
|
+
ends.push(cursor);
|
|
123
|
+
}
|
|
124
|
+
view += plain;
|
|
125
|
+
changed = true;
|
|
126
|
+
}
|
|
127
|
+
if (!changed)
|
|
128
|
+
return [];
|
|
129
|
+
copy(cursor, text.length);
|
|
130
|
+
const spans = [];
|
|
131
|
+
for (const value of values)
|
|
132
|
+
for (let at = view.indexOf(value); at !== -1; at = view.indexOf(value, at + value.length))
|
|
133
|
+
spans.push([starts[at] ?? 0, ends[at + value.length - 1] ?? text.length]);
|
|
134
|
+
return spans;
|
|
135
|
+
}
|
|
136
|
+
/** Sorted, with overlapping spans merged; spans that only touch stay apart. */
|
|
137
|
+
function mergeSpans(spans) {
|
|
138
|
+
const merged = [];
|
|
139
|
+
for (const [from, to] of [...spans].sort((left, right) => left[0] - right[0])) {
|
|
140
|
+
const last = merged.at(-1);
|
|
141
|
+
if (last !== undefined && from < last[1])
|
|
142
|
+
last[1] = Math.max(last[1], to);
|
|
143
|
+
else
|
|
144
|
+
merged.push([from, to]);
|
|
145
|
+
}
|
|
146
|
+
return merged;
|
|
147
|
+
}
|
|
148
|
+
function replaceSpans(text, spans, marker) {
|
|
149
|
+
if (spans.length === 0)
|
|
150
|
+
return text;
|
|
151
|
+
let replaced = "";
|
|
152
|
+
let written = 0;
|
|
153
|
+
for (const [from, to] of mergeSpans(spans)) {
|
|
154
|
+
replaced += `${text.slice(written, from)}${marker}`;
|
|
155
|
+
written = to;
|
|
156
|
+
}
|
|
157
|
+
return `${replaced}${text.slice(written)}`;
|
|
33
158
|
}
|
package/dist/run/stats.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { runDisplay, runDisplayLabel } from "./display.js";
|
|
6
6
|
import { readRunIndex } from "./run-index.js";
|
|
7
7
|
import { addCostTotals, emptyCostTotals, readCostTotals, } from "./costs.js";
|
|
8
|
+
import { runEstimateUsd } from "./run-cost.js";
|
|
8
9
|
import { round6 } from "./pricing.js";
|
|
9
10
|
import { plural } from "./text.js";
|
|
10
11
|
const STATS_SCHEMA = "humanish.stats.v2";
|
|
@@ -27,17 +28,6 @@ function addParticipants(into, entry) {
|
|
|
27
28
|
into.reachedGoal += entry.participants.reachedGoal;
|
|
28
29
|
into.reportedFriction += entry.participants.reportedFriction ?? 0;
|
|
29
30
|
}
|
|
30
|
-
/**
|
|
31
|
-
* A run's participant and desktop estimate: its recorded figure, or $0 for a dry run that records
|
|
32
|
-
* none, since a dry run makes no model request and creates no desktop. Undefined when the cost is
|
|
33
|
-
* unknown, including a dry run that records an unknown (null) figure.
|
|
34
|
-
*/
|
|
35
|
-
function runEstimateUsd(entry) {
|
|
36
|
-
const usd = entry.estimatedCostUsd;
|
|
37
|
-
if (typeof usd === "number" && Number.isFinite(usd) && usd >= 0)
|
|
38
|
-
return usd;
|
|
39
|
-
return usd === undefined && entry.mode === "dry-run" ? 0 : undefined;
|
|
40
|
-
}
|
|
41
31
|
function entryTime(entry) {
|
|
42
32
|
return entry.startedAt ?? entry.completedAt ?? entry.updatedAt;
|
|
43
33
|
}
|
|
@@ -175,7 +165,7 @@ export async function computeStats(cwdInput, options = {}) {
|
|
|
175
165
|
totals.dryRun += 1;
|
|
176
166
|
if (entry.liveness === "running")
|
|
177
167
|
totals.running += 1;
|
|
178
|
-
const runUsd = runEstimateUsd(entry);
|
|
168
|
+
const runUsd = runEstimateUsd(entry) ?? undefined;
|
|
179
169
|
if (runUsd === undefined)
|
|
180
170
|
totals.unpricedRuns += 1;
|
|
181
171
|
else
|
|
@@ -4,7 +4,12 @@ export declare function registerTransientCommsSecrets(values: string[]): void;
|
|
|
4
4
|
/** Literal, longest-first replacement in one pass; replacement text is never matched recursively. */
|
|
5
5
|
export declare function scrubTransientCommsText(text: string): string;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
8
|
+
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
9
|
+
* holds an encoded value is returned decoded with each value replaced. Text without one keeps its
|
|
10
|
+
* original spelling, so an exact quote that holds an escape still matches its evidence. The literal
|
|
11
|
+
* scrub runs first, before decoding can rewrite a value that itself holds an escape, and last,
|
|
12
|
+
* because the encoded scrub leaves text inside marker-shaped spans alone. Outside a scope it
|
|
13
|
+
* changes nothing.
|
|
9
14
|
*/
|
|
10
|
-
export declare function
|
|
15
|
+
export declare function transientCommsKnownValueScrub(): (text: string) => string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { decodeEscapes } from "../evidence/encoded-text.js";
|
|
2
3
|
import { scrubSecretValues } from "../evidence/secret-scrub.js";
|
|
3
4
|
import { escapeRegExp } from "./text.js";
|
|
4
5
|
const scopes = new AsyncLocalStorage();
|
|
@@ -75,10 +76,29 @@ export function scrubTransientCommsText(text) {
|
|
|
75
76
|
* The scope's values as a scrubSecretValues scrub, which also finds them percent-encoded, escaped or
|
|
76
77
|
* base64-encoded and returns decoded text. Outside a scope it changes nothing.
|
|
77
78
|
*/
|
|
78
|
-
|
|
79
|
+
function transientCommsEncodedScrub() {
|
|
79
80
|
const scope = scopes.getStore();
|
|
80
81
|
if (!scope)
|
|
81
82
|
return (text) => text;
|
|
82
83
|
usable(scope);
|
|
83
84
|
return scrubSecretValues([...scope.values]);
|
|
84
85
|
}
|
|
86
|
+
const REDACTED = "[REDACTED_SECRET]";
|
|
87
|
+
const markers = (text) => text.split(REDACTED).length - 1;
|
|
88
|
+
/**
|
|
89
|
+
* A scrub for text a model writes: each scope value as written and in its encoded forms
|
|
90
|
+
* (percent-encoded, JSON-escaped, base64, base64url, hex), and where escapes split one. Text that
|
|
91
|
+
* holds an encoded value is returned decoded with each value replaced. Text without one keeps its
|
|
92
|
+
* original spelling, so an exact quote that holds an escape still matches its evidence. The literal
|
|
93
|
+
* scrub runs first, before decoding can rewrite a value that itself holds an escape, and last,
|
|
94
|
+
* because the encoded scrub leaves text inside marker-shaped spans alone. Outside a scope it
|
|
95
|
+
* changes nothing.
|
|
96
|
+
*/
|
|
97
|
+
export function transientCommsKnownValueScrub() {
|
|
98
|
+
const encoded = transientCommsEncodedScrub();
|
|
99
|
+
return (text) => {
|
|
100
|
+
const literal = scrubTransientCommsText(text);
|
|
101
|
+
const found = encoded(literal);
|
|
102
|
+
return scrubTransientCommsText(markers(found) > markers(decodeEscapes(literal)) ? found : literal);
|
|
103
|
+
};
|
|
104
|
+
}
|
package/dist/study/keys.d.ts
CHANGED
package/dist/study/keys.js
CHANGED
|
@@ -108,7 +108,7 @@ export const EXECUTION = {
|
|
|
108
108
|
timeoutMs: true,
|
|
109
109
|
completionTimeoutMs: true,
|
|
110
110
|
egressAllow: true,
|
|
111
|
-
terminal: { stdin: true, transport: true },
|
|
111
|
+
terminal: { stdin: true, transport: true, doNotTrack: true },
|
|
112
112
|
desktop: {
|
|
113
113
|
template: true,
|
|
114
114
|
browser: true,
|
|
@@ -75,9 +75,15 @@ function parseTerminal(raw) {
|
|
|
75
75
|
return { ok: true, value: undefined };
|
|
76
76
|
}
|
|
77
77
|
if (!isRecord(raw)) {
|
|
78
|
-
return invalid("`execution.terminal` must be an object ({ transport?, stdin? }).");
|
|
78
|
+
return invalid("`execution.terminal` must be an object ({ transport?, stdin?, doNotTrack? }).");
|
|
79
79
|
}
|
|
80
80
|
const terminal = {};
|
|
81
|
+
if (raw.doNotTrack !== undefined) {
|
|
82
|
+
if (typeof raw.doNotTrack !== "boolean") {
|
|
83
|
+
return invalid("`execution.terminal.doNotTrack` must be true or false (unquoted).");
|
|
84
|
+
}
|
|
85
|
+
terminal.doNotTrack = raw.doNotTrack;
|
|
86
|
+
}
|
|
81
87
|
if (raw.transport !== undefined) {
|
|
82
88
|
const transport = str(raw.transport);
|
|
83
89
|
if (transport !== "exec-stream") {
|
|
@@ -217,6 +217,7 @@ export type TerminalPlan = PlanBase & {
|
|
|
217
217
|
readonly personaId?: string;
|
|
218
218
|
readonly mission?: string;
|
|
219
219
|
readonly runtime: {
|
|
220
|
+
readonly doNotTrack: boolean;
|
|
220
221
|
readonly version?: string;
|
|
221
222
|
/** Passed to Codex as `--model` on every run, so the bundle can name and price it. */
|
|
222
223
|
readonly model: string;
|
|
@@ -100,7 +100,8 @@ async function signedInAgentSuggestion(env) {
|
|
|
100
100
|
if (ready.length === 0)
|
|
101
101
|
return "";
|
|
102
102
|
const labels = ready.map((agent) => agent.label).join(" and ");
|
|
103
|
-
|
|
103
|
+
const one = ready.length === 1;
|
|
104
|
+
return ` ${labels} ${one ? "reports" : "report"} authenticated on this machine. Set actor.type: local-agent to use ${one ? "it" : "one"} instead of a key.`;
|
|
104
105
|
}
|
|
105
106
|
/**
|
|
106
107
|
* Refuses a local-agent brain whose CLI is missing, signed out, too old, or cannot honor the
|
package/dist/study/types.d.ts
CHANGED
|
@@ -401,6 +401,8 @@ export interface StudyExecutionTerminal {
|
|
|
401
401
|
transport?: StudyTerminalTransport;
|
|
402
402
|
/** Operator stdin posture. Default "disabled". */
|
|
403
403
|
stdin?: StudyTerminalStdin;
|
|
404
|
+
/** Send DO_NOT_TRACK=1 to participant commands by default; false leaves it unset. */
|
|
405
|
+
doNotTrack?: boolean;
|
|
404
406
|
}
|
|
405
407
|
/**
|
|
406
408
|
* The terminal agent's runtime-auth channel. "openai-egress" (the default) keeps the raw runtime
|
|
@@ -511,7 +513,8 @@ export interface StudyExecution {
|
|
|
511
513
|
};
|
|
512
514
|
/**
|
|
513
515
|
* `terminal-product` route: outbound routing allowlist passed to E2B with a deny-all fallback.
|
|
514
|
-
* Every other route leaves egress open and ignores it with a warning;
|
|
516
|
+
* Every other route leaves egress open and ignores it with a warning;
|
|
517
|
+
* the first minor release on or after 2026-11-04 refuses it there.
|
|
515
518
|
* Domain filtering is a routing control, not strict destination isolation on shared hosting.
|
|
516
519
|
* It does not constrain spending through an allowed runtime provider, including when
|
|
517
520
|
* openai-egress keeps the raw runtime key outside the sandbox.
|
package/dist/study/warnings.d.ts
CHANGED
|
@@ -14,7 +14,8 @@ export declare function inertFieldPaths(config: StudyConfig): string[];
|
|
|
14
14
|
* terminal route creates its sandbox with the list as an outbound allowlist; on any other route the
|
|
15
15
|
* study author would trust a list that blocks nothing. `humanish study check` and every run print
|
|
16
16
|
* it, and the run records it in its bundle.
|
|
17
|
-
* Removed in
|
|
17
|
+
* Removed in the first minor release on or after 2026-11-04: deprecated on 2026-10-05. That release refuses the field off the terminal
|
|
18
|
+
* route.
|
|
18
19
|
*/
|
|
19
20
|
export declare function egressAllowIgnoredWarning(config: StudyConfig): string | undefined;
|
|
20
21
|
export declare function forwardDeclaredWarnings(config: StudyConfig): string[];
|
package/dist/study/warnings.js
CHANGED
|
@@ -270,12 +270,13 @@ export function inertFieldPaths(config) {
|
|
|
270
270
|
* terminal route creates its sandbox with the list as an outbound allowlist; on any other route the
|
|
271
271
|
* study author would trust a list that blocks nothing. `humanish study check` and every run print
|
|
272
272
|
* it, and the run records it in its bundle.
|
|
273
|
-
* Removed in
|
|
273
|
+
* Removed in the first minor release on or after 2026-11-04: deprecated on 2026-10-05. That release refuses the field off the terminal
|
|
274
|
+
* route.
|
|
274
275
|
*/
|
|
275
276
|
export function egressAllowIgnoredWarning(config) {
|
|
276
277
|
if (config.execution?.egressAllow === undefined || config.route === "terminal")
|
|
277
278
|
return undefined;
|
|
278
|
-
return `\`execution.egressAllow\` is ignored on route: ${config.route}. Only route: terminal creates its sandbox with that outbound allowlist, and this route's browser desktops can reach any site. Remove the field:
|
|
279
|
+
return `\`execution.egressAllow\` is ignored on route: ${config.route}. Only route: terminal creates its sandbox with that outbound allowlist, and this route's browser desktops can reach any site. Remove the field: the first minor release on or after 2026-11-04 refuses it on every route except terminal.`;
|
|
279
280
|
}
|
|
280
281
|
// Report fields that are present but not yet consumed by the engine, so a user never trusts a
|
|
281
282
|
// setting that silently does nothing.
|