humanish 0.76.0 → 0.78.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/README.md CHANGED
@@ -53,8 +53,8 @@ npx humanish run first-run # a study with no keys and no spend — about a mi
53
53
  npx humanish run try-live # a REAL study on a hosted desktop, capped at $2
54
54
  ```
55
55
 
56
- **What it finds, measured (2026-09-01 and 09-03, receipts in `bench/` and `docs/goals/`):** 43 of
57
- 45 planted defects over three benchmark runs on an app we wrote, none invented on the clean arm; on
56
+ **What it finds, measured (2026-09-01 to 09-04, receipts in `bench/` and `docs/goals/`):** 58 of
57
+ 60 planted defects over four benchmark runs on an app we wrote, none invented in 15 clean runs; on
58
58
  two apps we did not write, 16 of 18 distinct findings confirmed against the source and 0 invented,
59
59
  across 14 participants; a drawDB modal reported by 5 of 5 keyboard-first participants (3 stopped
60
60
  there) and never mentioned by 5 mouse-driving newcomers; a TodoMVC rename that blocked 6 of 6
@@ -483,8 +483,10 @@ and `resolved`: what the page itself reported afterwards (`navigator.userAgent`,
483
483
  `devicePixelRatio`, `innerWidth`, `maxTouchPoints`, coarse pointer). A page without a viewport
484
484
  meta lays out at 980 px, as it would on a phone, and the bundle says so. Firefox cannot be
485
485
  emulated, so the lane fails closed instead of shipping a desktop run labelled mobile. The
486
- emulation covers the launch tab (the user agent and touch flags are browser-wide); a bundle
487
- without a `fidelity` block is a responsive-viewport study whatever its preset is called.
486
+ viewport and DPR override cover the launch tab (the user agent and touch flags are browser-wide);
487
+ if an observation reads a tab the participant opened later, the lane records one warning saying
488
+ so, because that tab laid out at the window width. A bundle without a `fidelity` block is a
489
+ responsive-viewport study whatever its preset is called.
488
490
 
489
491
  **Desktop browser choice.** Hosted computer-use lanes and shared-world actor seats use the
490
492
  route's historical opener unless you set `execution.desktop.browser` to `chrome`, `chromium`,
@@ -21,8 +21,10 @@ export interface ChromeCdpProbeArgs {
21
21
  /**
22
22
  * "state": url/title/text/scrollY. "geometry": outer window + CSS viewport. "port": resolution
23
23
  * only. "emulate": apply mobile emulation (#221) to the selected page and exit (the overrides that
24
- * are session-scoped, UA / touch / DPR, lapse when the socket closes). "hold": the same, then keep
25
- * the socket open until killed, which is how a lane keeps them for its whole life. "fidelity": read
24
+ * are session-scoped, UA / touch / DPR, lapse when the socket closes). "hold": the same over a browser-level
25
+ * socket, then stay attached until killed (how a lane keeps them for its whole life) and attach
26
+ * to every page target Chrome opens later, sending it the same overrides and a reload the moment
27
+ * it exists, never pausing it (#623). "fidelity": read
26
28
  * back what the page reports about itself (UA, DPR, viewport, touch), the proof for the bundle.
27
29
  */
28
30
  mode: "state" | "geometry" | "port" | "emulate" | "hold" | "fidelity";
@@ -73,116 +73,128 @@ def select_page(pages, args):
73
73
  return match
74
74
  return http_pages[0] if len(http_pages) == 1 else None
75
75
 
76
- def ws_session(ws_url, messages, timeout=1.5, hold=False):
77
- """Send CDP messages over one page socket, in order, and return their replies (None on failure).
78
- hold=True prints the replies and then keeps the socket open until the process is killed."""
79
- match = re.match(r"^ws://([^/:]+):(\d+)(/.*)$", str(ws_url or ""))
80
- if not match:
81
- return None
82
- host, port, path = match.group(1), int(match.group(2)), match.group(3)
83
- sock = None
84
- try:
85
- sock = socket.create_connection((host, port), timeout=timeout)
86
- sock.settimeout(timeout)
76
+ class Ws:
77
+ """One DevTools WebSocket: hand-rolled client frames, JSON in and out; events that arrive while
78
+ a reply is awaited are kept in .events for the caller."""
79
+ def __init__(self, ws_url, timeout=1.5):
80
+ match = re.match(r"^ws://([^/:]+):(\d+)(/.*)$", str(ws_url or ""))
81
+ if not match:
82
+ raise ValueError("not a ws url")
83
+ host, port, path = match.group(1), int(match.group(2)), match.group(3)
84
+ self.sock = socket.create_connection((host, port), timeout=timeout)
85
+ self.sock.settimeout(timeout)
87
86
  key = base64.b64encode(os.urandom(16)).decode("ascii")
88
- handshake = (
87
+ self.sock.sendall((
89
88
  "GET %s HTTP/1.1\r\nHost: %s:%d\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"
90
89
  "Sec-WebSocket-Key: %s\r\nSec-WebSocket-Version: 13\r\n\r\n" % (path, host, port, key)
91
- )
92
- sock.sendall(handshake.encode("ascii"))
90
+ ).encode("ascii"))
93
91
  buffer = b""
94
92
  while b"\r\n\r\n" not in buffer:
95
- chunk = sock.recv(4096)
93
+ chunk = self.sock.recv(4096)
96
94
  if not chunk:
97
- return None
95
+ raise EOFError("closed during the handshake")
98
96
  buffer += chunk
99
- head, buffer = buffer.split(b"\r\n\r\n", 1)
97
+ head, self.buffer = buffer.split(b"\r\n\r\n", 1)
100
98
  if b" 101 " not in head.split(b"\r\n", 1)[0]:
101
- return None
102
- state = {"buffer": buffer}
99
+ raise EOFError("handshake refused")
100
+ self.next_id = 0
101
+ self.events = []
103
102
 
104
- def need(count):
105
- while len(state["buffer"]) < count:
106
- chunk = sock.recv(65536)
107
- if not chunk:
108
- raise EOFError("socket closed")
109
- state["buffer"] += chunk
103
+ def need(self, count):
104
+ while len(self.buffer) < count:
105
+ chunk = self.sock.recv(65536)
106
+ if not chunk:
107
+ raise EOFError("socket closed")
108
+ self.buffer += chunk
110
109
 
111
- def send(message):
112
- payload = json.dumps(message).encode("utf-8")
113
- mask = os.urandom(4)
114
- frame = bytearray([0x81])
115
- size = len(payload)
116
- if size < 126:
117
- frame.append(0x80 | size)
118
- elif size < 65536:
119
- frame.append(0x80 | 126)
120
- frame += struct.pack(">H", size)
121
- else:
122
- frame.append(0x80 | 127)
123
- frame += struct.pack(">Q", size)
124
- frame += mask + bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
125
- sock.sendall(bytes(frame))
110
+ def send(self, method, params=None, session_id=None):
111
+ self.next_id += 1
112
+ message = {"id": self.next_id, "method": method, "params": params or {}}
113
+ if session_id:
114
+ message["sessionId"] = session_id
115
+ payload = json.dumps(message).encode("utf-8")
116
+ mask = os.urandom(4)
117
+ frame = bytearray([0x81])
118
+ size = len(payload)
119
+ if size < 126:
120
+ frame.append(0x80 | size)
121
+ elif size < 65536:
122
+ frame.append(0x80 | 126)
123
+ frame += struct.pack(">H", size)
124
+ else:
125
+ frame.append(0x80 | 127)
126
+ frame += struct.pack(">Q", size)
127
+ frame += mask + bytes(byte ^ mask[index % 4] for index, byte in enumerate(payload))
128
+ self.sock.sendall(bytes(frame))
129
+ return self.next_id
126
130
 
127
- def receive(wanted_id):
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:
131
+ def recv(self):
132
+ """The next JSON message (a reply or an event), or None once the socket closes."""
133
+ message = b""
134
+ while True:
135
+ self.need(2)
136
+ first, second = self.buffer[0], self.buffer[1]
137
+ fin, opcode = first & 0x80, first & 0x0F
138
+ masked, length, offset = second & 0x80, second & 0x7F, 2
139
+ if length == 126:
140
+ self.need(4)
141
+ length, offset = struct.unpack(">H", self.buffer[2:4])[0], 4
142
+ elif length == 127:
143
+ self.need(10)
144
+ length, offset = struct.unpack(">Q", self.buffer[2:10])[0], 10
145
+ frame_mask = b""
146
+ if masked:
147
+ self.need(offset + 4)
148
+ frame_mask, offset = self.buffer[offset:offset + 4], offset + 4
149
+ self.need(offset + length)
150
+ data = self.buffer[offset:offset + length]
151
+ self.buffer = self.buffer[offset + length:]
152
+ if masked:
153
+ data = bytes(byte ^ frame_mask[index % 4] for index, byte in enumerate(data))
154
+ if opcode == 8:
155
+ return None
156
+ if opcode in (9, 10):
157
+ continue
158
+ message += data
159
+ if fin:
160
+ try:
161
+ reply = json.loads(message.decode("utf-8"))
162
+ except Exception:
150
163
  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") == wanted_id:
161
- return reply
164
+ message = b""
165
+ if isinstance(reply, dict):
166
+ return reply
162
167
 
163
- replies = []
164
- for index, message in enumerate(messages, 1):
165
- send({"id": index, **message})
166
- replies.append(receive(index))
167
- if hold:
168
- # Announce the result now (the caller reads stdout once), then stay attached.
169
- applied = [m["method"] for m, r in zip(messages, replies) if isinstance(r, dict) and "error" not in r]
170
- failed = [m["method"] for m, r in zip(messages, replies) if not (isinstance(r, dict) and "error" not in r)]
171
- print(json.dumps({"applied": applied, "held": not failed, **({"unavailable": "%s failed" % failed[0]} if failed else {})}), flush=True)
172
- if failed:
173
- return replies
174
- sock.settimeout(None)
175
- while True:
176
- time.sleep(30)
177
- return replies
168
+ def call(self, method, params=None, session_id=None):
169
+ """Send one command and wait for its reply; events seen on the way are queued."""
170
+ wanted = self.send(method, params, session_id)
171
+ while True:
172
+ reply = self.recv()
173
+ if reply is None:
174
+ return None
175
+ if reply.get("id") == wanted:
176
+ return reply
177
+ if "method" in reply:
178
+ self.events.append(reply)
179
+
180
+ def close(self):
181
+ try:
182
+ self.sock.close()
183
+ except Exception:
184
+ pass
185
+
186
+ def ws_session(ws_url, messages, timeout=1.5):
187
+ """Send CDP messages over one page socket, in order, and return their replies (None on failure)."""
188
+ try:
189
+ ws = Ws(ws_url, timeout)
190
+ except Exception:
191
+ return None
192
+ try:
193
+ return [ws.call(message["method"], message.get("params")) for message in messages]
178
194
  except Exception:
179
195
  return None
180
196
  finally:
181
- if sock is not None:
182
- try:
183
- sock.close()
184
- except Exception:
185
- pass
197
+ ws.close()
186
198
 
187
199
  def evaluate(ws_url, expression, timeout=1.5):
188
200
  replies = ws_session(ws_url, [{"method": "Runtime.evaluate", "params": {"returnByValue": True, "expression": expression}}], timeout)
@@ -208,9 +220,8 @@ FIDELITY_EXPRESSION = (
208
220
  "coarsePointer: !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches) })"
209
221
  )
210
222
 
211
- def emulate(ws_url, request, hold=False):
212
- """Apply the Emulation domain to the page and reload it so scripts that read the UA at load see it.
213
- With hold=True the socket stays open (the overrides are bound to this session) until the process is killed."""
223
+ def emulation_messages(request, reload):
224
+ """The Emulation domain for one page session; reload only where scripts already ran at load."""
214
225
  width = int(request.get("width") or 0)
215
226
  height = int(request.get("height") or 0)
216
227
  scale = float(request.get("deviceScaleFactor") or 1)
@@ -229,10 +240,11 @@ def emulate(ws_url, request, hold=False):
229
240
  if platform:
230
241
  params["platform"] = platform
231
242
  messages.append({"method": "Emulation.setUserAgentOverride", "params": params})
232
- messages.append({"method": "Page.reload", "params": {}})
233
- replies = ws_session(ws_url, messages, timeout=5, hold=hold)
234
- if replies is None:
235
- return None, "the page socket could not be opened"
243
+ if reload:
244
+ messages.append({"method": "Page.reload", "params": {}})
245
+ return messages
246
+
247
+ def applied_of(messages, replies):
236
248
  applied = []
237
249
  for message, reply in zip(messages, replies):
238
250
  if isinstance(reply, dict) and "error" not in reply:
@@ -242,6 +254,122 @@ def emulate(ws_url, request, hold=False):
242
254
  return applied, "%s failed: %s" % (message["method"], detail)
243
255
  return applied, None
244
256
 
257
+ def emulate(ws_url, request):
258
+ """One-shot: apply the Emulation domain to the page and reload it. The session-scoped overrides
259
+ lapse when this socket closes; the lane uses hold() instead."""
260
+ messages = emulation_messages(request, reload=True)
261
+ replies = ws_session(ws_url, messages, timeout=5)
262
+ if replies is None:
263
+ return None, "the page socket could not be opened"
264
+ return applied_of(messages, replies)
265
+
266
+ def apply_over(ws, request, session_id, reload):
267
+ messages = emulation_messages(request, reload)
268
+ return applied_of(messages, [ws.call(m["method"], m.get("params"), session_id) for m in messages])
269
+
270
+ def browser_ws_url(port):
271
+ opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
272
+ with opener.open("http://127.0.0.1:%d/json/version" % port, timeout=2) as response:
273
+ info = json.loads(response.read().decode("utf-8"))
274
+ return str(info.get("webSocketDebuggerUrl") or "")
275
+
276
+ def hold(port, page, request):
277
+ """Emulate the launch page and then every page target Chrome opens later, for as long as this
278
+ process lives (#221, #623). One browser-level socket with flattened sessions: the launch page is
279
+ attached by id and reloaded so scripts that read the UA at load see it; a target that appears
280
+ later is attached the moment it exists, sent the same overrides, and reloaded once after its
281
+ first real navigation commits (touch emulation reaches a document only when it loads under it).
282
+ Two things this loop must never do (measured on a real desktop, 2026-09-04): pause new targets
283
+ (waitForDebuggerOnStart) and wait for their replies. A popup a participant taps open shares its
284
+ opener's renderer, answers Emulation commands only once it runs, and a loop blocked on that
285
+ reply never resumed the tab (it loaded forever) and never reached the next one. Overrides are
286
+ sent without waiting; replies, errors included, are logged as they arrive.
287
+ Prints one JSON line per attached target; the first line is the announce the lane reads."""
288
+ launch_id = str(page.get("id") or "")
289
+ try:
290
+ ws = Ws(browser_ws_url(port), timeout=5)
291
+ except Exception as error:
292
+ print(json.dumps({"unavailable": "the browser socket could not be opened (%s)" % type(error).__name__}), flush=True)
293
+ return
294
+ attach = ws.call("Target.attachToTarget", {"targetId": launch_id, "flatten": True})
295
+ session_id = ((attach or {}).get("result") or {}).get("sessionId") if isinstance(attach, dict) else None
296
+ if not session_id:
297
+ print(json.dumps({"unavailable": "Target.attachToTarget failed for the launch page"}), flush=True)
298
+ return
299
+ covered = {launch_id}
300
+ applied, failure = apply_over(ws, request, session_id, reload=True)
301
+ print(json.dumps({"applied": applied, "held": failure is None, "targetId": launch_id, **({"unavailable": failure} if failure else {})}), flush=True)
302
+ if failure:
303
+ return
304
+ auto = ws.call("Target.setAutoAttach", {"autoAttach": True, "waitForDebuggerOnStart": False, "flatten": True})
305
+ if not isinstance(auto, dict) or "error" in auto:
306
+ print(json.dumps({"autoAttach": False, "unavailable": "Target.setAutoAttach failed; later tabs are not emulated"}), flush=True)
307
+ ws.sock.settimeout(None)
308
+ pending = {}
309
+ # Sessions of later targets that still owe one reload: touch emulation reaches a document only
310
+ # when it loads under the override (measured: a popup's first document reported
311
+ # maxTouchPoints 0 with the viewport already 414 px), so each later tab is reloaded once after
312
+ # its first real navigation commits, or at once when it had already committed at attach time.
313
+ reload_owed = {}
314
+ while True:
315
+ try:
316
+ message = ws.events.pop(0) if ws.events else ws.recv()
317
+ except Exception as error:
318
+ print(json.dumps({"holderExit": "%s: %s" % (type(error).__name__, error)}), flush=True)
319
+ return
320
+ if message is None:
321
+ print(json.dumps({"holderExit": "browser socket closed"}), flush=True)
322
+ return
323
+ reply_id = message.get("id")
324
+ session = message.get("sessionId")
325
+ if reply_id in pending:
326
+ method = pending.pop(reply_id)
327
+ if "error" in message:
328
+ print(json.dumps({"replyError": method, "message": str((message.get("error") or {}).get("message"))}), flush=True)
329
+ elif method == "href" and session in reload_owed:
330
+ # The navigation may have committed before Page.enable could report it; the page's
331
+ # own location says so, and the reload it is owed goes out now (once: pop guards it).
332
+ href = str((((message.get("result") or {}).get("result") or {}).get("value")) or "")
333
+ if href and not href.startswith("about:"):
334
+ target_id = reload_owed.pop(session)
335
+ pending[ws.send("Page.reload", {}, session)] = "Page.reload"
336
+ print(json.dumps({"reloaded": target_id, "url": href, "by": "href"}), flush=True)
337
+ continue
338
+ method = message.get("method")
339
+ params = message.get("params") or {}
340
+ if method == "Page.frameNavigated" and session in reload_owed:
341
+ frame = params.get("frame") or {}
342
+ url = str(frame.get("url") or "")
343
+ if not frame.get("parentId") and url and not url.startswith("about:"):
344
+ target_id = reload_owed.pop(session)
345
+ pending[ws.send("Page.reload", {}, session)] = "Page.reload"
346
+ print(json.dumps({"reloaded": target_id, "url": url}), flush=True)
347
+ continue
348
+ if method != "Target.attachedToTarget":
349
+ continue
350
+ info = params.get("targetInfo") or {}
351
+ new_session = params.get("sessionId")
352
+ target_id = str(info.get("targetId") or "")
353
+ if info.get("type") == "page" and target_id and target_id not in covered:
354
+ covered.add(target_id)
355
+ messages = emulation_messages(request, reload=False)
356
+ for m in messages:
357
+ pending[ws.send(m["method"], m.get("params"), new_session)] = m["method"]
358
+ pending[ws.send("Page.enable", {}, new_session)] = "Page.enable"
359
+ url = str(info.get("url") or "")
360
+ if url and not url.startswith("about:"):
361
+ pending[ws.send("Page.reload", {}, new_session)] = "Page.reload"
362
+ print(json.dumps({"attached": target_id, "sent": [m["method"] for m in messages] + ["Page.reload"], "url": url}), flush=True)
363
+ else:
364
+ reload_owed[new_session] = target_id
365
+ # Two ways to learn the first real navigation committed, whichever answers first:
366
+ # Page.frameNavigated (if Page.enable landed before the commit) or the page's own
367
+ # location.href (if it did not).
368
+ pending[ws.send("Runtime.evaluate", {"expression": "location.href", "returnByValue": True}, new_session)] = "href"
369
+ print(json.dumps({"attached": target_id, "sent": [m["method"] for m in messages], "reloadAfterNavigation": True}), flush=True)
370
+ if params.get("waitingForDebugger"):
371
+ ws.send("Runtime.runIfWaitingForDebugger", {}, new_session)
372
+
245
373
  def main():
246
374
  args = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {}
247
375
  mode = args.get("mode") or "state"
@@ -258,7 +386,11 @@ def main():
258
386
  if page is None:
259
387
  print(json.dumps({"unavailable": "no http page among %d CDP targets on 127.0.0.1:%d" % (len(pages), port)}))
260
388
  return
261
- ws_url = page.get("webSocketDebuggerUrl")
389
+ # Chrome omits webSocketDebuggerUrl from /json while another DevTools client is attached to the
390
+ # page, or for a moment after one detaches; the page socket URL is still /devtools/page/<id>.
391
+ ws_url = page.get("webSocketDebuggerUrl") or (
392
+ "ws://127.0.0.1:%d/devtools/page/%s" % (port, page.get("id")) if page.get("id") else None
393
+ )
262
394
  if mode == "emulate":
263
395
  applied, failure = emulate(ws_url, args.get("emulation") or {}) if ws_url else (None, "the page has no socket")
264
396
  if failure is not None:
@@ -267,13 +399,9 @@ def main():
267
399
  print(json.dumps({"applied": applied, "targetId": str(page.get("id") or "")}))
268
400
  return
269
401
  if mode == "hold":
270
- if not ws_url:
271
- print(json.dumps({"unavailable": "the page has no socket"}), flush=True)
272
- return
273
- # Prints its own line from inside ws_session (before blocking) and never returns on success.
274
- applied, failure = emulate(ws_url, args.get("emulation") or {}, hold=True)
275
- if failure is not None:
276
- print(json.dumps({"unavailable": failure, "applied": applied or []}), flush=True)
402
+ # Prints its announce line, then stays attached (and attaches to every later page target)
403
+ # until killed.
404
+ hold(port, page, args.get("emulation") or {})
277
405
  return
278
406
  if mode == "fidelity":
279
407
  result = evaluate(ws_url, FIDELITY_EXPRESSION) if ws_url else None
@@ -300,7 +428,7 @@ def main():
300
428
  title = result["title"] if isinstance(result.get("title"), str) else title
301
429
  text = result["text"] if isinstance(result.get("text"), str) else ""
302
430
  scroll_y = result["scrollY"] if isinstance(result.get("scrollY"), (int, float)) else None
303
- print(json.dumps({"url": url, "title": title, "text": text, "scrollY": scroll_y}))
431
+ print(json.dumps({"url": url, "title": title, "text": text, "scrollY": scroll_y, "targetId": str(page.get("id") or "")}))
304
432
 
305
433
  main()
306
434
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"chrome-cdp-probe.js","sourceRoot":"","sources":["../src/chrome-cdp-probe.ts"],"names":[],"mappings":"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"}
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;AA0EjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoZ5C,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,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACrE,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,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;QAC3C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC;QAC3E,CAAC,CAAC,SAAS,CAAC;IACd,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;IACD,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,MAAM,QAAQ,GAAG,CAAC,GAAmC,EAAE;QACrD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC5B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACtD,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACjD,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QACvD,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,GAAG,KAAK,SAAS,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;YACvJ,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,KAAK,IAAI,EAAE,CAAC;IACvJ,CAAC,CAAC,EAAE,CAAC;IACL,OAAO;QACL,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,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"}
@@ -685,7 +685,26 @@ export interface ChromeCdpEndpoint {
685
685
  * The observer still degrades to `{}` for the loop; the callback is how a lane says out loud that
686
686
  * url/text criteria are not being measured, instead of letting the funnel report 0/N (#514).
687
687
  */
688
- export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void): () => Promise<{
688
+ export declare function makeChromeBrowserStateObserver(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId?: string, onUnavailable?: (reason: string) => void,
689
+ /**
690
+ * Mobile emulation on later tabs (#623): the holder attaches to every page target Chrome opens
691
+ * after the launch page, so a tab the participant opens later should lay out at the phone width
692
+ * too. The first observation on each new target reads that page's OWN report; a target that
693
+ * reports the requested width is recorded through `onCovered`, and one that does not (or cannot
694
+ * be read) fires `onDrift` once, so a phone-labelled lane that spent part of its session at
695
+ * desktop layout says so with the number the page gave.
696
+ */
697
+ drift?: {
698
+ emulatedTargetId: string;
699
+ expectedWidth: number;
700
+ expectTouch?: boolean;
701
+ onDrift: (reason: string) => void;
702
+ onCovered?: (targetId: string, read: {
703
+ innerWidth: number;
704
+ devicePixelRatio: number;
705
+ maxTouchPoints: number;
706
+ }) => void;
707
+ }): () => Promise<{
689
708
  url?: string;
690
709
  title?: string;
691
710
  text?: string;
@@ -712,6 +731,8 @@ export declare const DEFAULT_MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhon
712
731
  export declare function applyMobileEmulation(desktop: E2BDesktopSandbox, requestTimeoutMs: number, endpoint: ChromeCdpEndpoint, targetId: string | undefined, request: ChromeMobileEmulationRequest): Promise<{
713
732
  fidelity: NonNullable<RunDesktopGeometry["fidelity"]>;
714
733
  warnings: string[];
734
+ targetId?: string;
735
+ holderName: string;
715
736
  }>;
716
737
  /** Shared hosted-browser geometry capture used by per-lane and sequential shared-world routes. */
717
738
  export declare function captureDesktopBrowserGeometry(args: {