cdp-toolkit 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.js CHANGED
@@ -278,6 +278,169 @@ 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_TARGET_TYPES = new Set(["service_worker", "shared_worker"]);
410
+ function isWorkerSelector(selector) {
411
+ return typeof selector === "string" && selector.startsWith(WORKER_SELECTOR_PREFIX);
412
+ }
413
+ function workerNeedle(selector) {
414
+ return selector.slice(WORKER_SELECTOR_PREFIX.length);
415
+ }
416
+ function isWorkerTargetType(type) {
417
+ return type !== undefined && WORKER_TARGET_TYPES.has(type);
418
+ }
419
+ function resolveWorkerTargets(targets, needle) {
420
+ const liveWorkers = targets.filter((t) => isWorkerTargetType(t.type));
421
+ return {
422
+ matches: needle === "" ? [] : liveWorkers.filter((t) => t.url.includes(needle)),
423
+ liveWorkers
424
+ };
425
+ }
426
+ var WORKER_EMPTY_NEEDLE_MESSAGE = "'worker:' needs a substring of the worker's url, e.g. worker:<extension-id> or worker:background.js";
427
+ 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 an extension's background worker.";
428
+ 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). evaluate_script is the one tool that " + "accepts a worker: target.";
429
+ function urlList(workers) {
430
+ return workers.map((w) => w.url).join(", ");
431
+ }
432
+ function workerAmbiguityMessage(needle, matches) {
433
+ 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.";
434
+ }
435
+ function workerMissMessage(needle, opts) {
436
+ const running = opts.liveWorkers?.length ? ` (running workers: ${urlList(opts.liveWorkers)})` : " (no workers are running)";
437
+ if (!opts.wakeAttempted) {
438
+ 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.";
439
+ }
440
+ 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.";
441
+ 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.";
442
+ }
443
+
281
444
  // src/client.ts
282
445
  var BASE = process.env.CDP_BASE ?? "http://127.0.0.1:9222";
283
446
  var DEFAULT_TIMEOUT_MS = Number(process.env.CDP_TIMEOUT_MS ?? 15000);
@@ -433,11 +596,13 @@ async function browserWsUrl() {
433
596
  }
434
597
  async function resolveTarget(selector, opts = {}) {
435
598
  const targets = await listTargets();
436
- const hit = pickTarget(targets, selector);
599
+ const hit = await pickTarget(targets, selector);
600
+ if (isWorkerTargetType(hit.type))
601
+ return hit;
437
602
  await assertLeaseOk("chrome", hit.id, { lease: opts.lease, url: hit.url, title: hit.title });
438
603
  return hit;
439
604
  }
