cdp-toolkit 1.8.0 → 1.9.1

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/mcp.js CHANGED
@@ -278,6 +278,185 @@ async function listLeases(opts = {}) {
278
278
  return out;
279
279
  }
280
280
 
281
+ // src/origins.ts
282
+ import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
283
+ import { join as join2 } from "node:path";
284
+ function safeId2(id) {
285
+ return id.replace(/[^A-Za-z0-9._-]/g, "_");
286
+ }
287
+ function originFile(backend, targetId) {
288
+ return join2(leaseDir(), `origin-${backend}-${safeId2(targetId)}.json`);
289
+ }
290
+ var ORIGIN_FILE_RE = /^origin-(chrome|firefox)-(.+)\.json$/;
291
+ async function recordOrigin(backend, targetId, opts = {}) {
292
+ const record = {
293
+ backend,
294
+ targetId,
295
+ label: typeof opts.label === "string" && opts.label.length ? opts.label : defaultLabel(),
296
+ pid: process.pid,
297
+ createdAt: opts.now ?? Date.now()
298
+ };
299
+ const file = originFile(backend, targetId);
300
+ try {
301
+ await mkdir2(leaseDir(), { recursive: true });
302
+ try {
303
+ await writeFile2(file, JSON.stringify(record), { flag: "wx" });
304
+ return;
305
+ } catch (err) {
306
+ if (err.code !== "EEXIST")
307
+ throw err;
308
+ }
309
+ await unlink2(file).catch(() => {
310
+ return;
311
+ });
312
+ await writeFile2(file, JSON.stringify(record), { flag: "wx" });
313
+ } catch {}
314
+ }
315
+ async function readOrigin(backend, targetId) {
316
+ let raw;
317
+ try {
318
+ raw = await readFile2(originFile(backend, targetId), "utf8");
319
+ } catch {
320
+ return;
321
+ }
322
+ try {
323
+ const rec = JSON.parse(raw);
324
+ if (typeof rec?.targetId === "string" && typeof rec?.label === "string" && typeof rec?.createdAt === "number")
325
+ return rec;
326
+ } catch {}
327
+ return;
328
+ }
329
+ async function listOrigins(opts = {}) {
330
+ let names = [];
331
+ try {
332
+ names = await readdir2(leaseDir());
333
+ } catch {
334
+ return [];
335
+ }
336
+ const live = opts.liveIds === undefined ? undefined : new Set(opts.liveIds.map((id) => safeId2(id)));
337
+ const out = [];
338
+ for (const name of names) {
339
+ const parts = ORIGIN_FILE_RE.exec(name);
340
+ if (!parts)
341
+ continue;
342
+ const backend = parts[1];
343
+ const nameId = parts[2];
344
+ const scoped = live !== undefined && (opts.liveBackend === undefined || opts.liveBackend === backend);
345
+ if (scoped && !live.has(nameId)) {
346
+ await unlink2(join2(leaseDir(), name)).catch(() => {
347
+ return;
348
+ });
349
+ continue;
350
+ }
351
+ const fromName = { backend, targetId: nameId, label: "", pid: 0, createdAt: 0 };
352
+ let rec;
353
+ try {
354
+ rec = JSON.parse(await readFile2(join2(leaseDir(), name), "utf8"));
355
+ } catch (err) {
356
+ const code = err.code;
357
+ if (code === "ENOENT")
358
+ continue;
359
+ out.push({ ...fromName, unreadable: code ?? "unparseable" });
360
+ continue;
361
+ }
362
+ if (typeof rec?.targetId !== "string" || typeof rec?.label !== "string" || typeof rec?.createdAt !== "number") {
363
+ out.push({ ...fromName, unreadable: "unparseable" });
364
+ continue;
365
+ }
366
+ out.push({ backend: rec.backend, targetId: rec.targetId, label: rec.label, pid: rec.pid, createdAt: rec.createdAt });
367
+ }
368
+ return out;
369
+ }
370
+ async function originIndex(backend, liveIds) {
371
+ const rows = await listOrigins({ liveIds, liveBackend: backend }).catch(() => []);
372
+ const index = new Map;
373
+ for (const row of rows) {
374
+ if (row.backend !== backend)
375
+ continue;
376
+ index.set(row.targetId, row);
377
+ }
378
+ return index;
379
+ }
380
+ async function newTrackedPage(driver, backend, opts = {}) {
381
+ const page = await driver.newPage(opts.url);
382
+ await recordOrigin(backend, page.id, { label: opts.label });
383
+ return page;
384
+ }
385
+ async function resolveLiveLabel(backend, label, liveIds) {
386
+ const live = new Set(liveIds);
387
+ const [origins, leases] = await Promise.all([originIndex(backend), listLeases({ liveBackend: backend })]);
388
+ const knownLabels = new Set;
389
+ const matchIds = new Set;
390
+ for (const rec of origins.values()) {
391
+ if (rec.unreadable !== undefined || !live.has(rec.targetId))
392
+ continue;
393
+ knownLabels.add(rec.label);
394
+ if (rec.label === label)
395
+ matchIds.add(rec.targetId);
396
+ }
397
+ for (const rec of leases) {
398
+ if (rec.backend !== backend || rec.unreadable !== undefined || !live.has(rec.targetId))
399
+ continue;
400
+ knownLabels.add(rec.label);
401
+ if (rec.label === label)
402
+ matchIds.add(rec.targetId);
403
+ }
404
+ return { matchIds: [...matchIds], knownLabels: [...knownLabels].sort() };
405
+ }
406
+
407
+ // src/workers.ts
408
+ var WORKER_SELECTOR_PREFIX = "worker:";
409
+ var WORKER_CAPABLE_TOOLS = [
410
+ "evaluate_script",
411
+ "list_network_requests",
412
+ "get_network_request",
413
+ "list_console_messages"
414
+ ];
415
+ var WORKER_TARGET_TYPES = new Set(["service_worker", "shared_worker"]);
416
+ function isWorkerSelector(selector) {
417
+ return typeof selector === "string" && selector.startsWith(WORKER_SELECTOR_PREFIX);
418
+ }
419
+ function workerNeedle(selector) {
420
+ return selector.slice(WORKER_SELECTOR_PREFIX.length);
421
+ }
422
+ function isWorkerTargetType(type) {
423
+ return type !== undefined && WORKER_TARGET_TYPES.has(type);
424
+ }
425
+ function resolveWorkerTargets(targets, needle) {
426
+ const liveWorkers = targets.filter((t) => isWorkerTargetType(t.type));
427
+ return {
428
+ matches: needle === "" ? [] : liveWorkers.filter((t) => t.url.includes(needle)),
429
+ liveWorkers
430
+ };
431
+ }
432
+ var WORKER_EMPTY_NEEDLE_MESSAGE = "'worker:' needs a substring of the worker's url, e.g. worker:<extension-id> or worker:background.js";
433
+ var WORKER_SELECTOR_UNSUPPORTED_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, which this backend cannot address " + "(WebDriver BiDi has no extension service-worker target; Capability 'worker.targets' is Chrome-only). " + "Use --browser chrome to evaluate in, or record console/network from, an extension's background worker.";
434
+ var WORKER_SELECTOR_PAGE_ONLY_MESSAGE = "target 'worker:<substring>' selects a service/shared worker, and this tool is page-only " + "(closing, selecting or releasing a worker is meaningless). The tools that accept a worker: target are " + `${WORKER_CAPABLE_TOOLS.join(", ")}.`;
435
+ function workerWakeMisuseMessage(kind) {
436
+ if (kind === "not-a-worker-selector") {
437
+ return "'wake' only applies to a target of the form 'worker:<substring>'. A page is never asleep, and a bare " + "worker target id cannot be woken either: Chrome destroys the target when an MV3 worker is evicted and " + "mints a NEW id when it restarts, so only the url-substring form can name a worker that is not running.";
438
+ }
439
+ return "'wake' only applies when a capture is being started (reload:true, or includeBody with url). Reading an " + "existing buffer cannot use a woken worker: a restarted MV3 worker gets a NEW target id, and the buffer is " + "keyed by id, so the wake would hand back an empty capture. Re-run with reload:true to record a fresh one.";
440
+ }
441
+ function workerBufferReadMissMessage(needle, liveWorkers) {
442
+ const running = liveWorkers.length ? ` (running workers: ${urlList(liveWorkers)})` : " (no workers are running)";
443
+ return `no running worker whose url contains '${needle}'${running}. ` + "An MV3 extension service worker is idle-evicted after seconds of inactivity and then appears in NO target " + "listing, so this is usually a sleeping worker rather than a wrong substring. This call only READS a buffer " + "recorded earlier, and that buffer is keyed by the worker's target id, which Chrome re-mints on restart — so " + "re-run with reload:true, which starts the worker and records a fresh capture window.";
444
+ }
445
+ function urlList(workers) {
446
+ return workers.map((w) => w.url).join(", ");
447
+ }
448
+ function workerAmbiguityMessage(needle, matches) {
449
+ return `'worker:${needle}' matches more than one running worker: ` + matches.map((m) => `${m.url} (${m.id})`).join(", ") + ". Narrow the substring, or pass the worker's target id directly.";
450
+ }
451
+ function workerMissMessage(needle, opts) {
452
+ const running = opts.liveWorkers?.length ? ` (running workers: ${urlList(opts.liveWorkers)})` : " (no workers are running)";
453
+ if (!opts.wakeAttempted) {
454
+ return `no running worker whose url contains '${needle}'${running}. ` + "An MV3 extension service worker is idle-evicted after seconds of inactivity and then appears in NO target " + "listing, so this is usually a sleeping worker rather than a wrong substring. You passed wake:false; " + "wake:true (the default) asks Chrome to start it first.";
455
+ }
456
+ const scopes = opts.registeredScopes?.length ? ` Registered service-worker scopes in this browser: ${opts.registeredScopes.join(", ")}.` : " No service-worker registrations were reported by this browser at all.";
457
+ return `no worker whose url contains '${needle}' could be reached${running}. ` + `A start was requested (wake:true) and no matching worker appeared.${scopes} ` + "If the extension is installed but its worker never starts, check the worker script for a top-level throw.";
458
+ }
459
+
281
460
  // src/client.ts
