humanish 0.74.0 → 0.75.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/dist/chrome-cdp-probe.d.ts +63 -0
- package/dist/chrome-cdp-probe.js +288 -0
- package/dist/chrome-cdp-probe.js.map +1 -0
- package/dist/cua-actor-lab.d.ts +16 -10
- package/dist/cua-actor-lab.js +123 -148
- package/dist/cua-actor-lab.js.map +1 -1
- package/dist/pricing.js +15 -6
- package/dist/pricing.js.map +1 -1
- package/docs/contracts/schemas.md +1 -1
- package/docs/goals/current.md +13 -1
- package/docs/ramp/README.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Which target to attribute when the endpoint lists several pages. */
|
|
2
|
+
export type ChromeCdpPagePreference = "pinned" | "active";
|
|
3
|
+
export interface ChromeCdpProbeArgs {
|
|
4
|
+
/** Launch-time CDP port, if the launch capture caught it. */
|
|
5
|
+
cdpPort?: number;
|
|
6
|
+
/** The launched profile dir; the probe re-reads DevToolsActivePort at observe time. */
|
|
7
|
+
profileDir?: string;
|
|
8
|
+
/** The URL this lane opened; attributes the page when no target id is pinned yet. */
|
|
9
|
+
targetUrl: string;
|
|
10
|
+
/** The pinned page target id from the launch-time geometry capture. */
|
|
11
|
+
targetId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* "pinned" (default): the launch-time target, for measurements about the ORIGINAL window
|
|
14
|
+
* (geometry). "active": the tab the participant is driving NOW — Chrome's /json lists page
|
|
15
|
+
* targets most-recently-focused first. The state observer must follow the participant: a
|
|
16
|
+
* verification link that opens in a NEW tab left a pinned observer reading the old tab forever,
|
|
17
|
+
* so the observed URL never changed again and stopWhen/task criteria went blind (a live run's
|
|
18
|
+
* funnel read reach-dashboard 0/2 under a screenshot OF the dashboard).
|
|
19
|
+
*/
|
|
20
|
+
prefer?: ChromeCdpPagePreference;
|
|
21
|
+
/** "state": url/title/text/scrollY. "geometry": outer window + CSS viewport. "port": resolution only. */
|
|
22
|
+
mode: "state" | "geometry" | "port";
|
|
23
|
+
}
|
|
24
|
+
/** The probe's stdout, before the caller narrows it. */
|
|
25
|
+
export interface ChromeCdpProbeResult {
|
|
26
|
+
unavailable?: string;
|
|
27
|
+
cdpPort?: number;
|
|
28
|
+
url?: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
scrollY?: number;
|
|
32
|
+
targetId?: string;
|
|
33
|
+
browserWindow?: {
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
viewport?: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
deviceScaleFactor: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The probe itself. Kept as one string so the shipped command is exactly what the tests execute
|
|
47
|
+
* (tests/chrome-cdp-probe.test.ts runs it under the real python3 against a real headless Chrome).
|
|
48
|
+
*
|
|
49
|
+
* WebSocket is hand-rolled because python's stdlib has no client: one masked text frame out, frames
|
|
50
|
+
* in until the reply with id 1 arrives, 1.5 s budget, and NO Origin header (Chrome refuses
|
|
51
|
+
* cross-origin DevTools sockets unless --remote-allow-origins is set; a header-less client is a
|
|
52
|
+
* local one). urllib is opened WITHOUT proxy handlers so a sandbox-wide http_proxy cannot redirect
|
|
53
|
+
* a loopback read.
|
|
54
|
+
*/
|
|
55
|
+
export declare const CHROME_CDP_PROBE_PY: string;
|
|
56
|
+
/** The exact shell command a sandbox runs for one probe. */
|
|
57
|
+
export declare function chromeCdpProbeCommand(args: ChromeCdpProbeArgs): string;
|
|
58
|
+
/**
|
|
59
|
+
* Narrow one probe's stdout. A parse failure is reported as unavailable with the reason, never as
|
|
60
|
+
* an empty success: the difference between "nothing to observe" and "could not observe" is the
|
|
61
|
+
* whole point of #514.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseChromeCdpProbeOutput(stdout: string | undefined): ChromeCdpProbeResult;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// The in-sandbox Chrome DevTools probe behind every URL / page-text / viewport observation.
|
|
2
|
+
//
|
|
3
|
+
// It runs on python3, stdlib only. It used to run on node, and that was the #514 root cause: the
|
|
4
|
+
// stock E2B desktop template ships python3 and curl but NO Node, and Node only arrives when a
|
|
5
|
+
// subject's serve pipeline needs it (subject-runtime.ts). So on the app-url route, and on any
|
|
6
|
+
// subject served by something other than Node (the taskly benchmark is `python3 -m http.server`),
|
|
7
|
+
// `node -e` exited 127 on every turn, the probe degraded to `{}`, and every urlIncludes /
|
|
8
|
+
// textIncludes stop condition and task criterion went blind for the whole session. The only trace
|
|
9
|
+
// was a geometry warning that the CSS viewport "could not be measured", which named the symptom
|
|
10
|
+
// and not the cause. The tab-pinning fix that preceded this one (prefer "active") was diagnosed on
|
|
11
|
+
// a Node subject, where the probe happened to work.
|
|
12
|
+
//
|
|
13
|
+
// The same lesson was learned once already: the comms catch was rewritten from node to python3 in
|
|
14
|
+
// 0.29.0 (comms-sandbox-catch.ts). This is the third in-sandbox runtime dependency to move.
|
|
15
|
+
//
|
|
16
|
+
// The script takes ONE JSON argument and prints ONE JSON line. Failures print
|
|
17
|
+
// `{"unavailable": "<reason>"}` with exit 0 so the caller can say WHY the channel is dark instead
|
|
18
|
+
// of swallowing an exit code; the TypeScript side turns that into a lane warning that names the
|
|
19
|
+
// consequence ("url/text criteria will read as NEVER MEASURED").
|
|
20
|
+
/**
|
|
21
|
+
* The probe itself. Kept as one string so the shipped command is exactly what the tests execute
|
|
22
|
+
* (tests/chrome-cdp-probe.test.ts runs it under the real python3 against a real headless Chrome).
|
|
23
|
+
*
|
|
24
|
+
* WebSocket is hand-rolled because python's stdlib has no client: one masked text frame out, frames
|
|
25
|
+
* in until the reply with id 1 arrives, 1.5 s budget, and NO Origin header (Chrome refuses
|
|
26
|
+
* cross-origin DevTools sockets unless --remote-allow-origins is set; a header-less client is a
|
|
27
|
+
* local one). urllib is opened WITHOUT proxy handlers so a sandbox-wide http_proxy cannot redirect
|
|
28
|
+
* a loopback read.
|
|
29
|
+
*/
|
|
30
|
+
export const CHROME_CDP_PROBE_PY = String.raw `
|
|
31
|
+
import base64, json, os, re, socket, struct, sys, urllib.request
|
|
32
|
+
|
|
33
|
+
def resolve_port(args):
|
|
34
|
+
port = args.get("cdpPort")
|
|
35
|
+
if isinstance(port, int) and port > 0:
|
|
36
|
+
return port
|
|
37
|
+
profile_dir = str(args.get("profileDir") or "")
|
|
38
|
+
if profile_dir:
|
|
39
|
+
try:
|
|
40
|
+
with open(os.path.join(profile_dir, "DevToolsActivePort"), "r", encoding="utf-8") as handle:
|
|
41
|
+
first = handle.readline().strip()
|
|
42
|
+
parsed = int(first)
|
|
43
|
+
if parsed > 0:
|
|
44
|
+
return parsed
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
return 9222
|
|
48
|
+
|
|
49
|
+
def list_pages(port):
|
|
50
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
51
|
+
with opener.open("http://127.0.0.1:%d/json" % port, timeout=2) as response:
|
|
52
|
+
pages = json.loads(response.read().decode("utf-8"))
|
|
53
|
+
return pages if isinstance(pages, list) else []
|
|
54
|
+
|
|
55
|
+
def select_page(pages, args):
|
|
56
|
+
http_pages = [
|
|
57
|
+
page for page in pages
|
|
58
|
+
if isinstance(page, dict) and page.get("type") == "page" and re.match(r"^https?:", str(page.get("url") or ""))
|
|
59
|
+
]
|
|
60
|
+
target_id = str(args.get("targetId") or "")
|
|
61
|
+
target_url = str(args.get("targetUrl") or "")
|
|
62
|
+
normalize = lambda value: str(value or "").rstrip("/")
|
|
63
|
+
if args.get("prefer") == "active":
|
|
64
|
+
if http_pages:
|
|
65
|
+
return http_pages[0]
|
|
66
|
+
if target_id:
|
|
67
|
+
return next((page for page in http_pages if page.get("id") == target_id), None)
|
|
68
|
+
return None
|
|
69
|
+
if target_id:
|
|
70
|
+
return next((page for page in http_pages if page.get("id") == target_id), None)
|
|
71
|
+
match = next((page for page in http_pages if normalize(page.get("url")) == normalize(target_url)), None)
|
|
72
|
+
if match is not None:
|
|
73
|
+
return match
|
|
74
|
+
return http_pages[0] if len(http_pages) == 1 else None
|
|
75
|
+
|
|
76
|
+
def evaluate(ws_url, expression, timeout=1.5):
|
|
77
|
+
match = re.match(r"^ws://([^/:]+):(\d+)(/.*)$", str(ws_url or ""))
|
|
78
|
+
if not match:
|
|
79
|
+
return None
|
|
80
|
+
host, port, path = match.group(1), int(match.group(2)), match.group(3)
|
|
81
|
+
sock = None
|
|
82
|
+
try:
|
|
83
|
+
sock = socket.create_connection((host, port), timeout=timeout)
|
|
84
|
+
sock.settimeout(timeout)
|
|
85
|
+
key = base64.b64encode(os.urandom(16)).decode("ascii")
|
|
86
|
+
handshake = (
|
|
87
|
+
"GET %s HTTP/1.1\r\nHost: %s:%d\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
|
|
88
|
+
"Sec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\n\r\n" % (path, host, port, key)
|
|
89
|
+
)
|
|
90
|
+
sock.sendall(handshake.encode("ascii"))
|
|
91
|
+
buffer = b""
|
|
92
|
+
while b"\r\n\r\n" not in buffer:
|
|
93
|
+
chunk = sock.recv(4096)
|
|
94
|
+
if not chunk:
|
|
95
|
+
return None
|
|
96
|
+
buffer += chunk
|
|
97
|
+
head, buffer = buffer.split(b"\r\n\r\n", 1)
|
|
98
|
+
if b" 101 " not in head.split(b"\r\n", 1)[0]:
|
|
99
|
+
return None
|
|
100
|
+
payload = json.dumps({
|
|
101
|
+
"id": 1,
|
|
102
|
+
"method": "Runtime.evaluate",
|
|
103
|
+
"params": {"returnByValue": True, "expression": expression},
|
|
104
|
+
}).encode("utf-8")
|
|
105
|
+
mask = os.urandom(4)
|
|
106
|
+
frame = bytearray([0x81])
|
|
107
|
+
size = len(payload)
|
|
108
|
+
if size < 126:
|
|
109
|
+
frame.append(0x80 | size)
|
|
110
|
+
elif size < 65536:
|
|
111
|
+
frame.append(0x80 | 126)
|
|
112
|
+
frame += struct.pack(">H", size)
|
|
113
|
+
else:
|
|
114
|
+
frame.append(0x80 | 127)
|
|
115
|
+
frame += struct.pack(">Q", size)
|
|
116
|
+
frame += mask + bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
|
|
117
|
+
sock.sendall(bytes(frame))
|
|
118
|
+
|
|
119
|
+
state = {"buffer": buffer}
|
|
120
|
+
|
|
121
|
+
def need(count):
|
|
122
|
+
while len(state["buffer"]) < count:
|
|
123
|
+
chunk = sock.recv(65536)
|
|
124
|
+
if not chunk:
|
|
125
|
+
raise EOFError("socket closed")
|
|
126
|
+
state["buffer"] += chunk
|
|
127
|
+
|
|
128
|
+
message = b""
|
|
129
|
+
while True:
|
|
130
|
+
need(2)
|
|
131
|
+
first, second = state["buffer"][0], state["buffer"][1]
|
|
132
|
+
fin, opcode = first & 0x80, first & 0x0F
|
|
133
|
+
masked, length, offset = second & 0x80, second & 0x7F, 2
|
|
134
|
+
if length == 126:
|
|
135
|
+
need(4)
|
|
136
|
+
length, offset = struct.unpack(">H", state["buffer"][2:4])[0], 4
|
|
137
|
+
elif length == 127:
|
|
138
|
+
need(10)
|
|
139
|
+
length, offset = struct.unpack(">Q", state["buffer"][2:10])[0], 10
|
|
140
|
+
frame_mask = b""
|
|
141
|
+
if masked:
|
|
142
|
+
need(offset + 4)
|
|
143
|
+
frame_mask, offset = state["buffer"][offset:offset + 4], offset + 4
|
|
144
|
+
need(offset + length)
|
|
145
|
+
data = state["buffer"][offset:offset + length]
|
|
146
|
+
state["buffer"] = state["buffer"][offset + length:]
|
|
147
|
+
if masked:
|
|
148
|
+
data = bytes(byte ^ frame_mask[index % 4] for index, byte in enumerate(data))
|
|
149
|
+
if opcode == 8:
|
|
150
|
+
return None
|
|
151
|
+
if opcode in (9, 10):
|
|
152
|
+
continue
|
|
153
|
+
message += data
|
|
154
|
+
if fin:
|
|
155
|
+
try:
|
|
156
|
+
reply = json.loads(message.decode("utf-8"))
|
|
157
|
+
except Exception:
|
|
158
|
+
return None
|
|
159
|
+
message = b""
|
|
160
|
+
if isinstance(reply, dict) and reply.get("id") == 1:
|
|
161
|
+
result = reply.get("result") or {}
|
|
162
|
+
inner = result.get("result") if isinstance(result, dict) else None
|
|
163
|
+
return inner.get("value") if isinstance(inner, dict) else None
|
|
164
|
+
except Exception:
|
|
165
|
+
return None
|
|
166
|
+
finally:
|
|
167
|
+
if sock is not None:
|
|
168
|
+
try:
|
|
169
|
+
sock.close()
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
STATE_EXPRESSION = (
|
|
174
|
+
"({ url: location.href, title: document.title, "
|
|
175
|
+
"text: (document.body && document.body.innerText || '').slice(0, 20000), "
|
|
176
|
+
"scrollY: (window.scrollY || 0) })"
|
|
177
|
+
)
|
|
178
|
+
GEOMETRY_EXPRESSION = (
|
|
179
|
+
"({ browserWindow: { x: window.screenX, y: window.screenY, width: window.outerWidth, height: window.outerHeight }, "
|
|
180
|
+
"viewport: { width: window.innerWidth, height: window.innerHeight, deviceScaleFactor: window.devicePixelRatio } })"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
args = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
|
|
185
|
+
mode = args.get("mode") or "state"
|
|
186
|
+
port = resolve_port(args)
|
|
187
|
+
if mode == "port":
|
|
188
|
+
print(json.dumps({"cdpPort": port}))
|
|
189
|
+
return
|
|
190
|
+
try:
|
|
191
|
+
pages = list_pages(port)
|
|
192
|
+
except Exception as error:
|
|
193
|
+
print(json.dumps({"unavailable": "CDP endpoint 127.0.0.1:%d/json unreachable (%s)" % (port, type(error).__name__)}))
|
|
194
|
+
return
|
|
195
|
+
page = select_page(pages, args)
|
|
196
|
+
if page is None:
|
|
197
|
+
print(json.dumps({"unavailable": "no http page among %d CDP targets on 127.0.0.1:%d" % (len(pages), port)}))
|
|
198
|
+
return
|
|
199
|
+
ws_url = page.get("webSocketDebuggerUrl")
|
|
200
|
+
if mode == "geometry":
|
|
201
|
+
result = evaluate(ws_url, GEOMETRY_EXPRESSION) if ws_url else None
|
|
202
|
+
if not isinstance(result, dict):
|
|
203
|
+
print(json.dumps({"unavailable": "Runtime.evaluate over the page socket returned nothing"}))
|
|
204
|
+
return
|
|
205
|
+
result["targetId"] = str(page.get("id") or "")
|
|
206
|
+
print(json.dumps(result))
|
|
207
|
+
return
|
|
208
|
+
url = str(page.get("url") or "")
|
|
209
|
+
title = str(page.get("title") or "")
|
|
210
|
+
text = ""
|
|
211
|
+
scroll_y = None
|
|
212
|
+
result = evaluate(ws_url, STATE_EXPRESSION) if ws_url else None
|
|
213
|
+
if isinstance(result, dict):
|
|
214
|
+
url = result["url"] if isinstance(result.get("url"), str) else url
|
|
215
|
+
title = result["title"] if isinstance(result.get("title"), str) else title
|
|
216
|
+
text = result["text"] if isinstance(result.get("text"), str) else ""
|
|
217
|
+
scroll_y = result["scrollY"] if isinstance(result.get("scrollY"), (int, float)) else None
|
|
218
|
+
print(json.dumps({"url": url, "title": title, "text": text, "scrollY": scroll_y}))
|
|
219
|
+
|
|
220
|
+
main()
|
|
221
|
+
`;
|
|
222
|
+
function shellSingleQuote(value) {
|
|
223
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
224
|
+
}
|
|
225
|
+
/** The exact shell command a sandbox runs for one probe. */
|
|
226
|
+
export function chromeCdpProbeCommand(args) {
|
|
227
|
+
const payload = { mode: args.mode, targetUrl: args.targetUrl };
|
|
228
|
+
if (args.cdpPort !== undefined)
|
|
229
|
+
payload.cdpPort = args.cdpPort;
|
|
230
|
+
if (args.profileDir !== undefined)
|
|
231
|
+
payload.profileDir = args.profileDir;
|
|
232
|
+
if (args.targetId !== undefined)
|
|
233
|
+
payload.targetId = args.targetId;
|
|
234
|
+
if (args.prefer !== undefined)
|
|
235
|
+
payload.prefer = args.prefer;
|
|
236
|
+
return `python3 -c ${shellSingleQuote(CHROME_CDP_PROBE_PY)} ${shellSingleQuote(JSON.stringify(payload))}`;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Narrow one probe's stdout. A parse failure is reported as unavailable with the reason, never as
|
|
240
|
+
* an empty success: the difference between "nothing to observe" and "could not observe" is the
|
|
241
|
+
* whole point of #514.
|
|
242
|
+
*/
|
|
243
|
+
export function parseChromeCdpProbeOutput(stdout) {
|
|
244
|
+
const trimmed = (stdout ?? "").trim();
|
|
245
|
+
if (trimmed.length === 0)
|
|
246
|
+
return { unavailable: "probe printed nothing" };
|
|
247
|
+
let parsed;
|
|
248
|
+
try {
|
|
249
|
+
parsed = JSON.parse(trimmed);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return { unavailable: "probe output was not JSON" };
|
|
253
|
+
}
|
|
254
|
+
if (!parsed || typeof parsed !== "object")
|
|
255
|
+
return { unavailable: "probe output was not an object" };
|
|
256
|
+
const record = parsed;
|
|
257
|
+
if (typeof record.unavailable === "string")
|
|
258
|
+
return { unavailable: record.unavailable };
|
|
259
|
+
const numberOr = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
260
|
+
const box = (value, keys) => {
|
|
261
|
+
if (!value || typeof value !== "object")
|
|
262
|
+
return undefined;
|
|
263
|
+
const source = value;
|
|
264
|
+
const out = {};
|
|
265
|
+
for (const key of keys) {
|
|
266
|
+
const n = numberOr(source[key]);
|
|
267
|
+
if (n === undefined)
|
|
268
|
+
return undefined;
|
|
269
|
+
out[key] = n;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
};
|
|
273
|
+
const browserWindow = box(record.browserWindow, ["x", "y", "width", "height"]);
|
|
274
|
+
const viewport = box(record.viewport, ["width", "height", "deviceScaleFactor"]);
|
|
275
|
+
const cdpPort = numberOr(record.cdpPort);
|
|
276
|
+
const scrollY = numberOr(record.scrollY);
|
|
277
|
+
return {
|
|
278
|
+
...(cdpPort === undefined ? {} : { cdpPort }),
|
|
279
|
+
...(typeof record.url === "string" && record.url.length > 0 ? { url: record.url } : {}),
|
|
280
|
+
...(typeof record.title === "string" && record.title.length > 0 ? { title: record.title } : {}),
|
|
281
|
+
...(typeof record.text === "string" && record.text.length > 0 ? { text: record.text } : {}),
|
|
282
|
+
...(scrollY === undefined ? {} : { scrollY }),
|
|
283
|
+
...(typeof record.targetId === "string" && record.targetId.length > 0 ? { targetId: record.targetId } : {}),
|
|
284
|
+
...(browserWindow === undefined ? {} : { browserWindow }),
|
|
285
|
+
...(viewport === undefined ? {} : { viewport })
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=chrome-cdp-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-cdp-probe.js","sourceRoot":"","sources":["../src/chrome-cdp-probe.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,EAAE;AACF,iGAAiG;AACjG,8FAA8F;AAC9F,8FAA8F;AAC9F,kGAAkG;AAClG,0FAA0F;AAC1F,kGAAkG;AAClG,gGAAgG;AAChG,mGAAmG;AACnG,oDAAoD;AACpD,EAAE;AACF,kGAAkG;AAClG,4FAA4F;AAC5F,EAAE;AACF,8EAA8E;AAC9E,kGAAkG;AAClG,gGAAgG;AAChG,iEAAiE;AAwCjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+L5C,CAAC;AAEF,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC7C,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC5D,MAAM,OAAO,GAA4B,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IACxF,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC/D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACxE,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAClE,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC5D,OAAO,cAAc,gBAAgB,CAAC,mBAAmB,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;AAC5G,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAA0B;IAClE,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;IAC1E,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;IACpG,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;IACvF,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAsB,EAAE,CACtD,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,MAAM,GAAG,GAAG,CAAC,KAAc,EAAE,IAAc,EAAsC,EAAE;QACjF,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAChC,IAAI,CAAC,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YACtC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAA0C,CAAC;IACxH,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAqC,CAAC;IACpH,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO;QACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvF,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3G,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC;QACzD,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC"}
|
package/dist/cua-actor-lab.d.ts
CHANGED
|
@@ -661,9 +661,9 @@ export declare function desktopBrowserFamily(value: string | undefined): Desktop
|
|
|
661
661
|
/**
|
|
662
662
|
* Runtime-only CDP endpoint attribution for the exact chromium this lane launched. Port
|
|
663
663
|
* resolution at OBSERVE time: the cached launch-time `cdpPort` wins; absent that, the observer
|
|
664
|
-
*
|
|
664
|
+
* probe re-reads `profileDir`'s DevToolsActivePort marker (a slow cold start can publish it
|
|
665
665
|
* AFTER the launch-time poll gave up); absent both it falls back to the legacy fixed 9222,
|
|
666
|
-
* where a dead endpoint degrades into an honest warning.
|
|
666
|
+
* where a dead endpoint degrades into an honest warning that names the cause.
|
|
667
667
|
*/
|
|
668
668
|
export interface ChromeCdpEndpoint {
|
|
669
669
|
cdpPort?: number;
|
|
@@ -673,14 +673,18 @@ export interface ChromeCdpEndpoint {
|
|
|
673
673
|
targetUrl: string;
|
|
674
674
|
}
|
|
675
675
|
/**
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
676
|
+
* The URL / title / page-text / scroll observer behind stopWhen and task criteria. One probe per
|
|
677
|
+
* observation, run on the sandbox's python3 (see chrome-cdp-probe.ts for why not node: #514).
|
|
678
|
+
*
|
|
679
|
+
* "active": follow the participant to whatever tab they are driving now — never pin the state
|
|
680
|
+
* observer to the launch tab (a verification link that opened in a NEW tab left a pinned observer
|
|
681
|
+
* reading the old tab forever).
|
|
682
|
+
*
|
|
683
|
+
* `onUnavailable` fires ONCE, on the first probe that could not read the page, with the reason.
|
|
684
|
+
* The observer still degrades to `{}` for the loop; the callback is how a lane says out loud that
|
|
685
|
+
* url/text criteria are not being measured, instead of letting the funnel report 0/N (#514).
|
|
681
686
|
*/
|
|
682
|
-
export declare function
|
|
683
|
-
export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string): () => Promise<{
|
|
687
|
+
export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void): () => Promise<{
|
|
684
688
|
url?: string;
|
|
685
689
|
title?: string;
|
|
686
690
|
text?: string;
|
|
@@ -690,8 +694,10 @@ export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbo
|
|
|
690
694
|
* Read the running browser's actual outer-window bounds and CSS layout viewport through the
|
|
691
695
|
* already-enabled local Chrome DevTools endpoint. The returned values come from `window.*` in
|
|
692
696
|
* the target page; requested E2B resolution is deliberately not an input to this function.
|
|
697
|
+
* `undefined` carries the reason the measurement is missing via `onUnavailable`, so the geometry
|
|
698
|
+
* warning can name the cause (a dead CDP endpoint, no python3) instead of only the symptom.
|
|
693
699
|
*/
|
|
694
|
-
export declare function makeChromeDesktopGeometryObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string): () => Promise<(Pick<RunDesktopGeometry, "browserWindow" | "viewport"> & {
|
|
700
|
+
export declare function makeChromeDesktopGeometryObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void): () => Promise<(Pick<RunDesktopGeometry, "browserWindow" | "viewport"> & {
|
|
695
701
|
targetId?: string;
|
|
696
702
|
}) | undefined>;
|
|
697
703
|
/** Shared hosted-browser geometry capture used by per-lane and sequential shared-world routes. */
|