440
- function pickTarget(targets, selector) {
605
+ async function pickTarget(targets, selector) {
441
606
  const pages = targets.filter((t) => t.type === "page");
442
607
  if (!selector || selector === "active") {
443
608
  if (!pages.length)
@@ -464,6 +629,30 @@ function pickTarget(targets, selector) {
464
629
  throw new CdpError(`no page title containing '${needle}'`);
465
630
  return hit;
466
631
  }
632
+ if (selector.startsWith("label:")) {
633
+ const label = selector.slice(6);
634
+ const { matchIds, knownLabels } = await resolveLiveLabel("chrome", label, pages.map((t) => t.id));
635
+ if (matchIds.length > 1) {
636
+ throw new CdpError(`label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
637
+ }
638
+ if (matchIds.length === 1) {
639
+ const hit = pages.find((t) => t.id === matchIds[0]);
640
+ if (hit)
641
+ return hit;
642
+ }
643
+ 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)`);
644
+ }
645
+ if (isWorkerSelector(selector)) {
646
+ const needle = workerNeedle(selector);
647
+ if (needle === "")
648
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
649
+ const { matches, liveWorkers } = resolveWorkerTargets(targets, needle);
650
+ if (matches.length > 1)
651
+ throw new CdpError(workerAmbiguityMessage(needle, matches));
652
+ if (matches.length === 1)
653
+ return matches[0];
654
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers }));
655
+ }
467
656
  const byId = targets.find((t) => t.id === selector);
468
657
  if (!byId)
469
658
  throw new CdpError(`no target with id '${selector}'`);
@@ -653,6 +842,91 @@ async function resolveUid(conn, uid) {
653
842
  return { objectId: object.objectId };
654
843
  }
655
844
 
845
+ // src/cdp/workers.ts
846
+ var INVENTORY_FIRST_EVENT_MS = 1500;
847
+ var INVENTORY_SETTLE_MS = 250;
848
+ var WAKE_POLL_MS = 300;
849
+ var WAKE_POLL_ATTEMPTS = 30;
850
+ 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.";
851
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
852
+ async function requestWorkerStart(needle) {
853
+ const conn = await openBrowser();
854
+ try {
855
+ const host = (await listTargets()).find((t) => t.type === "page");
856
+ if (!host)
857
+ throw new CdpError(NO_HOST_PAGE_MESSAGE);
858
+ const { sessionId } = await conn.send("Target.attachToTarget", {
859
+ targetId: host.id,
860
+ flatten: true
861
+ });
862
+ if (!sessionId)
863
+ throw new CdpError("could not attach a session to host the ServiceWorker domain");
864
+ const scopeById = new Map;
865
+ const versions = [];
866
+ conn.on("ServiceWorker.workerRegistrationUpdated", (params) => {
867
+ for (const r of params.registrations ?? []) {
868
+ if (r.isDeleted === true)
869
+ continue;
870
+ scopeById.set(String(r.registrationId), String(r.scopeURL ?? ""));
871
+ }
872
+ });
873
+ conn.on("ServiceWorker.workerVersionUpdated", (params) => {
874
+ for (const v of params.versions ?? []) {
875
+ versions.push({ registrationId: String(v.registrationId), scriptURL: String(v.scriptURL ?? "") });
876
+ }
877
+ });
878
+ await conn.send("ServiceWorker.enable", {}, { sessionId });
879
+ await conn.waitFor("ServiceWorker.workerVersionUpdated", undefined, INVENTORY_FIRST_EVENT_MS).catch(() => {
880
+ return;
881
+ });
882
+ await sleep(INVENTORY_SETTLE_MS);
883
+ const scopes = [...new Set(scopeById.values())].filter((s) => s.length > 0).sort();
884
+ const matched = new Set;
885
+ for (const v of versions) {
886
+ const scope = scopeById.get(v.registrationId);
887
+ if (scope === undefined)
888
+ continue;
889
+ if (v.scriptURL.includes(needle) || scope.includes(needle))
890
+ matched.add(scope);
891
+ }
892
+ if (matched.size === 0)
893
+ return { startRequested: false, scopes };
894
+ if (matched.size > 1) {
895
+ throw new CdpError(`'worker:${needle}' matches more than one registered service-worker scope: ${[...matched].sort().join(", ")}. ` + "Narrow the substring.");
896
+ }
897
+ await conn.send("ServiceWorker.startWorker", { scopeURL: [...matched][0] }, { sessionId });
898
+ return { startRequested: true, scopes };
899
+ } finally {
900
+ conn.close();
901
+ }
902
+ }
903
+ async function resolveWorkerSelector(selector, opts) {
904
+ const needle = workerNeedle(selector);
905
+ if (needle === "")
906
+ throw new CdpError(WORKER_EMPTY_NEEDLE_MESSAGE);
907
+ const first = resolveWorkerTargets(await listTargets(), needle);
908
+ if (first.matches.length > 1)
909
+ throw new CdpError(workerAmbiguityMessage(needle, first.matches));
910
+ if (first.matches.length === 1)
911
+ return first.matches[0];
912
+ if (!opts.wake) {
913
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: false, liveWorkers: first.liveWorkers }));
914
+ }
915
+ const { startRequested, scopes } = await requestWorkerStart(needle);
916
+ if (startRequested) {
917
+ for (let i = 0;i < WAKE_POLL_ATTEMPTS; i++) {
918
+ await sleep(WAKE_POLL_MS);
919
+ const again = resolveWorkerTargets(await listTargets(), needle);
920
+ if (again.matches.length > 1)
921
+ throw new CdpError(workerAmbiguityMessage(needle, again.matches));
922
+ if (again.matches.length === 1)
923
+ return again.matches[0];
924
+ }
925
+ }
926
+ const final = resolveWorkerTargets(await listTargets(), needle);
927
+ throw new CdpError(workerMissMessage(needle, { wakeAttempted: true, liveWorkers: final.liveWorkers, registeredScopes: scopes }));
928
+ }
929
+
656
930
  // src/tools/network_mock.ts