282
461
  var BASE = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
283
462
  var DEFAULT_TIMEOUT_MS = Number(process.env.CDP_TIMEOUT_MS ?? 15000);
@@ -433,11 +612,13 @@ async function browserWsUrl() {
433
612
  }
434
613
  async function resolveTarget(selector, opts = {}) {
435
614
  const targets = await listTargets();
436
- const hit = pickTarget(targets, selector);
615
+ const hit = await pickTarget(targets, selector);
616
+ if (isWorkerTargetType(hit.type))
617
+ return hit;
437
618
  await assertLeaseOk("chrome", hit.id, { lease: opts.lease, url: hit.url, title: hit.title });
438
619
  return hit;
439
620
  }
440
- function pickTarget(targets, selector) {
621
+ async function pickTarget(targets, selector) {
441
622
  const pages = targets.filter((t) => t.type === "page");
442
623
  if (!selector || selector === "active") {
443
624
  if (!pages.length)
@@ -464,6 +645,30 @@ function pickTarget(targets, selector) {
464
645
  throw new CdpError(`no page title containing '${needle}'`);
465
646
  return hit;
466
647
  }
648
+ if (selector.startsWith("label:")) {
649
+ const label = selector.slice(6);
650
+ const { matchIds, knownLabels } = await resolveLiveLabel("chrome", label, pages.map((t) => t.id));
651
+ if (matchIds.length > 1) {
652
+ throw new CdpError(`label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
653
+ }
654
+ if (matchIds.length === 1) {
655
+ const hit = pages.find((t) => t.id === matchIds[0]);
656
+ if (hit)
657
+ return hit;
658
+ }
659
+ throw new CdpError(knownLabels.length ? `no live target with label '${label}' (labels currently in use: ${knownLabels.join(", ")})` : `no live target with label '${label}' (no labels are currently assigned to any open target)`);
660
+ }
661
+ if (isWorkerSelector(selector)) {
662
+ const needle = workerNeedle(selector);
663
+ if (needle === "")
664
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
665
+ const { matches, liveWorkers } = resolveWorkerTargets(targets, needle);
666
+ if (matches.length > 1)
667
+ throw new CdpError(workerAmbiguityMessage(needle, matches));
668
+ if (matches.length === 1)
669
+ return matches[0];
670
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers }));
671
+ }
467
672
  const byId = targets.find((t) => t.id === selector);
468
673
  if (!byId)
469
674
  throw new CdpError(`no target with id '${selector}'`);
@@ -653,6 +858,91 @@ async function resolveUid(conn, uid) {
653
858
  return { objectId: object.objectId };
654
859
  }
655
860
 
861
+ // src/cdp/workers.ts
862
+ var INVENTORY_FIRST_EVENT_MS = 1500;
863
+ var INVENTORY_SETTLE_MS = 250;
864
+ var WAKE_POLL_MS = 300;
865
+ var WAKE_POLL_ATTEMPTS = 30;
866
+ var NO_HOST_PAGE_MESSAGE = "cannot start a service worker: the ServiceWorker CDP domain is only reachable over a page session, " + "and this browser has no page target open. Open any tab (new_page) and retry — the tab does not have " + "to belong to the extension.";
867
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
868
+ async function requestWorkerStart(needle) {
869
+ const conn = await openBrowser();
870
+ try {
871
+ const host = (await listTargets()).find((t) => t.type === "page");
872
+ if (!host)
873
+ throw new CdpError(NO_HOST_PAGE_MESSAGE);
874
+ const { sessionId } = await conn.send("Target.attachToTarget", {
875
+ targetId: host.id,
876
+ flatten: true
877
+ });
878
+ if (!sessionId)
879
+ throw new CdpError("could not attach a session to host the ServiceWorker domain");
880
+ const scopeById = new Map;
881
+ const versions = [];
882
+ conn.on("ServiceWorker.workerRegistrationUpdated", (params) => {
883
+ for (const r of params.registrations ?? []) {
884
+ if (r.isDeleted === true)
885
+ continue;
886
+ scopeById.set(String(r.registrationId), String(r.scopeURL ?? ""));
887
+ }
888
+ });
889
+ conn.on("ServiceWorker.workerVersionUpdated", (params) => {
890
+ for (const v of params.versions ?? []) {
891
+ versions.push({ registrationId: String(v.registrationId), scriptURL: String(v.scriptURL ?? "") });
892
+ }
893
+ });
894
+ await conn.send("ServiceWorker.enable", {}, { sessionId });
895
+ await conn.waitFor("ServiceWorker.workerVersionUpdated", undefined, INVENTORY_FIRST_EVENT_MS).catch(() => {
896
+ return;
897
+ });
898
+ await sleep(INVENTORY_SETTLE_MS);
899
+ const scopes = [...new Set(scopeById.values())].filter((s) => s.length > 0).sort();
900
+ const matched = new Set;
901
+ for (const v of versions) {
902
+ const scope = scopeById.get(v.registrationId);
903
+ if (scope === undefined)
904
+ continue;
905
+ if (v.scriptURL.includes(needle) || scope.includes(needle))
906
+ matched.add(scope);
907
+ }
908
+ if (matched.size === 0)
909
+ return { startRequested: false, scopes };
910
+ if (matched.size > 1) {
911
+ throw new CdpError(`'worker:${needle}' matches more than one registered service-worker scope: ${[...matched].sort().join(", ")}. ` + "Narrow the substring.");
912
+ }
913
+ await conn.send("ServiceWorker.startWorker", { scopeURL: [...matched][0] }, { sessionId });
914
+ return { startRequested: true, scopes };
915
+ } finally {
916
+ conn.close();
917
+ }
918
+ }
919
+ async function resolveWorkerSelector(selector, opts) {
920
+ const needle = workerNeedle(selector);
921
+ if (needle === "")
922
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
923
+ const first = resolveWorkerTargets(await listTargets(), needle);
924
+ if (first.matches.length > 1)
925
+ throw new CdpError(workerAmbiguityMessage(needle, first.matches));
926
+ if (first.matches.length === 1)
927
+ return first.matches[0];
928
+ if (!opts.wake) {
929
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers: first.liveWorkers }));
930
+ }
931
+ const { startRequested, scopes } = await requestWorkerStart(needle);
932
+ if (startRequested) {
933
+ for (let i = 0;i < WAKE_POLL_ATTEMPTS; i++) {
934
+ await sleep(WAKE_POLL_MS);
935
+ const again = resolveWorkerTargets(await listTargets(), needle);
936
+ if (again.matches.length > 1)
937
+ throw new CdpError(workerAmbiguityMessage(needle, again.matches));
938
+ if (again.matches.length === 1)
939
+ return again.matches[0];
940
+ }
941
+ }
942
+ const final = resolveWorkerTargets(await listTargets(), needle);
943
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: true, liveWorkers: final.liveWorkers, registeredScopes: scopes }));
944
+ }
945
+
656
946
  // src/tools/network_mock.ts
657
947
  function escapeRegExpChar(ch) {
658
948
  return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
@@ -1134,7 +1424,8 @@ var CDP_CAPABILITIES = new Set([
1134
1424
  "input.raw",
1135
1425
  "input.html5Drag",
1136
1426
  "browser.downloads",
1137
- "browser.permissions"
1427
+ "browser.permissions",
1428
+ "worker.targets"
1138
1429
  ]);
1139
1430
 
1140
1431
  class CdpPageDriver {
@@ -1702,6 +1993,10 @@ class CdpBrowserDriver {
1702
1993
  await withBrowserConn((conn) => conn.send("Target.activateTarget", { targetId: target.id }));
1703
1994
  return { id: target.id, url: target.url, title: target.title };
1704
1995
  }
1996
+ async resolveWorkerTarget(selector, opts) {
1997
+ const target = await resolveWorkerSelector(selector, opts).catch(noSuchTarget);
1998
+ return { id: target.id, url: target.url, title: target.title, type: target.type };
1999
+ }
1705
2000
  async page(selector) {
1706
2001
  const { conn, target } = await openPage(selector).catch(noSuchTarget);
1707
2002
  return new CdpPageDriver(conn, target, this);
@@ -1788,111 +2083,6 @@ function createCdpDriver() {
1788
2083
  return new CdpBrowserDriver;
1789
2084
  }
1790
2085
 
1791
- // src/origins.ts
1792
- import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
1793
- import { join as join2 } from "node:path";
1794
- function safeId2(id) {
1795
- return id.replace(/[^A-Za-z0-9._-]/g, "_");
1796
- }
1797
- function originFile(backend, targetId) {
1798
- return join2(leaseDir(), `origin-${backend}-${safeId2(targetId)}.json`);
1799
- }
1800
- var ORIGIN_FILE_RE = /^origin-(chrome|firefox)-(.+)\.json$/;
1801
- async function recordOrigin(backend, targetId, opts = {}) {
1802
- const record = {
1803
- backend,
1804
- targetId,
1805
- label: typeof opts.label === "string" && opts.label.length ? opts.label : defaultLabel(),
1806
- pid: process.pid,
1807
- createdAt: opts.now ?? Date.now()
1808
- };
1809
- const file = originFile(backend, targetId);
1810
- try {
1811
- await mkdir2(leaseDir(), { recursive: true });
1812
- try {
1813
- await writeFile2(file, JSON.stringify(record), { flag: "wx" });
1814
- return;
1815
- } catch (err) {
1816
- if (err.code !== "EEXIST")
1817
- throw err;
1818
- }
1819
- await unlink2(file).catch(() => {
1820
- return;
1821
- });
1822
- await writeFile2(file, JSON.stringify(record), { flag: "wx" });
1823
- } catch {}
1824
- }
1825
- async function readOrigin(backend, targetId) {
1826
- let raw;
1827
- try {
1828
- raw = await readFile2(originFile(backend, targetId), "utf8");
1829
- } catch {
1830
- return;
1831
- }
1832
- try {
1833
- const rec = JSON.parse(raw);
1834
- if (typeof rec?.targetId === "string" && typeof rec?.label === "string" && typeof rec?.createdAt === "number")
1835
- return rec;
1836
- } catch {}
1837
- return;
1838
- }
1839
- async function listOrigins(opts = {}) {
1840
- let names = [];
1841
- try {
1842
- names = await readdir2(leaseDir());
1843
- } catch {
1844
- return [];
1845
- }
1846
- const live = opts.liveIds === undefined ? undefined : new Set(opts.liveIds.map((id) => safeId2(id)));
1847
- const out = [];
1848
- for (const name of names) {
1849
- const parts = ORIGIN_FILE_RE.exec(name);
1850
- if (!parts)
1851
- continue;
1852
- const backend = parts[1];
1853
- const nameId = parts[2];
1854
- const scoped = live !== undefined && (opts.liveBackend === undefined || opts.liveBackend === backend);
1855
- if (scoped && !live.has(nameId)) {
1856
- await unlink2(join2(leaseDir(), name)).catch(() => {
1857
- return;
1858
- });
1859
- continue;
1860
- }
1861
- const fromName = { backend, targetId: nameId, label: "", pid: 0, createdAt: 0 };
1862
- let rec;
1863
- try {
1864
- rec = JSON.parse(await readFile2(join2(leaseDir(), name), "utf8"));
1865
- } catch (err) {
1866
- const code = err.code;
1867
- if (code === "ENOENT")
1868
- continue;
1869
- out.push({ ...fromName, unreadable: code ?? "unparseable" });
1870
- continue;
1871
- }
1872
- if (typeof rec?.targetId !== "string" || typeof rec?.label !== "string" || typeof rec?.createdAt !== "number") {
1873
- out.push({ ...fromName, unreadable: "unparseable" });
1874
- continue;
1875
- }
1876
- out.push({ backend: rec.backend, targetId: rec.targetId, label: rec.label, pid: rec.pid, createdAt: rec.createdAt });
1877
- }
1878
- return out;
1879
- }
1880
- async function originIndex(backend, liveIds) {
1881
- const rows = await listOrigins({ liveIds, liveBackend: backend }).catch(() => []);
1882
- const index = new Map;
1883
- for (const row of rows) {
1884
- if (row.backend !== backend)
1885
- continue;
1886
- index.set(row.targetId, row);
1887
- }
1888
- return index;
1889
- }
1890
- async function newTrackedPage(driver, backend, opts = {}) {
1891
- const page = await driver.newPage(opts.url);
1892
- await recordOrigin(backend, page.id, { label: opts.label });
1893
- return page;
1894
- }
1895
-
1896
2086
  // src/reap.ts
1897
2087
  function staleAgentTabs(input) {
1898
2088
  const live = new Set(input.livePageIds);
@@ -1993,6 +2183,11 @@ async function pickPage(driver, pages, selector) {
1993
2183
  throw new SharedToolError(`no page title containing '${needle}'`);
1994
2184
  return p;
1995
2185
  }
2186
+ if (selector.startsWith("label:"))
2187
+ return pickByLabel(driver, pages, selector.slice(6));
2188
+ if (isWorkerSelector(selector)) {
2189
+ throw new SharedToolError(backendOf(driver) === "firefox" ? WORKER_SELECTOR_UNSUPPORTED_MESSAGE : WORKER_SELECTOR_PAGE_ONLY_MESSAGE);
2190
+ }
1996
2191
  const exactPage = pages.find((x) => x.id === selector);
1997
2192
  if (exactPage)
1998
2193
  return exactPage;
@@ -2002,6 +2197,18 @@ async function pickPage(driver, pages, selector) {
2002
2197
  return exact;
2003
2198
  throw new SharedToolError(`no target with id '${selector}'`);
2004
2199
  }
2200
+ async function pickByLabel(driver, pages, label) {
2201
+ const { matchIds, knownLabels } = await resolveLiveLabel(backendOf(driver), label, pages.map((x) => x.id));
2202
+ if (matchIds.length > 1) {
2203
+ throw new SharedToolError(`label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
2204
+ }
2205
+ if (matchIds.length === 1) {
2206
+ const p = pages.find((x) => x.id === matchIds[0]);
2207
+ if (p)
2208
+ return p;
2209
+ }
2210
+ throw new SharedToolError(knownLabels.length ? `no live target with label '${label}' (labels currently in use: ${knownLabels.join(", ")})` : `no live target with label '${label}' (no labels are currently assigned to any open target)`);
2211
+ }
2005
2212
  function locatorOf(args) {
2006
2213
  const hasUid = args.uid !== undefined && args.uid !== null && args.uid !== "";
2007
2214
  const hasSelector = typeof args.selector === "string" && args.selector.length > 0;
@@ -2143,11 +2350,29 @@ async function writeJsonSink(savePath, value) {
2143
2350
  function sinkRequested(savePath) {
2144
2351
  return savePath !== undefined && savePath !== "";
2145
2352
  }
2353
+ var EXPRESSION_ALIAS_KEYS = ["function", "code", "js", "script", "fn", "body"];
2354
+ async function resolveWorkerSelectorFor(driver, selector, wake) {
2355
+ if (!driver.capabilities.has("worker.targets") || typeof driver.resolveWorkerTarget !== "function") {
2356
+ throw new SharedToolError(`evaluateScript: ${WORKER_SELECTOR_UNSUPPORTED_MESSAGE}`);
2357
+ }
2358
+ return driver.resolveWorkerTarget(selector, { wake });
2359
+ }
2146
2360
  async function evaluateScript(driver, args) {
2147
2361
  if (typeof args.expression !== "string" || args.expression.length === 0) {
2362
+ const wrongKey = EXPRESSION_ALIAS_KEYS.find((k) => args[k] !== undefined);
2363
+ if (wrongKey !== undefined) {
2364
+ throw new SharedToolError(`evaluateScript: you passed '${wrongKey}'; the key is 'expression' — function literals go in 'expression', invoked with 'args'.`);
2365
+ }
2148
2366
  throw new SharedToolError("evaluateScript: 'expression' must be a non-empty string");
2149
2367
  }
2150
- return withPage2(driver, args.target, async (page) => {
2368
+ if (args.wake !== undefined && typeof args.wake !== "boolean") {
2369
+ throw new SharedToolError("evaluateScript: 'wake' must be a boolean");
2370
+ }
2371
+ if (args.wake !== undefined && !isWorkerSelector(args.target)) {
2372
+ throw new SharedToolError("evaluateScript: 'wake' only applies to a target of the form 'worker:<substring>'; a page target is never asleep.");
2373
+ }
2374
+ const target = isWorkerSelector(args.target) ? (await resolveWorkerSelectorFor(driver, args.target, args.wake ?? true)).id : args.target;
2375
+ return withPage2(driver, target, async (page) => {
2151
2376
  const value = await page.evaluate(args.expression, { args: args.args, awaitPromise: args.awaitPromise ?? true });
2152
2377
  if (!sinkRequested(args.savePath))
2153
2378
  return value;
@@ -2511,7 +2736,7 @@ async function claimPage(driver, args = {}) {
2511
2736
  const hasTarget = typeof args.target === "string" && args.target.length > 0;
2512
2737
  const hasTargetId = typeof args.targetId === "string" && args.targetId.length > 0;
2513
2738
  if (hasTarget && hasTargetId) {
2514
- throw new LeaseToolError("claim_page takes at most one of 'target' (any selector: active | index:N | url:<substr> | title:<substr> | <targetId>) or 'targetId' (an exact target id, kept for back-compat). Pass 'target' alone to claim an open tab, or neither to open a fresh one.");
2739
+ throw new LeaseToolError("claim_page takes at most one of 'target' (any selector: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>) or 'targetId' (an exact target id, kept for back-compat). Pass 'target' alone to claim an open tab, or neither to open a fresh one.");
2515
2740
  }
2516
2741
  const backend = backendOf2(driver);
2517
2742
  const label = typeof args.label === "string" && args.label.length ? args.label : defaultLabel();
@@ -2631,6 +2856,31 @@ function captureFile(targetId, captureId) {
2631
2856
  function delay2(ms) {
2632
2857
  return new Promise((r) => setTimeout(r, ms));
2633
2858
  }
2859
+ async function resolveRecorderTarget(target, opts) {
2860
+ if (!isWorkerSelector(target))
2861
+ return resolveTarget(target);
2862
+ if (opts.capture)
2863
+ return resolveWorkerSelector(target, { wake: opts.wake ?? true });
2864
+ const needle = workerNeedle(target);
2865
+ if (needle === "")
2866
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
2867
+ const { matches, liveWorkers } = resolveWorkerTargets(await listTargets(), needle);
2868
+ if (matches.length > 1)
2869
+ throw new CdpError(workerAmbiguityMessage(needle, matches));
2870
+ if (matches.length === 1)
2871
+ return matches[0];
2872
+ throw new CdpError(workerBufferReadMissMessage(needle, liveWorkers));
2873
+ }
2874
+ function assertWakeApplies(target, wake, capture) {
2875
+ if (wake === undefined)
2876
+ return;
2877
+ if (typeof wake !== "boolean")
2878
+ throw new CdpError("'wake' must be a boolean");
2879
+ if (!isWorkerSelector(target))
2880
+ throw new CdpError(workerWakeMisuseMessage("not-a-worker-selector"));
2881
+ if (!capture)
2882
+ throw new CdpError(workerWakeMisuseMessage("read-only-call"));
2883
+ }
2634
2884
  var NETWORK_EVENTS = [
2635
2885
  "Network.requestWillBeSent",
2636
2886
  "Network.responseReceived",
@@ -2696,7 +2946,8 @@ async function startRecorder(target, opts = {}) {
2696
2946
  };
2697
2947
  }
2698
2948
  async function captureWindow(target, durationMs, opts = {}) {
2699
- const resolved = await resolveTarget(target);
2949
+ const resolved = await resolveRecorderTarget(target, { capture: true, wake: opts.wake });
2950
+ const isWorker = isWorkerTargetType(resolved.type);
2700
2951
  const captureId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
2701
2952
  const file = captureFile(resolved.id, captureId);
2702
2953
  const rec = await startRecorder(resolved.id, {
@@ -2706,13 +2957,16 @@ async function captureWindow(target, durationMs, opts = {}) {
2706
2957
  file,
2707
2958
  timeoutMs: opts.timeoutMs
2708
2959
  });
2709
- await rec.conn.send("Page.enable");
2710
- await rec.conn.send("Page.reload", { ignoreCache: false });
2960
+ if (!isWorker) {
2961
+ await rec.conn.send("Page.enable");
2962
+ await rec.conn.send("Page.reload", { ignoreCache: false });
2963
+ }
2711
2964
  await delay2(durationMs);
2712
2965
  return {
2713
2966
  file: rec.file,
2714
2967
  conn: rec.conn,
2715
2968
  resolved: { id: rec.target.id, url: rec.target.url, title: rec.target.title },
2969
+ mode: isWorker ? "listen" : "reload",
2716
2970
  droppedWrites: rec.droppedWrites,
2717
2971
  flush: () => rec.flush(),
2718
2972
  async stop() {
@@ -2812,8 +3066,9 @@ async function readConsoleEntries(file) {
2812
3066
  return out;
2813
3067
  }
2814
3068
  async function listConsoleMessages(args = {}) {
3069
+ assertWakeApplies(args.target, args.wake, args.reload === true);
2815
3070
  if (args.reload) {
2816
- const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS);
3071
+ const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS, { wake: args.wake });
2817
3072
  await cap.stop();
2818
3073
  const messages2 = await readConsoleEntries(cap.file);
2819
3074
  return {
@@ -2823,7 +3078,7 @@ async function listConsoleMessages(args = {}) {
2823
3078
  droppedWrites: cap.droppedWrites()
2824
3079
  };
2825
3080
  }
2826
- const target = await resolveTarget(args.target);
3081
+ const target = await resolveRecorderTarget(args.target, { capture: false });
2827
3082
  const messages = await readConsoleEntries(recFile(target.id));
2828
3083
  return {
2829
3084
  target: { id: target.id, url: target.url, title: target.title },
@@ -2832,7 +3087,7 @@ async function listConsoleMessages(args = {}) {
2832
3087
  };
2833
3088
  }
2834
3089
  async function getConsoleMessage(args = {}) {
2835
- const target = await resolveTarget(args.target);
3090
+ const target = await resolveRecorderTarget(args.target, { capture: false });
2836
3091
  const messages = await readConsoleEntries(recFile(target.id));
2837
3092
  const index = args.index ?? 0;
2838
3093
  const entry = messages[index];
@@ -2930,14 +3185,15 @@ async function listNetworkRequests(args = {}) {
2930
3185
  let file;
2931
3186
  let resolved;
2932
3187
  let droppedWrites;
3188
+ assertWakeApplies(args.target, args.wake, args.reload === true);
2933
3189
  if (args.reload) {
2934
- const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS2);
3190
+ const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS2, { wake: args.wake });
2935
3191
  await cap.stop();
2936
3192
  file = cap.file;
2937
3193
  resolved = cap.resolved;
2938
3194
  droppedWrites = cap.droppedWrites();
2939
3195
  } else {
2940
- const target = await resolveTarget(args.target);
3196
+ const target = await resolveRecorderTarget(args.target, { capture: false });
2941
3197
  file = recFile(target.id);
2942
3198
  resolved = { id: target.id, url: target.url, title: target.title };
2943
3199
  }
@@ -2958,8 +3214,9 @@ async function getNetworkRequest(args = {}) {
2958
3214
  return requests.find((r) => r.url.includes(args.url));
2959
3215
  };
2960
3216
  const describeSelector = () => args.requestId ? `requestId ${args.requestId}` : `url ~ '${args.url}'`;
3217
+ assertWakeApplies(args.target, args.wake, Boolean(args.includeBody && args.url));
2961
3218
  if (args.includeBody && args.url) {
2962
- const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS2);
3219
+ const cap = await captureWindow(args.target, args.durationMs ?? DEFAULT_CAPTURE_MS2, { wake: args.wake });
2963
3220
  try {
2964
3221
  await cap.flush();
2965
3222
  const match2 = (await readNetworkRequests(cap.file)).find((r) => r.url.includes(args.url));
@@ -2976,7 +3233,7 @@ async function getNetworkRequest(args = {}) {
2976
3233
  await cap.stop();
2977
3234
  }
2978
3235
  }
2979
- const target = await resolveTarget(args.target);
3236
+ const target = await resolveRecorderTarget(args.target, { capture: false });
2980
3237
  const match = pick(await readNetworkRequests(recFile(target.id)));
2981
3238
  if (!match) {
2982
3239
  throw new CdpError(`no network request matched ${describeSelector()}; run list_network_requests with reload:true first` + (args.includeBody ? `, then fetch the body with the url selector (reload re-mints requestIds)` : ``));
@@ -4157,7 +4414,7 @@ var MANIFEST = [
4157
4414
  },
4158
4415
  target: {
4159
4416
  type: "string",
4160
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4417
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4161
4418
  }
4162
4419
  },
4163
4420
  required: [
@@ -4178,7 +4435,7 @@ var MANIFEST = [
4178
4435
  },
4179
4436
  target: {
4180
4437
  type: "string",
4181
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4438
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4182
4439
  }
4183
4440
  },
4184
4441
  required: [
@@ -4199,7 +4456,7 @@ var MANIFEST = [
4199
4456
  },
4200
4457
  target: {
4201
4458
  type: "string",
4202
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4459
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4203
4460
  },
4204
4461
  url: {
4205
4462
  type: "string",
@@ -4249,7 +4506,7 @@ var MANIFEST = [
4249
4506
  },
4250
4507
  target: {
4251
4508
  type: "string",
4252
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4509
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4253
4510
  },
4254
4511
  text: {
4255
4512
  type: "string",
@@ -4272,7 +4529,7 @@ var MANIFEST = [
4272
4529
  },
4273
4530
  {
4274
4531
  name: "evaluate_script",
4275
- description: "Run arbitrary JavaScript in the target page's main-world context over raw CDP and return the evaluated value (returnByValue). With no 'args' the 'expression' is evaluated as a raw expression; when 'args' is provided 'expression' must be a function literal (arrow or classic) invoked on globalThis with the args passed positionally. A thrown exception surfaces as an error; non-serializable returns (DOM nodes, functions) come back as their CDP description string. Pass 'savePath' to write the evaluated value to a JSON file instead: the response then carries only {path,bytes,type,target} and the value itself never appears in it, in any form. That is the way to read a credential (a JWT or session token out of localStorage, for example) without putting the secret into the caller's transcript.",
4532
+ description: "Run arbitrary JavaScript in the target page's main-world context over raw CDP and return the evaluated value (returnByValue). With no 'args' the 'expression' is evaluated as a raw expression; when 'args' is provided 'expression' must be a function literal (arrow or classic) invoked on globalThis with the args passed positionally. A thrown exception surfaces as an error; non-serializable returns (DOM nodes, functions) come back as their CDP description string. Pass 'savePath' to write the evaluated value to a JSON file instead: the response then carries only {path,bytes,type,target} and the value itself never appears in it, in any form. That is the way to read a credential (a JWT or session token out of localStorage, for example) without putting the secret into the caller's transcript. This is the one tool that can EVALUATE inside an MV3 extension's background SERVICE WORKER rather than a page (list_network_requests/get_network_request/list_console_messages OBSERVE one): pass target 'worker:<extension-id>' (Chrome only) and read chrome.storage.local, chrome.runtime and the worker's own globals directly, with an idle-evicted worker started for you first (see 'wake').",
4276
4533
  inputSchema: {
4277
4534
  type: "object",
4278
4535
  properties: {
@@ -4282,11 +4539,15 @@ var MANIFEST = [
4282
4539
  },
4283
4540
  target: {
4284
4541
  type: "string",
4285
- description: "Page selector: 'active' (or omit) -> first page-type target | '<32-hex targetId>' -> exact target by id | 'index:N' -> Nth page-type target (0-based) | 'url:<substring>' -> first page whose url contains substring | 'title:<substring>' -> first page whose title contains substring."
4542
+ description: "Page selector: 'active' (or omit) -> first page-type target | '<32-hex targetId>' -> exact target by id | 'index:N' -> Nth page-type target (0-based) | 'url:<substring>' -> first page whose url contains substring | 'title:<substring>' -> first page whose title contains substring | 'label:<name>' -> the page-type target with exactly this label (checked against the origin ledger and live leases) | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js) and read chrome.storage.local from it. The worker: arm is CHROME-ONLY (Capability 'worker.targets') and is accepted by four tools only — evaluate_script, list_network_requests, get_network_request and list_console_messages — never as universal grammar; an MV3 worker that has been idle-evicted is started automatically first, see 'wake'."
4543
+ },
4544
+ wake: {
4545
+ type: "boolean",
4546
+ description: "Only valid with a 'worker:<substring>' target, and rejected on a page target rather than ignored. Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so a wake asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything; the error then says the worker may simply be asleep."
4286
4547
  },
4287
4548
  expression: {
4288
4549
  type: "string",
4289
- description: "JavaScript to run. Evaluated as an expression when 'args' is omitted; must be a function literal (arrow or classic) whose parameters receive 'args' when 'args' is provided."
4550
+ description: "JavaScript to run. Evaluated as an expression when 'args' is omitted; must be a function literal (arrow or classic) whose parameters receive 'args' when 'args' is provided. REQUIRED — the code always goes in this key, never in 'function', 'code', 'js', 'script', 'fn' or 'body'; a call using one of those instead is rejected with an error naming the wrong key."
4290
4551
  },
4291
4552
  awaitPromise: {
4292
4553
  type: "boolean",
@@ -4319,7 +4580,7 @@ var MANIFEST = [
4319
4580
  },
4320
4581
  target: {
4321
4582
  type: "string",
4322
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4583
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4323
4584
  },
4324
4585
  domain: {
4325
4586
  type: "string",
@@ -4350,7 +4611,7 @@ var MANIFEST = [
4350
4611
  },
4351
4612
  target: {
4352
4613
  type: "string",
4353
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4614
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4354
4615
  },
4355
4616
  name: {
4356
4617
  type: "string",
@@ -4406,7 +4667,7 @@ var MANIFEST = [
4406
4667
  },
4407
4668
  target: {
4408
4669
  type: "string",
4409
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4670
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4410
4671
  },
4411
4672
  name: {
4412
4673
  type: "string",
@@ -4441,7 +4702,7 @@ var MANIFEST = [
4441
4702
  },
4442
4703
  target: {
4443
4704
  type: "string",
4444
- description: "Target page selector: 'active' (default, = first page-type target) | 'index:N' (Nth page-type target, 0-based) | 'url:<substring>' | 'title:<substring>' | '<32-hex targetId>' (exact target by id)."
4705
+ description: "Target page selector: 'active' (default, = first page-type target) | 'index:N' (Nth page-type target, 0-based) | 'url:<substring>' | 'title:<substring>' | 'label:<name>' (exact label match, origin ledger or live lease) | '<32-hex targetId>' (exact target by id)."
4445
4706
  },
4446
4707
  interactiveOnly: {
4447
4708
  type: "boolean",
@@ -4464,7 +4725,7 @@ var MANIFEST = [
4464
4725
  },
4465
4726
  target: {
4466
4727
  type: "string",
4467
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4728
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4468
4729
  },
4469
4730
  uid: {
4470
4731
  type: "number",
@@ -4517,7 +4778,7 @@ var MANIFEST = [
4517
4778
  },
4518
4779
  target: {
4519
4780
  type: "string",
4520
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4781
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4521
4782
  },
4522
4783
  uid: {
4523
4784
  type: "number",
@@ -4544,7 +4805,7 @@ var MANIFEST = [
4544
4805
  },
4545
4806
  target: {
4546
4807
  type: "string",
4547
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4808
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4548
4809
  },
4549
4810
  from: {
4550
4811
  type: "object",
@@ -4630,7 +4891,7 @@ var MANIFEST = [
4630
4891
  },
4631
4892
  target: {
4632
4893
  type: "string",
4633
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4894
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4634
4895
  },
4635
4896
  uid: {
4636
4897
  type: "number",
@@ -4673,7 +4934,7 @@ var MANIFEST = [
4673
4934
  },
4674
4935
  target: {
4675
4936
  type: "string",
4676
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4937
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4677
4938
  },
4678
4939
  action: {
4679
4940
  type: "string",
@@ -4739,7 +5000,7 @@ var MANIFEST = [
4739
5000
  },
4740
5001
  target: {
4741
5002
  type: "string",
4742
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'. A download is browser-scoped, so this names the tab for the lease check and for the echoed target, not which download is returned."
5003
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'. A download is browser-scoped, so this names the tab for the lease check and for the echoed target, not which download is returned."
4743
5004
  },
4744
5005
  arm: {
4745
5006
  type: "boolean",
@@ -4766,7 +5027,7 @@ var MANIFEST = [
4766
5027
  },
4767
5028
  target: {
4768
5029
  type: "string",
4769
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'. Used for the lease check and, when 'origin' is omitted, as the source of the origin to grant for."
5030
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'. Used for the lease check and, when 'origin' is omitted, as the source of the origin to grant for."
4770
5031
  },
4771
5032
  permissions: {
4772
5033
  type: "array",
@@ -4800,7 +5061,7 @@ var MANIFEST = [
4800
5061
  },
4801
5062
  target: {
4802
5063
  type: "string",
4803
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5064
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4804
5065
  },
4805
5066
  uid: {
4806
5067
  type: "number",
@@ -4833,7 +5094,7 @@ var MANIFEST = [
4833
5094
  },
4834
5095
  target: {
4835
5096
  type: "string",
4836
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5097
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4837
5098
  },
4838
5099
  fields: {
4839
5100
  type: "array",
@@ -4879,7 +5140,7 @@ var MANIFEST = [
4879
5140
  },
4880
5141
  target: {
4881
5142
  type: "string",
4882
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5143
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4883
5144
  },
4884
5145
  uid: {
4885
5146
  type: "number",
@@ -4912,7 +5173,7 @@ var MANIFEST = [
4912
5173
  },
4913
5174
  target: {
4914
5175
  type: "string",
4915
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5176
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4916
5177
  },
4917
5178
  key: {
4918
5179
  type: "string",
@@ -4944,7 +5205,7 @@ var MANIFEST = [
4944
5205
  },
4945
5206
  target: {
4946
5207
  type: "string",
4947
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5208
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4948
5209
  },
4949
5210
  uid: {
4950
5211
  type: "number",
@@ -4983,7 +5244,7 @@ var MANIFEST = [
4983
5244
  },
4984
5245
  target: {
4985
5246
  type: "string",
4986
- description: "Target page selector. undefined or 'active' -> first page target; '<32-hex targetId>' -> exact target by id; 'index:N' -> Nth page target (0-based); 'url:<substring>' -> first page whose url contains substring; 'title:<substring>' -> first page whose title contains substring."
5247
+ description: "Target page selector. undefined or 'active' -> first page target; '<32-hex targetId>' -> exact target by id; 'index:N' -> Nth page target (0-based); 'url:<substring>' -> first page whose url contains substring; 'title:<substring>' -> first page whose title contains substring; 'label:<name>' -> the page with exactly this label (origin ledger or live lease)."
4987
5248
  },
4988
5249
  format: {
4989
5250
  type: "string",
@@ -5034,7 +5295,7 @@ var MANIFEST = [
5034
5295
  },
5035
5296
  target: {
5036
5297
  type: "string",
5037
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5298
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5038
5299
  },
5039
5300
  width: {
5040
5301
  type: "number",
@@ -5134,7 +5395,7 @@ var MANIFEST = [
5134
5395
  },
5135
5396
  target: {
5136
5397
  type: "string",
5137
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5398
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5138
5399
  },
5139
5400
  width: {
5140
5401
  type: "number",
@@ -5172,7 +5433,7 @@ var MANIFEST = [
5172
5433
  },
5173
5434
  target: {
5174
5435
  type: "string",
5175
- description: "Target page selector: 'active' (or omitted) for the first page-type target | 'index:N' (0-based) | 'url:<substring>' | 'title:<substring>' | a 32-hex <targetId>."
5436
+ description: "Target page selector: 'active' (or omitted) for the first page-type target | 'index:N' (0-based) | 'url:<substring>' | 'title:<substring>' | 'label:<name>' (exact label match, origin ledger or live lease) | a 32-hex <targetId>."
5176
5437
  },
5177
5438
  accept: {
5178
5439
  type: "boolean",
@@ -5199,7 +5460,7 @@ var MANIFEST = [
5199
5460
  },
5200
5461
  {
5201
5462
  name: "list_console_messages",
5202
- description: "Read console output (logs, warnings, exceptions) captured for the target page. By default reads the target's existing shared buffer and returns parsed console entries (empty if no capture has run); with reload:true it reloads the page and records a fresh capture window (both console+network) so a network reload never wipes console history.",
5463
+ description: "Read console output (logs, warnings, exceptions) captured for the target page. By default reads the target's existing shared buffer and returns parsed console entries (empty if no capture has run); with reload:true it reloads the page and records a fresh capture window (both console+network) so a network reload never wipes console history. SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker's console.log arrives as Runtime.consoleAPICalled on its own session. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
5203
5464
  inputSchema: {
5204
5465
  type: "object",
5205
5466
  properties: {
@@ -5209,15 +5470,19 @@ var MANIFEST = [
5209
5470
  },
5210
5471
  target: {
5211
5472
  type: "string",
5212
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5473
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
5213
5474
  },
5214
5475
  reload: {
5215
5476
  type: "boolean",
5216
- description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer)."
5477
+ description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer). With a worker: target there is nothing to reload: the window records for durationMs while the worker runs, and the session it holds keeps that worker from being idle-evicted until the capture stops."
5217
5478
  },
5218
5479
  durationMs: {
5219
5480
  type: "number",
5220
5481
  description: "Capture window for reload mode, in milliseconds. Default 2500."
5482
+ },
5483
+ wake: {
5484
+ type: "boolean",
5485
+ description: "Worker targets only, and only when a capture is being started (reload:true). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
5221
5486
  }
5222
5487
  },
5223
5488
  required: [],
@@ -5236,7 +5501,7 @@ var MANIFEST = [
5236
5501
  },
5237
5502
  target: {
5238
5503
  type: "string",
5239
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5504
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5240
5505
  },
5241
5506
  index: {
5242
5507
  type: "number",
@@ -5249,7 +5514,7 @@ var MANIFEST = [
5249
5514
  },
5250
5515
  {
5251
5516
  name: "list_network_requests",
5252
- description: "Return correlated network request rows (one per requestId, with status/headers/state) for the target page. By default reads the target's existing buffer; with reload:true it reloads and records a fresh both-domains capture window. Use filterUrl to keep only requests whose URL contains a substring.",
5517
+ description: "Return correlated network request rows (one per requestId, with status/headers/state) for the target page. By default reads the target's existing buffer; with reload:true it reloads and records a fresh both-domains capture window. Use filterUrl to keep only requests whose URL contains a substring. SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
5253
5518
  inputSchema: {
5254
5519
  type: "object",
5255
5520
  properties: {
@@ -5259,11 +5524,11 @@ var MANIFEST = [
5259
5524
  },
5260
5525
  target: {
5261
5526
  type: "string",
5262
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5527
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
5263
5528
  },
5264
5529
  reload: {
5265
5530
  type: "boolean",
5266
- description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer)."
5531
+ description: "Record fresh by reloading the page and capturing for a window. Default false (read the existing buffer). With a worker: target there is nothing to reload: the window records for durationMs while the worker runs, and the session it holds keeps that worker from being idle-evicted until the capture stops."
5267
5532
  },
5268
5533
  durationMs: {
5269
5534
  type: "number",
@@ -5272,6 +5537,10 @@ var MANIFEST = [
5272
5537
  filterUrl: {
5273
5538
  type: "string",
5274
5539
  description: "Only return requests whose URL contains this substring."
5540
+ },
5541
+ wake: {
5542
+ type: "boolean",
5543
+ description: "Worker targets only, and only when a capture is being started (reload:true). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
5275
5544
  }
5276
5545
  },
5277
5546
  required: [],
@@ -5280,7 +5549,7 @@ var MANIFEST = [
5280
5549
  },
5281
5550
  {
5282
5551
  name: "get_network_request",
5283
- description: "Return one network request (matched by exact requestId, else by url substring) including status/headers. Requires at least one of requestId or url (throws otherwise). With includeBody:true the body fetch drives a fresh reload capture and is matched by url ONLY (reload re-mints requestIds, so a carried-over requestId cannot fetch a body, so it returns metadata plus bodyUnavailableReason).",
5552
+ description: "Return one network request (matched by exact requestId, else by url substring) including status/headers. Requires at least one of requestId or url (throws otherwise). With includeBody:true the body fetch drives a fresh capture (a reload for a page, a listen window for a worker) and is matched by url ONLY (a fresh capture re-mints requestIds, so a carried-over requestId cannot fetch a body, so it returns metadata plus bodyUnavailableReason). SERVICE WORKERS (Chrome only, Capability 'worker.targets'): pass target 'worker:<extension-id>' to watch an MV3 extension's BACKGROUND SERVICE WORKER. The outbound request that worker makes to your backend is recorded from CDP's Network domain on the worker's own session, which observes what the code ACTUALLY SENT and so needs neither of the two tricks a caller reaches for first. Both of those fail for ordinary JavaScript reasons rather than service-worker ones: a value bound at module top level is not visible from evaluate_script's global scope (plain lexical scoping, exactly the same in a page), and assigning self.fetch cannot rebind a fetch reference the module already captured. A worker capture has no reload to drive it: reload:true means 'listen for durationMs while the worker runs', so trigger the worker's work (another tool call, an extension message, an alarm) during the window; a worker woken by the call has already run its top-level code by the time the recorder attaches, so a request it makes at startup can be missed. SIDE EFFECT, deliberate and documented: the recording holds a CDP session on the worker, and that KEEPS THE WORKER ALIVE for as long as the capture runs — an MV3 worker is otherwise idle-evicted within seconds — and Chrome resumes evicting it once the capture stops.",
5284
5553
  inputSchema: {
5285
5554
  type: "object",
5286
5555
  properties: {
@@ -5290,7 +5559,7 @@ var MANIFEST = [
5290
5559
  },
5291
5560
  target: {
5292
5561
  type: "string",
5293
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5562
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>' | 'worker:<substring>' -> a service/shared worker whose url contains the substring, which is how you reach an MV3 extension's background service worker (worker:<extension-id> or worker:background.js). The worker: arm is CHROME-ONLY (Capability 'worker.targets'), is refused on Firefox, and a bare worker target id works too."
5294
5563
  },
5295
5564
  requestId: {
5296
5565
  type: "string",
@@ -5307,6 +5576,10 @@ var MANIFEST = [
5307
5576
  durationMs: {
5308
5577
  type: "number",
5309
5578
  description: "Capture window for the reload-driven body fetch, in milliseconds. Default 2500."
5579
+ },
5580
+ wake: {
5581
+ type: "boolean",
5582
+ description: "Worker targets only, and only when a capture is being started (includeBody together with url). Default true: an MV3 extension service worker is idle-evicted after seconds of inactivity and then exists in NO target listing, so the capture asks Chrome to start it (ServiceWorker.startWorker over a page session) and then re-reads the target list to confirm it actually came up. Pass false to fail fast instead of starting anything. Rejected rather than ignored on a page target, on a bare target id (Chrome re-mints a worker's target id when it restarts, so only the url-substring form can name a worker that is not running), and on a read-only call (the buffer is keyed by target id, so a woken worker's buffer would be empty)."
5310
5583
  }
5311
5584
  },
5312
5585
  required: [],
@@ -5325,7 +5598,7 @@ var MANIFEST = [
5325
5598
  },
5326
5599
  target: {
5327
5600
  type: "string",
5328
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5601
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5329
5602
  },
5330
5603
  categories: {
5331
5604
  type: "array",
@@ -5351,7 +5624,7 @@ var MANIFEST = [
5351
5624
  },
5352
5625
  target: {
5353
5626
  type: "string",
5354
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5627
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5355
5628
  }
5356
5629
  },
5357
5630
  required: [],
@@ -5389,7 +5662,7 @@ var MANIFEST = [
5389
5662
  },
5390
5663
  target: {
5391
5664
  type: "string",
5392
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5665
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5393
5666
  },
5394
5667
  durationMs: {
5395
5668
  type: "number",
@@ -5427,7 +5700,7 @@ var MANIFEST = [
5427
5700
  },
5428
5701
  target: {
5429
5702
  type: "string",
5430
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5703
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5431
5704
  },
5432
5705
  format: {
5433
5706
  type: "string",
@@ -5474,7 +5747,7 @@ var MANIFEST = [
5474
5747
  },
5475
5748
  target: {
5476
5749
  type: "string",
5477
- description: "Which recording to stop: 'active' (default, valid only when exactly one recording is in flight) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5750
+ description: "Which recording to stop: 'active' (default, valid only when exactly one recording is in flight) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5478
5751
  },
5479
5752
  savePath: {
5480
5753
  type: "string",
@@ -5497,7 +5770,7 @@ var MANIFEST = [
5497
5770
  },
5498
5771
  target: {
5499
5772
  type: "string",
5500
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5773
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5501
5774
  },
5502
5775
  savePath: {
5503
5776
  type: "string",
@@ -5564,7 +5837,7 @@ var MANIFEST = [
5564
5837
  },
5565
5838
  target: {
5566
5839
  type: "string",
5567
- description: "Target selector: active | index:N | url:<substr> | title:<substr> | <targetId>. Defaults to the active page."
5840
+ description: "Target selector: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Defaults to the active page."
5568
5841
  },
5569
5842
  urlPattern: {
5570
5843
  type: "string",
@@ -5672,7 +5945,7 @@ var MANIFEST = [
5672
5945
  properties: {
5673
5946
  target: {
5674
5947
  type: "string",
5675
- description: "Take over an already-open tab, e.g. one the human has open, when asked to: pass any target selector — active | index:N | url:<substr> | title:<substr> | <targetId>. Resolved against the live page list only, so it NEVER opens a tab: a selector that matches nothing is an error, not a new blank tab. The tab is left open on release because the toolkit did not create it. Refused if another live process holds it, including a lease the gate acquired for that process automatically under CDP_REQUIRE_LEASE: this takes over unleased (human) tabs and never steals one from a live agent. Mutually exclusive with targetId."
5948
+ description: "Take over an already-open tab, e.g. one the human has open, when asked to: pass any target selector — active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Resolved against the live page list only, so it NEVER opens a tab: a selector that matches nothing is an error, not a new blank tab. The tab is left open on release because the toolkit did not create it. Refused if another live process holds it, including a lease the gate acquired for that process automatically under CDP_REQUIRE_LEASE: this takes over unleased (human) tabs and never steals one from a live agent. Mutually exclusive with targetId."
5676
5949
  },
5677
5950
  targetId: {
5678
5951
  type: "string",
@@ -5707,7 +5980,7 @@ var MANIFEST = [
5707
5980
  },
5708
5981
  target: {
5709
5982
  type: "string",
5710
- description: "Target selector for a tab this process holds: active | index:N | url:<substr> | title:<substr> | <targetId>. Mutually exclusive with 'lease'. Refused if another process holds the tab."
5983
+ description: "Target selector for a tab this process holds: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Mutually exclusive with 'lease'. Refused if another process holds the tab."
5711
5984
  },
5712
5985
  close: {
5713
5986
  type: "boolean",
@@ -6552,6 +6825,22 @@ async function pickContext(conn, contexts, selector) {
6552
6825
  return c;
6553
6826
  throw driverError2("no-such-target", `no context with title containing '${needle}'`);
6554
6827
  }
6828
+ if (selector.startsWith("label:")) {
6829
+ const label = selector.slice(6);
6830
+ const { matchIds, knownLabels } = await resolveLiveLabel("firefox", label, contexts.map((c) => c.context));
6831
+ if (matchIds.length > 1) {
6832
+ throw driverError2("no-such-target", `label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
6833
+ }
6834
+ if (matchIds.length === 1) {
6835
+ const hit = contexts.find((c) => c.context === matchIds[0]);
6836
+ if (hit)
6837
+ return hit;
6838
+ }
6839
+ throw driverError2("no-such-target", knownLabels.length ? `no live target with label '${label}' (labels currently in use: ${knownLabels.join(", ")})` : `no live target with label '${label}' (no labels are currently assigned to any open target)`);
6840
+ }
6841
+ if (isWorkerSelector(selector)) {
6842
+ throw driverError2("unsupported", WORKER_SELECTOR_UNSUPPORTED_MESSAGE);
6843
+ }
6555
6844
  const exact = contexts.find((c) => c.context === selector);
6556
6845
  if (exact)
6557
6846
  return exact;
@@ -7414,6 +7703,11 @@ async function withPage3(driver, target, fn) {
7414
7703
  }
7415
7704
  var consoleBuffers = new Map;
7416
7705
  var networkBuffers = new Map;
7706
+ function refuseWorkerSelector(driver, target) {
7707
+ if (isWorkerSelector(target) && !driver.capabilities.has("worker.targets")) {
7708
+ throw new BidiToolError(WORKER_SELECTOR_UNSUPPORTED_MESSAGE);
7709
+ }
7710
+ }
7417
7711
  function bufferFor(map, id) {
7418
7712
  let b = map.get(id);
7419
7713
  if (!b) {
@@ -7442,6 +7736,7 @@ async function captureFresh(page, durationMs) {
7442
7736
  }
7443
7737
  }
7444
7738
  async function listConsoleMessages2(driver, args = {}) {
7739
+ refuseWorkerSelector(driver, args.target);
7445
7740
  return withPage3(driver, args.target, async (page) => {
7446
7741
  if (args.reload)
7447
7742
  await captureFresh(page, args.durationMs ?? 2500);
@@ -7450,6 +7745,7 @@ async function listConsoleMessages2(driver, args = {}) {
7450
7745
  });
7451
7746
  }
7452
7747
  async function getConsoleMessage2(driver, args = {}) {
7748
+ refuseWorkerSelector(driver, args.target);
7453
7749
  return withPage3(driver, args.target, async (page) => {
7454
7750
  const buf = bufferFor(consoleBuffers, page.info.id);
7455
7751
  const idx = args.index ?? 0;
@@ -7460,6 +7756,7 @@ async function getConsoleMessage2(driver, args = {}) {
7460
7756
  });
7461
7757
  }
7462
7758
  async function listNetworkRequests2(driver, args = {}) {
7759
+ refuseWorkerSelector(driver, args.target);
7463
7760
  return withPage3(driver, args.target, async (page) => {
7464
7761
  if (args.reload)
7465
7762
  await captureFresh(page, args.durationMs ?? 2500);
@@ -7470,6 +7767,7 @@ async function listNetworkRequests2(driver, args = {}) {
7470
7767
  });
7471
7768
  }
7472
7769
  async function getNetworkRequest2(driver, args = {}) {
7770
+ refuseWorkerSelector(driver, args.target);
7473
7771
  if (!args.requestId && !args.url)
7474
7772
  throw new BidiToolError("get_network_request requires requestId or url");
7475
7773
  return withPage3(driver, args.target, async (page) => {
@@ -7561,7 +7859,7 @@ var FIREFOX_TOOLS = {
7561
7859
  };
7562
7860
 
7563
7861
  // src/mcp.ts
7564
- var VERSION = "1.8.0";
7862
+ var VERSION = "1.9.1";
7565
7863
  var BROWSER = resolveBrowserKind(stripBrowserFlag(process.argv.slice(2)));
7566
7864
  var AVAILABILITY = toolAvailability(BROWSER);
7567
7865
  var AVAILABLE_NAMES = new Set(AVAILABILITY.available);