humanish 0.136.0 → 0.137.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 +22 -0
- package/dist/actors/computer-use/actor.d.ts +2 -0
- package/dist/actors/computer-use/actor.js +1 -0
- package/dist/actors/computer-use/loop/observation.js +2 -0
- package/dist/actors/computer-use/loop/session.d.ts +3 -2
- package/dist/actors/computer-use/loop/session.js +8 -12
- package/dist/actors/computer-use/loop/types.d.ts +14 -1
- package/dist/evidence/browser-network.d.ts +108 -0
- package/dist/evidence/browser-network.js +516 -0
- package/dist/evidence/trace-page.d.ts +31 -1
- package/dist/evidence/trace-page.js +68 -2
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle.js +1 -0
- package/dist/routes/computer-use/e2b-desktop/desktop.js +6 -0
- package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +12 -1
- package/dist/routes/computer-use/e2b-desktop/fidelity.js +7 -2
- package/dist/routes/computer-use/e2b-desktop/network.d.ts +52 -0
- package/dist/routes/computer-use/e2b-desktop/network.js +153 -0
- package/dist/routes/computer-use/e2b-desktop/start.js +18 -1
- package/dist/routes/computer-use/e2b-desktop/state.d.ts +3 -0
- package/dist/routes/computer-use/e2b-desktop/state.js +2 -0
- package/dist/routes/computer-use/e2b-desktop/teardown.js +6 -3
- package/dist/routes/computer-use/fanout-records.js +10 -0
- package/dist/routes/computer-use/participant-desktop.d.ts +7 -2
- package/dist/routes/computer-use/participant-model.js +2 -1
- package/dist/routes/computer-use/plan.js +2 -1
- package/dist/routes/computer-use/single-bundle.d.ts +3 -0
- package/dist/routes/computer-use/single-bundle.js +4 -0
- package/dist/routes/computer-use/types.d.ts +7 -0
- package/dist/routes/scripted/plan.js +2 -2
- package/dist/routes/shared-world/plan.js +3 -2
- package/dist/routes/terminal/plan.js +2 -2
- package/dist/run/live-items.d.ts +20 -2
- package/dist/run/live-items.js +23 -14
- package/dist/run/streams.d.ts +7 -0
- package/dist/study/config.js +4 -1
- package/dist/study/keys.d.ts +3 -0
- package/dist/study/keys.js +2 -0
- package/dist/study/parse/comms.js +1 -20
- package/dist/study/parse/execution.js +30 -1
- package/dist/study/parse/values.d.ts +5 -0
- package/dist/study/parse/values.js +22 -0
- package/dist/study/plan-base.js +6 -1
- package/dist/study/plan-types.d.ts +1 -1
- package/dist/study/study-fields.d.ts +7 -1
- package/dist/study/study-fields.js +10 -0
- package/dist/study/types.d.ts +11 -0
- package/dist/study/validation.d.ts +8 -0
- package/dist/study/validation.js +31 -2
- package/dist/substrates/detached.d.ts +5 -0
- package/dist/substrates/detached.js +9 -0
- package/dist/substrates/e2b/cdp-network-capture.d.ts +23 -0
- package/dist/substrates/e2b/cdp-network-capture.js +194 -0
- package/dist/substrates/e2b/cdp-probe.d.ts +20 -5
- package/dist/substrates/e2b/cdp-probe.js +100 -36
- package/dist/substrates/e2b/desktop-cdp.d.ts +32 -2
- package/dist/substrates/e2b/desktop-cdp.js +87 -5
- package/dist/verify/network.d.ts +10 -0
- package/dist/verify/network.js +162 -0
- package/dist/verify/pages.js +1 -16
- package/dist/verify/verify.js +8 -2
- package/docs/contracts/run-bundle.md +86 -1
- package/package.json +6 -6
package/dist/run/live-items.d.ts
CHANGED
|
@@ -13,11 +13,29 @@ export interface LiveItemsCursor {
|
|
|
13
13
|
items: number;
|
|
14
14
|
bytes: number;
|
|
15
15
|
}
|
|
16
|
+
/** How run.json binds a file of JSON lines: its byte count, its sha256 and its number of lines. */
|
|
17
|
+
export interface JsonLinesIndex {
|
|
18
|
+
path: string;
|
|
19
|
+
bytes: number;
|
|
20
|
+
sha256: string;
|
|
21
|
+
lines: number;
|
|
22
|
+
}
|
|
23
|
+
/** What a refusal calls one line of a file and several: `an item` and `items` for a live file. */
|
|
24
|
+
export interface LineNoun {
|
|
25
|
+
one: string;
|
|
26
|
+
many: string;
|
|
27
|
+
}
|
|
16
28
|
/**
|
|
17
|
-
* The
|
|
29
|
+
* The JSON object on each line of a file run.json binds, or why the bytes do not match the index:
|
|
18
30
|
* `bytes` must be exactly the indexed byte count, hash to the indexed sha256, and hold the indexed
|
|
19
|
-
* number of lines, each a JSON object.
|
|
31
|
+
* number of lines, each a JSON object. A refusal names the file and counts, never what it holds.
|
|
20
32
|
*/
|
|
33
|
+
export declare function boundJsonLines(bytes: Buffer, index: JsonLinesIndex, noun: LineNoun): {
|
|
34
|
+
lines: Record<string, unknown>[];
|
|
35
|
+
} | {
|
|
36
|
+
refused: string;
|
|
37
|
+
};
|
|
38
|
+
/** The items the indexed prefix of a live file holds, or why the bytes do not match the index. */
|
|
21
39
|
export declare function liveItemsFromBytes(bytes: Buffer, index: LiveItemsIndex): {
|
|
22
40
|
items: ActorTraceItem[];
|
|
23
41
|
} | {
|
package/dist/run/live-items.js
CHANGED
|
@@ -29,19 +29,24 @@ export function isLiveItemsIndex(value) {
|
|
|
29
29
|
typeof value.sha256 === "string" &&
|
|
30
30
|
/^[0-9a-f]{64}$/.test(value.sha256));
|
|
31
31
|
}
|
|
32
|
+
const ITEM = { one: "an item", many: "items" };
|
|
32
33
|
/**
|
|
33
|
-
* The
|
|
34
|
+
* The JSON object on each line of a file run.json binds, or why the bytes do not match the index:
|
|
34
35
|
* `bytes` must be exactly the indexed byte count, hash to the indexed sha256, and hold the indexed
|
|
35
|
-
* number of lines, each a JSON object.
|
|
36
|
+
* number of lines, each a JSON object. A refusal names the file and counts, never what it holds.
|
|
36
37
|
*/
|
|
37
|
-
export function
|
|
38
|
+
export function boundJsonLines(bytes, index, noun) {
|
|
38
39
|
if (bytes.length !== index.bytes)
|
|
39
40
|
return {
|
|
40
41
|
refused: `${index.path} holds ${bytes.length} bytes and run.json records ${index.bytes}`,
|
|
41
42
|
};
|
|
42
43
|
if (createHash("sha256").update(bytes).digest("hex") !== index.sha256)
|
|
43
44
|
return { refused: `${index.path} does not match the sha256 run.json records for it` };
|
|
44
|
-
return
|
|
45
|
+
return objectLinesOf(bytes, index.lines, index.path, noun);
|
|
46
|
+
}
|
|
47
|
+
/** The items the indexed prefix of a live file holds, or why the bytes do not match the index. */
|
|
48
|
+
export function liveItemsFromBytes(bytes, index) {
|
|
49
|
+
return asItems(boundJsonLines(bytes, { ...index, lines: index.items }, ITEM));
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* The items past `after` in the indexed prefix of a live file, from the file's bytes between
|
|
@@ -59,10 +64,14 @@ export function liveItemsAfter(bytes, index, after) {
|
|
|
59
64
|
};
|
|
60
65
|
if (bytes[0] !== 0x0a)
|
|
61
66
|
return { refused: `${index.path} has no line end before byte ${after.bytes}` };
|
|
62
|
-
return
|
|
67
|
+
return asItems(objectLinesOf(bytes.subarray(1), index.items - after.items, index.path, ITEM));
|
|
68
|
+
}
|
|
69
|
+
/** A live file's lines as trace items. */
|
|
70
|
+
function asItems(read) {
|
|
71
|
+
return "refused" in read ? read : { items: read.lines };
|
|
63
72
|
}
|
|
64
73
|
/** The JSON object on each of `count` lines in `bytes`, which must be UTF-8 and end a line. */
|
|
65
|
-
function
|
|
74
|
+
function objectLinesOf(bytes, count, file, noun) {
|
|
66
75
|
let text;
|
|
67
76
|
try {
|
|
68
77
|
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
@@ -74,21 +83,21 @@ function itemLinesOf(bytes, count, file) {
|
|
|
74
83
|
if (lines.length > 0 && lines.pop() !== "")
|
|
75
84
|
return { refused: `${file} does not end its indexed bytes at a line end` };
|
|
76
85
|
if (lines.length !== count)
|
|
77
|
-
return { refused: `${file} holds ${lines.length}
|
|
78
|
-
const
|
|
86
|
+
return { refused: `${file} holds ${lines.length} ${noun.many} and run.json records ${count}` };
|
|
87
|
+
const objects = [];
|
|
79
88
|
for (const line of lines) {
|
|
80
|
-
let
|
|
89
|
+
let value;
|
|
81
90
|
try {
|
|
82
|
-
|
|
91
|
+
value = JSON.parse(line);
|
|
83
92
|
}
|
|
84
93
|
catch {
|
|
85
94
|
return { refused: `${file} holds a line that is not JSON` };
|
|
86
95
|
}
|
|
87
|
-
if (!isRecord(
|
|
88
|
-
return { refused: `${file} holds a line that is not
|
|
89
|
-
|
|
96
|
+
if (!isRecord(value))
|
|
97
|
+
return { refused: `${file} holds a line that is not ${noun.one}` };
|
|
98
|
+
objects.push(value);
|
|
90
99
|
}
|
|
91
|
-
return {
|
|
100
|
+
return { lines: objects };
|
|
92
101
|
}
|
|
93
102
|
/** The warning for a participant whose live items were written in full on two writes in a row. */
|
|
94
103
|
function rewrittenEvent(stream, position) {
|
package/dist/run/streams.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CodexAppServerTrace } from "../actors/codex/app-server-trace.js";
|
|
2
2
|
import type { ActorStatus, ActorTrace, ActorTraceItem } from "../actors/contract.js";
|
|
3
|
+
import type { BrowserNetworkIndex } from "../evidence/browser-network.js";
|
|
3
4
|
import type { RunDesktopRecording } from "../evidence/desktop-recording-types.js";
|
|
4
5
|
export type RunStreamKind = "ui" | "browser" | "terminal" | "tui" | "codex-ui" | "artifact" | "summary";
|
|
5
6
|
export type RunSimulationStatus = "queued" | "preparing" | "running" | "passed" | "abandoned" | "incomplete" | "complete" | "blocked" | "timed_out" | "failed" | "contract_proof_only";
|
|
@@ -310,6 +311,12 @@ export interface RunStream {
|
|
|
310
311
|
};
|
|
311
312
|
completion?: RunStreamCompletion;
|
|
312
313
|
recording?: RunDesktopRecording;
|
|
314
|
+
/**
|
|
315
|
+
* The participant's browser network log, when its study set `execution.network`: where the
|
|
316
|
+
* file is, what binds it, and why records are missing. The shape guard does not read it; verify
|
|
317
|
+
* checks it with the file (src/verify/network.ts).
|
|
318
|
+
*/
|
|
319
|
+
network?: BrowserNetworkIndex;
|
|
313
320
|
artifacts: Array<{
|
|
314
321
|
label: string;
|
|
315
322
|
path: string;
|
package/dist/study/config.js
CHANGED
|
@@ -39,7 +39,7 @@ import { invalid, optionalStr, str } from "./parse/values.js";
|
|
|
39
39
|
import { isComputerUseComposition, routeOf } from "./routing.js";
|
|
40
40
|
import { declaredParticipantIds } from "./plan-participants.js";
|
|
41
41
|
import { ID_PATTERN, STUDY_SCHEMA, } from "./types.js";
|
|
42
|
-
import { automaticAnalysisRouteReason, desktopMediaValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, smtpValidationReason, taskProtocolValidationReason, waitLimitValidationReason, } from "./validation.js";
|
|
42
|
+
import { automaticAnalysisRouteReason, desktopMediaValidationReason, networkCaptureValidationReason, outputTokenLimitValidationReason, receivingEmailValidationReason, smtpValidationReason, taskProtocolValidationReason, waitLimitValidationReason, } from "./validation.js";
|
|
43
43
|
import { forwardDeclaredWarnings, inertFieldLabels, inertFieldPaths } from "./warnings.js";
|
|
44
44
|
import { plural } from "../run/text.js";
|
|
45
45
|
/**
|
|
@@ -228,6 +228,9 @@ function checkStudyConfig(config) {
|
|
|
228
228
|
const mediaReason = desktopMediaValidationReason(config);
|
|
229
229
|
if (mediaReason)
|
|
230
230
|
return invalid(mediaReason);
|
|
231
|
+
const networkReason = networkCaptureValidationReason(config);
|
|
232
|
+
if (networkReason)
|
|
233
|
+
return invalid(networkReason);
|
|
231
234
|
const outputLimitReason = outputTokenLimitValidationReason(config);
|
|
232
235
|
if (outputLimitReason)
|
|
233
236
|
return invalid(outputLimitReason);
|
package/dist/study/keys.d.ts
CHANGED
package/dist/study/keys.js
CHANGED
|
@@ -119,6 +119,8 @@ export const EXECUTION = {
|
|
|
119
119
|
completionTimeoutMs: true,
|
|
120
120
|
egressAllow: true,
|
|
121
121
|
terminal: { stdin: true, transport: true, doNotTrack: true },
|
|
122
|
+
// true or false, or a mapping whose keys are checked here.
|
|
123
|
+
network: { origins: true },
|
|
122
124
|
desktop: {
|
|
123
125
|
template: true,
|
|
124
126
|
browser: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { invalid, posInt, str } from "./values.js";
|
|
1
|
+
import { exactOrigin, invalid, posInt, str } from "./values.js";
|
|
2
2
|
import { isRecord } from "../../run/type-guards.js";
|
|
3
3
|
// Fail-loud (never silently swallow a comms setting): a malformed `comms` block returns a parse
|
|
4
4
|
// failure rather than being dropped.
|
|
@@ -104,25 +104,6 @@ function parseEmailConnection(raw) {
|
|
|
104
104
|
}
|
|
105
105
|
return { ok: true, value };
|
|
106
106
|
}
|
|
107
|
-
/** The origin of an http(s) URL with no credentials, path, query or fragment, else undefined. */
|
|
108
|
-
function exactOrigin(input) {
|
|
109
|
-
if (typeof input !== "string")
|
|
110
|
-
return undefined;
|
|
111
|
-
try {
|
|
112
|
-
const url = new URL(input);
|
|
113
|
-
return ["http:", "https:"].includes(url.protocol) &&
|
|
114
|
-
!url.username &&
|
|
115
|
-
!url.password &&
|
|
116
|
-
url.pathname === "/" &&
|
|
117
|
-
!url.search &&
|
|
118
|
-
!url.hash
|
|
119
|
-
? url.origin
|
|
120
|
-
: undefined;
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
107
|
/** `comms.email.external`: the catch the adopter runs, its inbox and the env var holding its token. */
|
|
127
108
|
function parseExternalCatch(raw) {
|
|
128
109
|
if (raw === undefined)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveAutomaticAnalysis } from "../../analysis/automatic-config.js";
|
|
2
2
|
import { DEVICE_PRESET_NAMES, isDevicePresetName } from "../device-presets.js";
|
|
3
3
|
import { isExactRuntimeVersion } from "../../routes/terminal/runtime.js";
|
|
4
|
-
import { invalid, nonNegNumber, posInt, str } from "./values.js";
|
|
4
|
+
import { exactOrigin, invalid, nonNegNumber, posInt, str } from "./values.js";
|
|
5
5
|
import { isRecord } from "../../run/type-guards.js";
|
|
6
6
|
import { scanEncodedText } from "../../evidence/encoded-text.js";
|
|
7
7
|
export function parseExecution(raw) {
|
|
@@ -34,6 +34,11 @@ export function parseExecution(raw) {
|
|
|
34
34
|
}
|
|
35
35
|
if (desktopResult.value)
|
|
36
36
|
execution.desktop = desktopResult.value;
|
|
37
|
+
const network = parseNetwork(raw.network);
|
|
38
|
+
if (!network.ok)
|
|
39
|
+
return network;
|
|
40
|
+
if (network.value !== undefined)
|
|
41
|
+
execution.network = network.value;
|
|
37
42
|
const terminalResult = parseTerminal(raw.terminal);
|
|
38
43
|
if (!terminalResult.ok) {
|
|
39
44
|
return terminalResult;
|
|
@@ -71,6 +76,30 @@ export function parseExecution(raw) {
|
|
|
71
76
|
}
|
|
72
77
|
return { ok: true, value: Object.keys(execution).length > 0 ? execution : undefined };
|
|
73
78
|
}
|
|
79
|
+
/** `execution.network`: true, false, or `{ origins }`. False reads as absent. */
|
|
80
|
+
function parseNetwork(raw) {
|
|
81
|
+
if (raw === undefined || raw === false)
|
|
82
|
+
return { ok: true, value: undefined };
|
|
83
|
+
if (raw === true)
|
|
84
|
+
return { ok: true, value: true };
|
|
85
|
+
if (!isRecord(raw))
|
|
86
|
+
return invalid("`execution.network` must be true, false, or a mapping with `origins`: the origins whose requests are kept beside the target's.");
|
|
87
|
+
if (raw.origins === undefined)
|
|
88
|
+
return { ok: true, value: {} };
|
|
89
|
+
if (!Array.isArray(raw.origins) || raw.origins.length > 16)
|
|
90
|
+
return invalid("`execution.network.origins` must list at most 16 exact http(s) origins, such as https://api.example.com.");
|
|
91
|
+
const origins = [];
|
|
92
|
+
for (const entry of raw.origins) {
|
|
93
|
+
const origin = exactOrigin(entry);
|
|
94
|
+
if (origin === undefined)
|
|
95
|
+
return invalid("`execution.network.origins` accepts exact http(s) origins without credentials, path, query or fragment, such as https://api.example.com.");
|
|
96
|
+
// The network log's index records each origin, so one verify would flag is refused here.
|
|
97
|
+
if (scanEncodedText(origin).sensitive)
|
|
98
|
+
return invalid("`execution.network.origins` has an origin that looks like a secret or a local path, and the run records each origin in its network log index. List the origin of the API or asset host only.");
|
|
99
|
+
origins.push(origin);
|
|
100
|
+
}
|
|
101
|
+
return { ok: true, value: { origins: [...new Set(origins)] } };
|
|
102
|
+
}
|
|
74
103
|
function parseTerminal(raw) {
|
|
75
104
|
if (raw === undefined) {
|
|
76
105
|
return { ok: true, value: undefined };
|
|
@@ -14,3 +14,8 @@ export declare function posInt(value: unknown): number | undefined;
|
|
|
14
14
|
/** A non-negative finite number (0 allowed; caps default to 0 = no-spend). Accepts a numeric
|
|
15
15
|
* string too, since YAML scalars can arrive as strings. */
|
|
16
16
|
export declare function nonNegNumber(value: unknown): number | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The origin of an http(s) URL with no credentials, path, query or fragment, else undefined. The
|
|
19
|
+
* origin lists `comms.email.allowedOrigins` and `execution.network.origins` take this rule.
|
|
20
|
+
*/
|
|
21
|
+
export declare function exactOrigin(input: unknown): string | undefined;
|
|
@@ -60,3 +60,25 @@ export function nonNegNumber(value) {
|
|
|
60
60
|
}
|
|
61
61
|
return undefined;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* The origin of an http(s) URL with no credentials, path, query or fragment, else undefined. The
|
|
65
|
+
* origin lists `comms.email.allowedOrigins` and `execution.network.origins` take this rule.
|
|
66
|
+
*/
|
|
67
|
+
export function exactOrigin(input) {
|
|
68
|
+
if (typeof input !== "string")
|
|
69
|
+
return undefined;
|
|
70
|
+
try {
|
|
71
|
+
const url = new URL(input);
|
|
72
|
+
return ["http:", "https:"].includes(url.protocol) &&
|
|
73
|
+
!url.username &&
|
|
74
|
+
!url.password &&
|
|
75
|
+
url.pathname === "/" &&
|
|
76
|
+
!url.search &&
|
|
77
|
+
!url.hash
|
|
78
|
+
? url.origin
|
|
79
|
+
: undefined;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/study/plan-base.js
CHANGED
|
@@ -20,7 +20,9 @@ function residualOf(config) {
|
|
|
20
20
|
...(policies === undefined ? {} : { policies }),
|
|
21
21
|
...(defaults === undefined ? {} : { defaults }),
|
|
22
22
|
...(review === undefined ? {} : { review }),
|
|
23
|
-
...(config.execution?.desktop === undefined &&
|
|
23
|
+
...(config.execution?.desktop === undefined &&
|
|
24
|
+
config.execution?.target === undefined &&
|
|
25
|
+
config.execution?.network === undefined
|
|
24
26
|
? {}
|
|
25
27
|
: {
|
|
26
28
|
execution: {
|
|
@@ -30,6 +32,9 @@ function residualOf(config) {
|
|
|
30
32
|
...(config.execution?.target === undefined
|
|
31
33
|
? {}
|
|
32
34
|
: { target: config.execution.target }),
|
|
35
|
+
...(config.execution?.network === undefined
|
|
36
|
+
? {}
|
|
37
|
+
: { network: config.execution.network }),
|
|
33
38
|
},
|
|
34
39
|
}),
|
|
35
40
|
subject: {
|
|
@@ -51,7 +51,7 @@ export type Requirement = {
|
|
|
51
51
|
* fields at run time.
|
|
52
52
|
*/
|
|
53
53
|
export type ResidualConfig = Pick<StudyConfig, "comms" | "policies" | "defaults" | "review"> & {
|
|
54
|
-
readonly execution?: Pick<NonNullable<StudyConfig["execution"]>, "desktop" | "target">;
|
|
54
|
+
readonly execution?: Pick<NonNullable<StudyConfig["execution"]>, "desktop" | "target" | "network">;
|
|
55
55
|
readonly subject: Pick<StudyConfig["subject"], "clone" | "localTree" | "repos" | "envValues">;
|
|
56
56
|
};
|
|
57
57
|
export interface PlannedAnalysis {
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type { StudyConfig, StudyParticipantEntry } from "./types.js";
|
|
1
|
+
import type { StudyConfig, StudyNetworkCapture, StudyParticipantEntry } from "./types.js";
|
|
2
2
|
/** The participants listed one by one: a `participants` list. Undefined when none is listed. */
|
|
3
3
|
export declare function participantList(config: StudyConfig): readonly StudyParticipantEntry[] | undefined;
|
|
4
4
|
/** The declared number of identical participants: `participants: <n>` or `participants.count`. */
|
|
5
5
|
export declare function declaredParticipantCount(config: StudyConfig): number | undefined;
|
|
6
6
|
/** The steer each of a counted group of participants gets: `participants.instruction`. */
|
|
7
7
|
export declare function participantInstruction(config: StudyConfig): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* The origins each participant's network log keeps beside its target's
|
|
10
|
+
* (`execution.network.origins`), or undefined when capture is off: `execution.network` absent or
|
|
11
|
+
* false.
|
|
12
|
+
*/
|
|
13
|
+
export declare function networkCaptureOrigins(network: StudyNetworkCapture | undefined): readonly string[] | undefined;
|
|
@@ -17,6 +17,16 @@ export function declaredParticipantCount(config) {
|
|
|
17
17
|
export function participantInstruction(config) {
|
|
18
18
|
return isGroup(config.participants) ? config.participants.instruction : undefined;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* The origins each participant's network log keeps beside its target's
|
|
22
|
+
* (`execution.network.origins`), or undefined when capture is off: `execution.network` absent or
|
|
23
|
+
* false.
|
|
24
|
+
*/
|
|
25
|
+
export function networkCaptureOrigins(network) {
|
|
26
|
+
if (network === undefined || network === false)
|
|
27
|
+
return undefined;
|
|
28
|
+
return network === true ? [] : (network.origins ?? []);
|
|
29
|
+
}
|
|
20
30
|
function isGroup(participants) {
|
|
21
31
|
return typeof participants === "object" && participants !== null && !Array.isArray(participants);
|
|
22
32
|
}
|
package/dist/study/types.d.ts
CHANGED
|
@@ -570,6 +570,15 @@ export interface StudyDesktopFidelity {
|
|
|
570
570
|
/** Full user-agent string to present; default: a mobile Safari string. */
|
|
571
571
|
userAgent?: string;
|
|
572
572
|
}
|
|
573
|
+
/**
|
|
574
|
+
* `execution.network`: record each browser participant's requests to `network/<id>.ndjson`.
|
|
575
|
+
* `true` keeps the requests to the participant's target origin; `origins` adds up to 16 more exact
|
|
576
|
+
* http(s) origins. Only computer-use participants in hosted Chrome or Chromium on
|
|
577
|
+
* `execution.target: e2b-desktop` read it; every other route refuses it.
|
|
578
|
+
*/
|
|
579
|
+
export type StudyNetworkCapture = boolean | {
|
|
580
|
+
origins?: string[];
|
|
581
|
+
};
|
|
573
582
|
export interface StudyExecution {
|
|
574
583
|
target?: StudyExecutionTarget;
|
|
575
584
|
/** Actor session wall-clock budget. Consumed on the app-url route. */
|
|
@@ -582,6 +591,8 @@ export interface StudyExecution {
|
|
|
582
591
|
*/
|
|
583
592
|
concurrency?: number;
|
|
584
593
|
desktop?: StudyExecutionDesktop;
|
|
594
|
+
/** Each browser participant's network log; see StudyNetworkCapture. Absent or false: off. */
|
|
595
|
+
network?: StudyNetworkCapture;
|
|
585
596
|
/** `terminal-product` route: the terminal transport + stdin posture. Consumed on that route. */
|
|
586
597
|
terminal?: StudyExecutionTerminal;
|
|
587
598
|
/** `terminal-product` route: runtime key placement, defaulting to openai-egress, an external
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StudyRoute } from "./routing.js";
|
|
1
2
|
import type { StudyConfig } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* Cross-validate the computer-use fan-out declaration (`per-lane-worlds`). Returns the failure
|
|
@@ -21,6 +22,13 @@ export declare function smtpValidationReason(config: StudyConfig): string | unde
|
|
|
21
22
|
* topology may not identify the backend that is actually executing.
|
|
22
23
|
*/
|
|
23
24
|
export declare function desktopMediaValidationReason(config: StudyConfig, supportsMedia?: boolean): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Why a study's `execution.network` cannot run where it is going, or undefined. Only a
|
|
27
|
+
* computer-use participant in hosted Chrome or Chromium has the in-sandbox DevTools client that
|
|
28
|
+
* records requests. Enforced at parse and again by each route's planner, which passes its own
|
|
29
|
+
* route, for a library caller's config.
|
|
30
|
+
*/
|
|
31
|
+
export declare function networkCaptureValidationReason(config: StudyConfig, route?: StudyRoute): string | undefined;
|
|
24
32
|
/** Reused by direct library runners so unsupported receiving never becomes inert configuration. */
|
|
25
33
|
export declare function receivingEmailValidationReason(config: StudyConfig): string | undefined;
|
|
26
34
|
/** Refuse task declarations that the selected execution path would discard.
|
package/dist/study/validation.js
CHANGED
|
@@ -4,8 +4,8 @@ import { waitFieldsReason } from "../actors/computer-use/wait.js";
|
|
|
4
4
|
import { PARTICIPANT_ID_MAX_CHARS, PARTICIPANT_ID_PATTERN } from "./parse/actors.js";
|
|
5
5
|
import { isHttpUrl, isLoopbackUrl } from "./parse/subject.js";
|
|
6
6
|
import { declaredTargets } from "./plan-participants.js";
|
|
7
|
-
import { actorResolvesToComputerUse, computerUseParticipantCount, participantCountReason, registeredComputerUseActors, resolveEntryUrl, isComputerUseComposition, isScriptedBrowserComposition, isSharedWorldComposition, isTerminalProductComposition, } from "./routing.js";
|
|
8
|
-
import { participantList } from "./study-fields.js";
|
|
7
|
+
import { actorResolvesToComputerUse, computerUseParticipantCount, participantCountReason, registeredComputerUseActors, resolveEntryUrl, isComputerUseComposition, isScriptedBrowserComposition, isSharedWorldComposition, isTerminalProductComposition, routeOf, } from "./routing.js";
|
|
8
|
+
import { networkCaptureOrigins, participantList } from "./study-fields.js";
|
|
9
9
|
import { isStartAfterMs, rosterStartsReason, startAfterMessage, windowReason } from "./arrivals.js";
|
|
10
10
|
/**
|
|
11
11
|
* Cross-validate the computer-use fan-out declaration (`per-lane-worlds`). Returns the failure
|
|
@@ -206,6 +206,35 @@ export function desktopMediaValidationReason(config, supportsMedia = isComputerU
|
|
|
206
206
|
}
|
|
207
207
|
return undefined;
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Why a study's `execution.network` cannot run where it is going, or undefined. Only a
|
|
211
|
+
* computer-use participant in hosted Chrome or Chromium has the in-sandbox DevTools client that
|
|
212
|
+
* records requests. Enforced at parse and again by each route's planner, which passes its own
|
|
213
|
+
* route, for a library caller's config.
|
|
214
|
+
*/
|
|
215
|
+
export function networkCaptureValidationReason(config, route = routeOf(config)) {
|
|
216
|
+
if (networkCaptureOrigins(config.execution?.network) === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
const hosted = "only computer-use participants in hosted Chrome or Chromium on `execution.target: e2b-desktop` record their requests";
|
|
219
|
+
if (route === "terminal")
|
|
220
|
+
return "`execution.network` records each browser participant's requests, and route: terminal launches no browser: its participant works in a shell. Remove `execution.network`.";
|
|
221
|
+
if (route === "shared-world")
|
|
222
|
+
return "`execution.network` is not supported on route: shared-world yet: a shared world's lobby code sits in a page address the network log would keep, and that route keeps observed addresses only as digests. Remove `execution.network`, or run independent participants on route: computer-use.";
|
|
223
|
+
if (route === "scripted")
|
|
224
|
+
return `\`execution.network\` is not supported on route: scripted yet: ${hosted}. Remove \`execution.network\`.`;
|
|
225
|
+
if (route !== "computer-use")
|
|
226
|
+
return `\`execution.network\` is not read on route: ${route}: ${hosted}. Remove \`execution.network\`.`;
|
|
227
|
+
if (config.subject.source === "desktop-cli")
|
|
228
|
+
return "`execution.network` records each browser participant's requests, and a desktop-cli subject opens a terminal window and no browser, so there are none to record. Remove `execution.network`.";
|
|
229
|
+
if (config.subject.source === "local-app")
|
|
230
|
+
return `\`execution.network\` is not supported for a local-app subject: humanish does not launch that participant's browser, and ${hosted}. Remove \`execution.network\`.`;
|
|
231
|
+
const target = config.execution?.target;
|
|
232
|
+
if (target !== "e2b-desktop")
|
|
233
|
+
return `\`execution.network\` is not supported with \`execution.target: ${target ?? "(absent)"}\` yet: ${hosted}. Set \`execution.target: e2b-desktop\`, or remove \`execution.network\`.`;
|
|
234
|
+
if (config.execution?.desktop?.browser === "firefox")
|
|
235
|
+
return "`execution.network` needs Chrome or Chromium, and `execution.desktop.browser: firefox` launches Firefox with no DevTools port to record requests through. Set `execution.desktop.browser` to chrome, chromium or default, or remove `execution.network`.";
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
209
238
|
/** Reused by direct library runners so unsupported receiving never becomes inert configuration. */
|
|
210
239
|
export function receivingEmailValidationReason(config) {
|
|
211
240
|
if (config.comms?.email?.kind !== "real")
|
|
@@ -27,6 +27,11 @@ export interface DetachedStepResult {
|
|
|
27
27
|
}
|
|
28
28
|
/** Read the capped log tail for a step (raw; caller redacts). */
|
|
29
29
|
export declare function readDetachedLog(shell: Shell, name: string, requestTimeoutMs?: number): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a process started by startDetachedProcess or runDetachedStep has exited. Its wrapper
|
|
32
|
+
* writes a status file however the command ended, a signal included.
|
|
33
|
+
*/
|
|
34
|
+
export declare function detachedProcessExited(shell: Shell, name: string, requestTimeoutMs?: number): Promise<boolean>;
|
|
30
35
|
/**
|
|
31
36
|
* Run a bounded step (install/build) detached, polling its atomic status file until it
|
|
32
37
|
* exits or the budget runs out. On timeout the process group is killed and the log tail is
|
|
@@ -75,6 +75,15 @@ export async function readDetachedLog(shell, name, requestTimeoutMs = DEFAULT_RE
|
|
|
75
75
|
const result = await runOrThrow(shell, `tail -c ${LOG_TAIL_BYTES} ${shellQuote(`${stepDir(name)}/log.txt`)} 2>/dev/null || true`, { requestTimeoutMs });
|
|
76
76
|
return result.stdout;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether a process started by startDetachedProcess or runDetachedStep has exited. Its wrapper
|
|
80
|
+
* writes a status file however the command ended, a signal included.
|
|
81
|
+
*/
|
|
82
|
+
export async function detachedProcessExited(shell, name, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
83
|
+
assertName(name);
|
|
84
|
+
const result = await runOrThrow(shell, `test -e ${shellQuote(`${stepDir(name)}/status`)} && echo exited || true`, { requestTimeoutMs });
|
|
85
|
+
return result.stdout.trim() === "exited";
|
|
86
|
+
}
|
|
78
87
|
/**
|
|
79
88
|
* Run a bounded step (install/build) detached, polling its atomic status file until it
|
|
80
89
|
* exits or the budget runs out. On timeout the process group is killed and the log tail is
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The `network` field of a hold request. */
|
|
2
|
+
export interface ChromeNetworkCaptureRequest {
|
|
3
|
+
/**
|
|
4
|
+
* Sandbox file the holder appends to; its folder is created. Defaults to
|
|
5
|
+
* /tmp/humanish-network/events.ndjson.
|
|
6
|
+
*/
|
|
7
|
+
eventsPath?: string;
|
|
8
|
+
/** Origins, as `new URL(url).origin` writes them, whose request addresses the holder writes. */
|
|
9
|
+
origins: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The capture half of the holder, spliced into CHROME_CDP_PROBE_PY so the shipped script stays one
|
|
13
|
+
* string. Line kinds: `start` (Network answered on the launch page), `request`, `response`,
|
|
14
|
+
* `servedFromCache`, `finished`, `failed`, `backForward` (a main-frame restore from the
|
|
15
|
+
* back-forward cache, which sends no request), `cap` (the file reached 32 MiB and writing stopped)
|
|
16
|
+
* and `exit` (the holder stopped; written in a finally). Ids are `<session>:<requestId>`, the
|
|
17
|
+
* session numbered in attach order from 0 for the launch page, because request ids repeat across
|
|
18
|
+
* renderers. `wallTime` is seconds since the epoch on the sandbox clock; `timestamp` is CDP's
|
|
19
|
+
* monotonic clock, for durations. A path longer than 4,096 characters ends at its last whole
|
|
20
|
+
* segment within them, then `/[TRUNCATED]`: the host redacts each segment, and a token cut in two
|
|
21
|
+
* can fall below the length its rules withhold.
|
|
22
|
+
*/
|
|
23
|
+
export declare const NETWORK_CAPTURE_PY: string;
|