657
931
  function escapeRegExpChar(ch) {
658
932
  return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
@@ -1134,7 +1408,8 @@ var CDP_CAPABILITIES = new Set([
1134
1408
  "input.raw",
1135
1409
  "input.html5Drag",
1136
1410
  "browser.downloads",
1137
- "browser.permissions"
1411
+ "browser.permissions",
1412
+ "worker.targets"
1138
1413
  ]);
1139
1414
 
1140
1415
  class CdpPageDriver {
@@ -1702,6 +1977,10 @@ class CdpBrowserDriver {
1702
1977
  await withBrowserConn((conn) => conn.send("Target.activateTarget", { targetId: target.id }));
1703
1978
  return { id: target.id, url: target.url, title: target.title };
1704
1979
  }
1980
+ async resolveWorkerTarget(selector, opts) {
1981
+ const target = await resolveWorkerSelector(selector, opts).catch(noSuchTarget);
1982
+ return { id: target.id, url: target.url, title: target.title, type: target.type };
1983
+ }
1705
1984
  async page(selector) {
1706
1985
  const { conn, target } = await openPage(selector).catch(noSuchTarget);
1707
1986
  return new CdpPageDriver(conn, target, this);
@@ -1788,111 +2067,6 @@ function createCdpDriver() {
1788
2067
  return new CdpBrowserDriver;
1789
2068
  }
1790
2069
 
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
2070
  // src/reap.ts
1897
2071
  function staleAgentTabs(input) {
1898
2072
  const live = new Set(input.livePageIds);
@@ -1993,6 +2167,11 @@ async function pickPage(driver, pages, selector) {
1993
2167
  throw new SharedToolError(`no page title containing '${needle}'`);
1994
2168
  return p;
1995
2169
  }
2170
+ if (selector.startsWith("label:"))
2171
+ return pickByLabel(driver, pages, selector.slice(6));
2172
+ if (isWorkerSelector(selector)) {
2173
+ throw new SharedToolError(backendOf(driver) === "firefox" ? WORKER_SELECTOR_UNSUPPORTED_MESSAGE : WORKER_SELECTOR_PAGE_ONLY_MESSAGE);
2174
+ }
1996
2175
  const exactPage = pages.find((x) => x.id === selector);
1997
2176
  if (exactPage)
1998
2177
  return exactPage;
@@ -2002,6 +2181,18 @@ async function pickPage(driver, pages, selector) {
2002
2181
  return exact;
2003
2182
  throw new SharedToolError(`no target with id '${selector}'`);
2004
2183
  }
2184
+ async function pickByLabel(driver, pages, label) {
2185
+ const { matchIds, knownLabels } = await resolveLiveLabel(backendOf(driver), label, pages.map((x) => x.id));
2186
+ if (matchIds.length > 1) {
2187
+ throw new SharedToolError(`label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
2188
+ }
2189
+ if (matchIds.length === 1) {
2190
+ const p = pages.find((x) => x.id === matchIds[0]);
2191
+ if (p)
2192
+ return p;
2193
+ }
2194
+ 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)`);
2195
+ }
2005
2196
  function locatorOf(args) {
2006
2197
  const hasUid = args.uid !== undefined && args.uid !== null && args.uid !== "";
2007
2198
  const hasSelector = typeof args.selector === "string" && args.selector.length > 0;
@@ -2143,11 +2334,29 @@ async function writeJsonSink(savePath, value) {
2143
2334
  function sinkRequested(savePath) {
2144
2335
  return savePath !== undefined && savePath !== "";
2145
2336
  }
2337
+ var EXPRESSION_ALIAS_KEYS = ["function", "code", "js", "script", "fn", "body"];
2338
+ async function resolveWorkerSelectorFor(driver, selector, wake) {
2339
+ if (!driver.capabilities.has("worker.targets") || typeof driver.resolveWorkerTarget !== "function") {
2340
+ throw new SharedToolError(`evaluateScript: ${WORKER_SELECTOR_UNSUPPORTED_MESSAGE}`);
2341
+ }
2342
+ return driver.resolveWorkerTarget(selector, { wake });
2343
+ }
2146
2344
  async function evaluateScript(driver, args) {
2147
2345
  if (typeof args.expression !== "string" || args.expression.length === 0) {
2346
+ const wrongKey = EXPRESSION_ALIAS_KEYS.find((k) => args[k] !== undefined);
2347
+ if (wrongKey !== undefined) {
2348
+ throw new SharedToolError(`evaluateScript: you passed '${wrongKey}'; the key is 'expression' — function literals go in 'expression', invoked with 'args'.`);
2349
+ }
2148
2350
  throw new SharedToolError("evaluateScript: 'expression' must be a non-empty string");
2149
2351
  }
2150
- return withPage2(driver, args.target, async (page) => {
2352
+ if (args.wake !== undefined && typeof args.wake !== "boolean") {
2353
+ throw new SharedToolError("evaluateScript: 'wake' must be a boolean");
2354
+ }
2355
+ if (args.wake !== undefined && !isWorkerSelector(args.target)) {
2356
+ throw new SharedToolError("evaluateScript: 'wake' only applies to a target of the form 'worker:<substring>'; a page target is never asleep.");
2357
+ }
2358
+ const target = isWorkerSelector(args.target) ? (await resolveWorkerSelectorFor(driver, args.target, args.wake ?? true)).id : args.target;
2359
+ return withPage2(driver, target, async (page) => {
2151
2360
  const value = await page.evaluate(args.expression, { args: args.args, awaitPromise: args.awaitPromise ?? true });
2152
2361
  if (!sinkRequested(args.savePath))
2153
2362
  return value;
@@ -2511,7 +2720,7 @@ async function claimPage(driver, args = {}) {
2511
2720
  const hasTarget = typeof args.target === "string" && args.target.length > 0;
2512
2721
  const hasTargetId = typeof args.targetId === "string" && args.targetId.length > 0;
2513
2722
  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.");
2723
+ 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
2724
  }
2516
2725
  const backend = backendOf2(driver);
2517
2726
  const label = typeof args.label === "string" && args.label.length ? args.label : defaultLabel();
@@ -4157,7 +4366,7 @@ var MANIFEST = [
4157
4366
  },
4158
4367
  target: {
4159
4368
  type: "string",
4160
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4369
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4161
4370
  }
4162
4371
  },
4163
4372
  required: [
@@ -4178,7 +4387,7 @@ var MANIFEST = [
4178
4387
  },
4179
4388
  target: {
4180
4389
  type: "string",
4181
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4390
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4182
4391
  }
4183
4392
  },
4184
4393
  required: [
@@ -4199,7 +4408,7 @@ var MANIFEST = [
4199
4408
  },
4200
4409
  target: {
4201
4410
  type: "string",
4202
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4411
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4203
4412
  },
4204
4413
  url: {
4205
4414
  type: "string",
@@ -4249,7 +4458,7 @@ var MANIFEST = [
4249
4458
  },
4250
4459
  target: {
4251
4460
  type: "string",
4252
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4461
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4253
4462
  },
4254
4463
  text: {
4255
4464
  type: "string",
@@ -4272,7 +4481,7 @@ var MANIFEST = [
4272
4481
  },
4273
4482
  {
4274
4483
  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.",
4484
+ 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 also the one tool that can evaluate inside an MV3 extension's background SERVICE WORKER rather than a page: 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
4485
  inputSchema: {
4277
4486
  type: "object",
4278
4487
  properties: {
@@ -4282,11 +4491,15 @@ var MANIFEST = [
4282
4491
  },
4283
4492
  target: {
4284
4493
  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."
4494
+ 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 this tool only; an MV3 worker that has been idle-evicted is started automatically first, see 'wake'."
4495
+ },
4496
+ wake: {
4497
+ type: "boolean",
4498
+ 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
4499
  },
4287
4500
  expression: {
4288
4501
  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."
4502
+ 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
4503
  },
4291
4504
  awaitPromise: {
4292
4505
  type: "boolean",
@@ -4319,7 +4532,7 @@ var MANIFEST = [
4319
4532
  },
4320
4533
  target: {
4321
4534
  type: "string",
4322
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4535
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4323
4536
  },
4324
4537
  domain: {
4325
4538
  type: "string",
@@ -4350,7 +4563,7 @@ var MANIFEST = [
4350
4563
  },
4351
4564
  target: {
4352
4565
  type: "string",
4353
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4566
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4354
4567
  },
4355
4568
  name: {
4356
4569
  type: "string",
@@ -4406,7 +4619,7 @@ var MANIFEST = [
4406
4619
  },
4407
4620
  target: {
4408
4621
  type: "string",
4409
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
4622
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
4410
4623
  },
4411
4624
  name: {
4412
4625
  type: "string",
@@ -4441,7 +4654,7 @@ var MANIFEST = [
4441
4654
  },
4442
4655
  target: {
4443
4656
  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)."
4657
+ 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
4658
  },
4446
4659
  interactiveOnly: {
4447
4660
  type: "boolean",
@@ -4464,7 +4677,7 @@ var MANIFEST = [
4464
4677
  },
4465
4678
  target: {
4466
4679
  type: "string",
4467
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4680
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4468
4681
  },
4469
4682
  uid: {
4470
4683
  type: "number",
@@ -4517,7 +4730,7 @@ var MANIFEST = [
4517
4730
  },
4518
4731
  target: {
4519
4732
  type: "string",
4520
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4733
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4521
4734
  },
4522
4735
  uid: {
4523
4736
  type: "number",
@@ -4544,7 +4757,7 @@ var MANIFEST = [
4544
4757
  },
4545
4758
  target: {
4546
4759
  type: "string",
4547
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4760
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4548
4761
  },
4549
4762
  from: {
4550
4763
  type: "object",
@@ -4630,7 +4843,7 @@ var MANIFEST = [
4630
4843
  },
4631
4844
  target: {
4632
4845
  type: "string",
4633
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4846
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4634
4847
  },
4635
4848
  uid: {
4636
4849
  type: "number",
@@ -4673,7 +4886,7 @@ var MANIFEST = [
4673
4886
  },
4674
4887
  target: {
4675
4888
  type: "string",
4676
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
4889
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4677
4890
  },
4678
4891
  action: {
4679
4892
  type: "string",
@@ -4739,7 +4952,7 @@ var MANIFEST = [
4739
4952
  },
4740
4953
  target: {
4741
4954
  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."
4955
+ 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
4956
  },
4744
4957
  arm: {
4745
4958
  type: "boolean",
@@ -4766,7 +4979,7 @@ var MANIFEST = [
4766
4979
  },
4767
4980
  target: {
4768
4981
  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."
4982
+ 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
4983
  },
4771
4984
  permissions: {
4772
4985
  type: "array",
@@ -4800,7 +5013,7 @@ var MANIFEST = [
4800
5013
  },
4801
5014
  target: {
4802
5015
  type: "string",
4803
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5016
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4804
5017
  },
4805
5018
  uid: {
4806
5019
  type: "number",
@@ -4833,7 +5046,7 @@ var MANIFEST = [
4833
5046
  },
4834
5047
  target: {
4835
5048
  type: "string",
4836
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5049
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4837
5050
  },
4838
5051
  fields: {
4839
5052
  type: "array",
@@ -4879,7 +5092,7 @@ var MANIFEST = [
4879
5092
  },
4880
5093
  target: {
4881
5094
  type: "string",
4882
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5095
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4883
5096
  },
4884
5097
  uid: {
4885
5098
  type: "number",
@@ -4912,7 +5125,7 @@ var MANIFEST = [
4912
5125
  },
4913
5126
  target: {
4914
5127
  type: "string",
4915
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5128
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4916
5129
  },
4917
5130
  key: {
4918
5131
  type: "string",
@@ -4944,7 +5157,7 @@ var MANIFEST = [
4944
5157
  },
4945
5158
  target: {
4946
5159
  type: "string",
4947
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5160
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
4948
5161
  },
4949
5162
  uid: {
4950
5163
  type: "number",
@@ -4983,7 +5196,7 @@ var MANIFEST = [
4983
5196
  },
4984
5197
  target: {
4985
5198
  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."
5199
+ 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
5200
  },
4988
5201
  format: {
4989
5202
  type: "string",
@@ -5034,7 +5247,7 @@ var MANIFEST = [
5034
5247
  },
5035
5248
  target: {
5036
5249
  type: "string",
5037
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5250
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5038
5251
  },
5039
5252
  width: {
5040
5253
  type: "number",
@@ -5134,7 +5347,7 @@ var MANIFEST = [
5134
5347
  },
5135
5348
  target: {
5136
5349
  type: "string",
5137
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5350
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5138
5351
  },
5139
5352
  width: {
5140
5353
  type: "number",
@@ -5172,7 +5385,7 @@ var MANIFEST = [
5172
5385
  },
5173
5386
  target: {
5174
5387
  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>."
5388
+ 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
5389
  },
5177
5390
  accept: {
5178
5391
  type: "boolean",
@@ -5209,7 +5422,7 @@ var MANIFEST = [
5209
5422
  },
5210
5423
  target: {
5211
5424
  type: "string",
5212
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5425
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5213
5426
  },
5214
5427
  reload: {
5215
5428
  type: "boolean",
@@ -5236,7 +5449,7 @@ var MANIFEST = [
5236
5449
  },
5237
5450
  target: {
5238
5451
  type: "string",
5239
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5452
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5240
5453
  },
5241
5454
  index: {
5242
5455
  type: "number",
@@ -5259,7 +5472,7 @@ var MANIFEST = [
5259
5472
  },
5260
5473
  target: {
5261
5474
  type: "string",
5262
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5475
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5263
5476
  },
5264
5477
  reload: {
5265
5478
  type: "boolean",
@@ -5290,7 +5503,7 @@ var MANIFEST = [
5290
5503
  },
5291
5504
  target: {
5292
5505
  type: "string",
5293
- description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>'."
5506
+ description: "Target page selector: 'active' (default) | '<targetId>' | 'index:N' | 'url:<substring>' | 'title:<substring>' | 'label:<name>'."
5294
5507
  },
5295
5508
  requestId: {
5296
5509
  type: "string",
@@ -5325,7 +5538,7 @@ var MANIFEST = [
5325
5538
  },
5326
5539
  target: {
5327
5540
  type: "string",
5328
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5541
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5329
5542
  },
5330
5543
  categories: {
5331
5544
  type: "array",
@@ -5351,7 +5564,7 @@ var MANIFEST = [
5351
5564
  },
5352
5565
  target: {
5353
5566
  type: "string",
5354
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5567
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5355
5568
  }
5356
5569
  },
5357
5570
  required: [],
@@ -5389,7 +5602,7 @@ var MANIFEST = [
5389
5602
  },
5390
5603
  target: {
5391
5604
  type: "string",
5392
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5605
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5393
5606
  },
5394
5607
  durationMs: {
5395
5608
  type: "number",
@@ -5427,7 +5640,7 @@ var MANIFEST = [
5427
5640
  },
5428
5641
  target: {
5429
5642
  type: "string",
5430
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5643
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5431
5644
  },
5432
5645
  format: {
5433
5646
  type: "string",
@@ -5474,7 +5687,7 @@ var MANIFEST = [
5474
5687
  },
5475
5688
  target: {
5476
5689
  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>'."
5690
+ 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
5691
  },
5479
5692
  savePath: {
5480
5693
  type: "string",
@@ -5497,7 +5710,7 @@ var MANIFEST = [
5497
5710
  },
5498
5711
  target: {
5499
5712
  type: "string",
5500
- description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | '<targetId>'."
5713
+ description: "Target page selector: 'active' (default) | 'index:N' | 'url:<substr>' | 'title:<substr>' | 'label:<name>' | '<targetId>'."
5501
5714
  },
5502
5715
  savePath: {
5503
5716
  type: "string",
@@ -5564,7 +5777,7 @@ var MANIFEST = [
5564
5777
  },
5565
5778
  target: {
5566
5779
  type: "string",
5567
- description: "Target selector: active | index:N | url:<substr> | title:<substr> | <targetId>. Defaults to the active page."
5780
+ description: "Target selector: active | index:N | url:<substr> | title:<substr> | label:<name> | <targetId>. Defaults to the active page."
5568
5781
  },
5569
5782
  urlPattern: {
5570
5783
  type: "string",
@@ -5672,7 +5885,7 @@ var MANIFEST = [
5672
5885
  properties: {
5673
5886
  target: {
5674
5887
  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."
5888
+ 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
5889
  },
5677
5890
  targetId: {
5678
5891
  type: "string",
@@ -5707,7 +5920,7 @@ var MANIFEST = [
5707
5920
  },
5708
5921
  target: {
5709
5922
  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."
5923
+ 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
5924
  },
5712
5925
  close: {
5713
5926
  type: "boolean",
@@ -6552,6 +6765,22 @@ async function pickContext(conn, contexts, selector) {
6552
6765
  return c;
6553
6766
  throw driverError2("no-such-target", `no context with title containing '${needle}'`);
6554
6767
  }
6768
+ if (selector.startsWith("label:")) {
6769
+ const label = selector.slice(6);
6770
+ const { matchIds, knownLabels } = await resolveLiveLabel("firefox", label, contexts.map((c) => c.context));
6771
+ if (matchIds.length > 1) {
6772
+ throw driverError2("no-such-target", `label 'label:${label}' matches more than one live target: ${matchIds.join(", ")}. Resolve by target id instead.`);
6773
+ }
6774
+ if (matchIds.length === 1) {
6775
+ const hit = contexts.find((c) => c.context === matchIds[0]);
6776
+ if (hit)
6777
+ return hit;
6778
+ }
6779
+ 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)`);
6780
+ }
6781
+ if (isWorkerSelector(selector)) {
6782
+ throw driverError2("unsupported", WORKER_SELECTOR_UNSUPPORTED_MESSAGE);
6783
+ }
6555
6784
  const exact = contexts.find((c) => c.context === selector);
6556
6785
  if (exact)
6557
6786
  return exact;
@@ -7561,7 +7790,7 @@ var FIREFOX_TOOLS = {
7561
7790
  };
7562
7791
 
7563
7792
  // src/mcp.ts
7564
- var VERSION = "1.8.0";
7793
+ var VERSION = "1.9.0";
7565
7794
  var BROWSER = resolveBrowserKind(stripBrowserFlag(process.argv.slice(2)));
7566
7795
  var AVAILABILITY = toolAvailability(BROWSER);
7567
7796
  var AVAILABLE_NAMES = new Set(AVAILABILITY.available);