browserclaw 0.11.2 → 0.11.3

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/index.cjs CHANGED
@@ -1782,137 +1782,10 @@ var STEALTH_SCRIPT = `(function() {
1782
1782
  });
1783
1783
  })()`;
1784
1784
 
1785
- // src/connection.ts
1786
- var BrowserTabNotFoundError = class extends Error {
1787
- constructor(message = "Tab not found") {
1788
- super(message);
1789
- this.name = "BrowserTabNotFoundError";
1790
- }
1791
- };
1792
- async function fetchJsonForCdp(url, timeoutMs) {
1793
- const ctrl = new AbortController();
1794
- const t = setTimeout(() => {
1795
- ctrl.abort();
1796
- }, timeoutMs);
1797
- try {
1798
- const res = await fetch(url, { signal: ctrl.signal });
1799
- if (!res.ok) return null;
1800
- return await res.json();
1801
- } catch {
1802
- return null;
1803
- } finally {
1804
- clearTimeout(t);
1805
- }
1806
- }
1807
- function appendCdpPath2(cdpUrl, cdpPath) {
1808
- try {
1809
- const url = new URL(cdpUrl);
1810
- url.pathname = `${url.pathname.replace(/\/$/, "")}${cdpPath.startsWith("/") ? cdpPath : `/${cdpPath}`}`;
1811
- return url.toString();
1812
- } catch {
1813
- return `${cdpUrl.replace(/\/$/, "")}${cdpPath}`;
1814
- }
1815
- }
1816
- async function withPlaywrightPageCdpSession(page, fn) {
1817
- const CDP_SESSION_TIMEOUT_MS = 1e4;
1818
- const session = await Promise.race([
1819
- page.context().newCDPSession(page),
1820
- new Promise((_, reject) => {
1821
- setTimeout(() => {
1822
- reject(new Error("newCDPSession timed out after 10s"));
1823
- }, CDP_SESSION_TIMEOUT_MS);
1824
- })
1825
- ]);
1826
- try {
1827
- return await fn(session);
1828
- } finally {
1829
- await session.detach().catch(() => {
1830
- });
1831
- }
1832
- }
1833
- async function withPageScopedCdpClient(opts) {
1834
- return await withPlaywrightPageCdpSession(opts.page, async (session) => {
1835
- return await opts.fn((method, params) => session.send(method, params));
1836
- });
1837
- }
1838
- var LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]";
1839
- function noProxyAlreadyCoversLocalhost() {
1840
- const current = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
1841
- return current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]");
1842
- }
1843
- function isLoopbackCdpUrl(url) {
1844
- try {
1845
- return isLoopbackHost(new URL(url).hostname);
1846
- } catch {
1847
- return false;
1848
- }
1849
- }
1850
- var NoProxyLeaseManager = class {
1851
- leaseCount = 0;
1852
- snapshot = null;
1853
- acquire(url) {
1854
- if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return null;
1855
- if (this.leaseCount === 0 && !noProxyAlreadyCoversLocalhost()) {
1856
- const noProxy = process.env.NO_PROXY;
1857
- const noProxyLower = process.env.no_proxy;
1858
- const current = noProxy ?? noProxyLower ?? "";
1859
- const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
1860
- process.env.NO_PROXY = applied;
1861
- process.env.no_proxy = applied;
1862
- this.snapshot = { noProxy, noProxyLower, applied };
1863
- }
1864
- this.leaseCount += 1;
1865
- let released = false;
1866
- return () => {
1867
- if (released) return;
1868
- released = true;
1869
- this.release();
1870
- };
1871
- }
1872
- release() {
1873
- if (this.leaseCount <= 0) return;
1874
- this.leaseCount -= 1;
1875
- if (this.leaseCount > 0 || !this.snapshot) return;
1876
- const { noProxy, noProxyLower, applied } = this.snapshot;
1877
- const currentNoProxy = process.env.NO_PROXY;
1878
- const currentNoProxyLower = process.env.no_proxy;
1879
- if (currentNoProxy === applied && (currentNoProxyLower === applied || currentNoProxyLower === void 0)) {
1880
- if (noProxy !== void 0) process.env.NO_PROXY = noProxy;
1881
- else delete process.env.NO_PROXY;
1882
- if (noProxyLower !== void 0) process.env.no_proxy = noProxyLower;
1883
- else delete process.env.no_proxy;
1884
- }
1885
- this.snapshot = null;
1886
- }
1887
- };
1888
- var noProxyLeaseManager = new NoProxyLeaseManager();
1889
- async function withNoProxyForCdpUrl(url, fn) {
1890
- const release = noProxyLeaseManager.acquire(url);
1891
- try {
1892
- return await fn();
1893
- } finally {
1894
- release?.();
1895
- }
1896
- }
1897
- new http__default.default.Agent();
1898
- new https__default.default.Agent();
1899
- function getHeadersWithAuth(endpoint, baseHeaders = {}) {
1900
- const headers = { ...baseHeaders };
1901
- try {
1902
- const parsed = new URL(endpoint);
1903
- if (Object.keys(headers).some((k) => k.toLowerCase() === "authorization")) return headers;
1904
- if (parsed.username || parsed.password) {
1905
- const credentials = Buffer.from(
1906
- `${decodeURIComponent(parsed.username)}:${decodeURIComponent(parsed.password)}`
1907
- ).toString("base64");
1908
- headers.Authorization = `Basic ${credentials}`;
1909
- }
1910
- } catch {
1911
- }
1912
- return headers;
1913
- }
1914
- var cachedByCdpUrl = /* @__PURE__ */ new Map();
1915
- var connectingByCdpUrl = /* @__PURE__ */ new Map();
1785
+ // src/page-utils.ts
1786
+ var MAX_CONSOLE_MESSAGES = 500;
1787
+ var MAX_PAGE_ERRORS = 200;
1788
+ var MAX_NETWORK_REQUESTS = 500;
1916
1789
  var pageStates = /* @__PURE__ */ new WeakMap();
1917
1790
  var contextStates = /* @__PURE__ */ new WeakMap();
1918
1791
  var observedContexts = /* @__PURE__ */ new WeakSet();
@@ -1932,56 +1805,6 @@ function bumpDownloadArmId() {
1932
1805
  nextDownloadArmId += 1;
1933
1806
  return nextDownloadArmId;
1934
1807
  }
1935
- var BlockedBrowserTargetError = class extends Error {
1936
- constructor() {
1937
- super("Browser target is unavailable after SSRF policy blocked its navigation.");
1938
- this.name = "BlockedBrowserTargetError";
1939
- }
1940
- };
1941
- var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
1942
- var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
1943
- function blockedTargetKey(cdpUrl, targetId) {
1944
- return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
1945
- }
1946
- function isBlockedTarget(cdpUrl, targetId) {
1947
- const normalized = targetId?.trim() ?? "";
1948
- if (normalized === "") return false;
1949
- return blockedTargetsByCdpUrl.has(blockedTargetKey(cdpUrl, normalized));
1950
- }
1951
- function markTargetBlocked(cdpUrl, targetId) {
1952
- const normalized = targetId?.trim() ?? "";
1953
- if (normalized === "") return;
1954
- blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
1955
- }
1956
- function clearBlockedTarget(cdpUrl, targetId) {
1957
- const normalized = targetId?.trim() ?? "";
1958
- if (normalized === "") return;
1959
- blockedTargetsByCdpUrl.delete(blockedTargetKey(cdpUrl, normalized));
1960
- }
1961
- function hasBlockedTargetsForCdpUrl(cdpUrl) {
1962
- const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
1963
- for (const key of blockedTargetsByCdpUrl) {
1964
- if (key.startsWith(prefix)) return true;
1965
- }
1966
- return false;
1967
- }
1968
- function blockedPageRefsForCdpUrl(cdpUrl) {
1969
- const normalized = normalizeCdpUrl(cdpUrl);
1970
- const existing = blockedPageRefsByCdpUrl.get(normalized);
1971
- if (existing) return existing;
1972
- const created = /* @__PURE__ */ new WeakSet();
1973
- blockedPageRefsByCdpUrl.set(normalized, created);
1974
- return created;
1975
- }
1976
- function isBlockedPageRef(cdpUrl, page) {
1977
- return blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.has(page) ?? false;
1978
- }
1979
- function markPageRefBlocked(cdpUrl, page) {
1980
- blockedPageRefsForCdpUrl(cdpUrl).add(page);
1981
- }
1982
- function clearBlockedPageRef(cdpUrl, page) {
1983
- blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
1984
- }
1985
1808
  function ensureContextState(context) {
1986
1809
  const existing = contextStates.get(context);
1987
1810
  if (existing) return existing;
@@ -1989,16 +1812,8 @@ function ensureContextState(context) {
1989
1812
  contextStates.set(context, state);
1990
1813
  return state;
1991
1814
  }
1992
- var roleRefsByTarget = /* @__PURE__ */ new Map();
1993
- var MAX_ROLE_REFS_CACHE = 50;
1994
- var MAX_CONSOLE_MESSAGES = 500;
1995
- var MAX_PAGE_ERRORS = 200;
1996
- var MAX_NETWORK_REQUESTS = 500;
1997
- function normalizeCdpUrl(raw) {
1998
- return raw.replace(/\/$/, "");
1999
- }
2000
- function roleRefsKey(cdpUrl, targetId) {
2001
- return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
1815
+ function getPageState(page) {
1816
+ return pageStates.get(page);
2002
1817
  }
2003
1818
  function findNetworkRequestById(state, id) {
2004
1819
  for (let i = state.requests.length - 1; i >= 0; i--) {
@@ -2119,77 +1934,353 @@ function ensurePageState(page) {
2119
1934
  observedPages.delete(page);
2120
1935
  });
2121
1936
  }
2122
- return state;
1937
+ return state;
1938
+ }
1939
+ function setDialogHandlerOnPage(page, handler) {
1940
+ const state = ensurePageState(page);
1941
+ state.dialogHandler = handler;
1942
+ }
1943
+ function applyStealthToPage(page) {
1944
+ page.evaluate(STEALTH_SCRIPT).catch((e) => {
1945
+ if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
1946
+ console.warn("[browserclaw] stealth evaluate failed:", e instanceof Error ? e.message : String(e));
1947
+ });
1948
+ }
1949
+ function observeContext(context) {
1950
+ if (observedContexts.has(context)) return;
1951
+ observedContexts.add(context);
1952
+ ensureContextState(context);
1953
+ context.addInitScript(STEALTH_SCRIPT).catch((e) => {
1954
+ if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
1955
+ console.warn("[browserclaw] stealth initScript failed:", e instanceof Error ? e.message : String(e));
1956
+ });
1957
+ for (const page of context.pages()) {
1958
+ ensurePageState(page);
1959
+ applyStealthToPage(page);
1960
+ }
1961
+ context.on("page", (page) => {
1962
+ ensurePageState(page);
1963
+ applyStealthToPage(page);
1964
+ });
1965
+ }
1966
+ function observeBrowser(browser) {
1967
+ for (const context of browser.contexts()) observeContext(context);
1968
+ }
1969
+ function toAIFriendlyError(error, selector) {
1970
+ const message = error instanceof Error ? error.message : String(error);
1971
+ if (message.includes("strict mode violation")) {
1972
+ const countMatch = /resolved to (\d+) elements/.exec(message);
1973
+ const count = countMatch ? countMatch[1] : "multiple";
1974
+ return new Error(
1975
+ `Selector "${selector}" matched ${count} elements. Run a new snapshot to get updated refs, or use a different ref.`
1976
+ );
1977
+ }
1978
+ if ((message.includes("Timeout") || message.includes("waiting for")) && (message.includes("to be visible") || message.includes("not visible"))) {
1979
+ return new Error(
1980
+ `Element "${selector}" not found or not visible. Run a new snapshot to see current page elements.`
1981
+ );
1982
+ }
1983
+ if (message.includes("intercepts pointer events") || message.includes("not visible") || message.includes("not receive pointer events")) {
1984
+ return new Error(
1985
+ `Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
1986
+ );
1987
+ }
1988
+ const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
1989
+ if (timeoutMatch) {
1990
+ return new Error(
1991
+ `Element "${selector}" timed out after ${timeoutMatch[1]}ms \u2014 element may be hidden or not interactable. Run a new snapshot to see current page elements.`
1992
+ );
1993
+ }
1994
+ const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
1995
+ return new Error(cleaned || message);
1996
+ }
1997
+ function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
1998
+ return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
1999
+ }
2000
+
2001
+ // src/ref-resolver.ts
2002
+ var roleRefsByTarget = /* @__PURE__ */ new Map();
2003
+ var MAX_ROLE_REFS_CACHE = 50;
2004
+ function normalizeCdpUrl(raw) {
2005
+ return raw.replace(/\/$/, "");
2006
+ }
2007
+ function roleRefsKey(cdpUrl, targetId) {
2008
+ return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
2009
+ }
2010
+ function rememberRoleRefsForTarget(opts) {
2011
+ const targetId = opts.targetId.trim();
2012
+ if (targetId === "") return;
2013
+ roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), {
2014
+ refs: opts.refs,
2015
+ ...opts.frameSelector !== void 0 && opts.frameSelector !== "" ? { frameSelector: opts.frameSelector } : {},
2016
+ ...opts.mode !== void 0 ? { mode: opts.mode } : {}
2017
+ });
2018
+ while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) {
2019
+ const first = roleRefsByTarget.keys().next();
2020
+ if (first.done === true) break;
2021
+ roleRefsByTarget.delete(first.value);
2022
+ }
2023
+ }
2024
+ function storeRoleRefsForTarget(opts) {
2025
+ const state = ensurePageState(opts.page);
2026
+ state.roleRefs = opts.refs;
2027
+ state.roleRefsFrameSelector = opts.frameSelector;
2028
+ state.roleRefsMode = opts.mode;
2029
+ if (opts.targetId === void 0 || opts.targetId.trim() === "") return;
2030
+ rememberRoleRefsForTarget({
2031
+ cdpUrl: opts.cdpUrl,
2032
+ targetId: opts.targetId,
2033
+ refs: opts.refs,
2034
+ frameSelector: opts.frameSelector,
2035
+ mode: opts.mode
2036
+ });
2037
+ }
2038
+ function restoreRoleRefsForTarget(opts) {
2039
+ const targetId = opts.targetId?.trim() ?? "";
2040
+ if (targetId === "") return;
2041
+ const entry = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId));
2042
+ if (!entry) return;
2043
+ const state = ensurePageState(opts.page);
2044
+ if (state.roleRefs) return;
2045
+ state.roleRefs = entry.refs;
2046
+ state.roleRefsFrameSelector = entry.frameSelector;
2047
+ state.roleRefsMode = entry.mode;
2048
+ }
2049
+ function clearRoleRefsForCdpUrl(cdpUrl) {
2050
+ const normalized = normalizeCdpUrl(cdpUrl);
2051
+ for (const key of roleRefsByTarget.keys()) {
2052
+ if (key.startsWith(normalized + "::")) roleRefsByTarget.delete(key);
2053
+ }
2054
+ }
2055
+ function parseRoleRef(raw) {
2056
+ const trimmed = raw.trim();
2057
+ if (!trimmed) return null;
2058
+ const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
2059
+ return /^e\d+$/.test(normalized) ? normalized : null;
2060
+ }
2061
+ function requireRef(value) {
2062
+ const raw = typeof value === "string" ? value.trim() : "";
2063
+ const ref = (raw ? parseRoleRef(raw) : null) ?? (raw.startsWith("@") ? raw.slice(1) : raw);
2064
+ if (!ref) throw new Error("ref is required");
2065
+ return ref;
2066
+ }
2067
+ function requireRefOrSelector(ref, selector) {
2068
+ const trimmedRef = typeof ref === "string" ? ref.trim() : "";
2069
+ const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
2070
+ if (!trimmedRef && !trimmedSelector) throw new Error("ref or selector is required");
2071
+ return { ref: trimmedRef || void 0, selector: trimmedSelector || void 0 };
2072
+ }
2073
+ function resolveInteractionTimeoutMs(timeoutMs) {
2074
+ return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
2075
+ }
2076
+ function resolveBoundedDelayMs(value, label, maxMs) {
2077
+ const normalized = Math.floor(value ?? 0);
2078
+ if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
2079
+ if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${String(maxMs)}ms`);
2080
+ return normalized;
2081
+ }
2082
+ function refLocator(page, ref) {
2083
+ const normalized = ref.startsWith("@") ? ref.slice(1) : ref.startsWith("ref=") ? ref.slice(4) : ref;
2084
+ if (normalized.trim() === "") throw new Error("ref is required");
2085
+ if (/^e\d+$/.test(normalized)) {
2086
+ const state = getPageState(page);
2087
+ if (state?.roleRefsMode === "aria") {
2088
+ return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
2089
+ }
2090
+ const info = state?.roleRefs?.[normalized];
2091
+ if (!info) throw new Error(`Unknown ref "${normalized}". Run a new snapshot and use a ref from that snapshot.`);
2092
+ const locAny = state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page;
2093
+ const role = info.role;
2094
+ const locator = info.name !== void 0 && info.name !== "" ? locAny.getByRole(role, { name: info.name, exact: true }) : locAny.getByRole(role);
2095
+ return info.nth !== void 0 ? locator.nth(info.nth) : locator;
2096
+ }
2097
+ return page.locator(`aria-ref=${normalized}`);
2098
+ }
2099
+
2100
+ // src/connection.ts
2101
+ var BrowserTabNotFoundError = class extends Error {
2102
+ constructor(message = "Tab not found") {
2103
+ super(message);
2104
+ this.name = "BrowserTabNotFoundError";
2105
+ }
2106
+ };
2107
+ async function fetchJsonForCdp(url, timeoutMs) {
2108
+ const ctrl = new AbortController();
2109
+ const t = setTimeout(() => {
2110
+ ctrl.abort();
2111
+ }, timeoutMs);
2112
+ try {
2113
+ const res = await fetch(url, { signal: ctrl.signal });
2114
+ if (!res.ok) return null;
2115
+ return await res.json();
2116
+ } catch {
2117
+ return null;
2118
+ } finally {
2119
+ clearTimeout(t);
2120
+ }
2121
+ }
2122
+ function appendCdpPath2(cdpUrl, cdpPath) {
2123
+ try {
2124
+ const url = new URL(cdpUrl);
2125
+ url.pathname = `${url.pathname.replace(/\/$/, "")}${cdpPath.startsWith("/") ? cdpPath : `/${cdpPath}`}`;
2126
+ return url.toString();
2127
+ } catch {
2128
+ return `${cdpUrl.replace(/\/$/, "")}${cdpPath}`;
2129
+ }
2130
+ }
2131
+ async function withPlaywrightPageCdpSession(page, fn) {
2132
+ const CDP_SESSION_TIMEOUT_MS = 1e4;
2133
+ const session = await Promise.race([
2134
+ page.context().newCDPSession(page),
2135
+ new Promise((_, reject) => {
2136
+ setTimeout(() => {
2137
+ reject(new Error("newCDPSession timed out after 10s"));
2138
+ }, CDP_SESSION_TIMEOUT_MS);
2139
+ })
2140
+ ]);
2141
+ try {
2142
+ return await fn(session);
2143
+ } finally {
2144
+ await session.detach().catch(() => {
2145
+ });
2146
+ }
2147
+ }
2148
+ async function withPageScopedCdpClient(opts) {
2149
+ return await withPlaywrightPageCdpSession(opts.page, async (session) => {
2150
+ return await opts.fn((method, params) => session.send(method, params));
2151
+ });
2152
+ }
2153
+ var LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]";
2154
+ function noProxyAlreadyCoversLocalhost() {
2155
+ const current = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
2156
+ return current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]");
2157
+ }
2158
+ function isLoopbackCdpUrl(url) {
2159
+ try {
2160
+ return isLoopbackHost(new URL(url).hostname);
2161
+ } catch {
2162
+ return false;
2163
+ }
2123
2164
  }
2124
- async function setDialogHandler(opts) {
2125
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2126
- const state = ensurePageState(page);
2127
- state.dialogHandler = opts.handler;
2165
+ var NoProxyLeaseManager = class {
2166
+ leaseCount = 0;
2167
+ snapshot = null;
2168
+ acquire(url) {
2169
+ if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return null;
2170
+ if (this.leaseCount === 0 && !noProxyAlreadyCoversLocalhost()) {
2171
+ const noProxy = process.env.NO_PROXY;
2172
+ const noProxyLower = process.env.no_proxy;
2173
+ const current = noProxy ?? noProxyLower ?? "";
2174
+ const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
2175
+ process.env.NO_PROXY = applied;
2176
+ process.env.no_proxy = applied;
2177
+ this.snapshot = { noProxy, noProxyLower, applied };
2178
+ }
2179
+ this.leaseCount += 1;
2180
+ let released = false;
2181
+ return () => {
2182
+ if (released) return;
2183
+ released = true;
2184
+ this.release();
2185
+ };
2186
+ }
2187
+ release() {
2188
+ if (this.leaseCount <= 0) return;
2189
+ this.leaseCount -= 1;
2190
+ if (this.leaseCount > 0 || !this.snapshot) return;
2191
+ const { noProxy, noProxyLower, applied } = this.snapshot;
2192
+ const currentNoProxy = process.env.NO_PROXY;
2193
+ const currentNoProxyLower = process.env.no_proxy;
2194
+ if (currentNoProxy === applied && (currentNoProxyLower === applied || currentNoProxyLower === void 0)) {
2195
+ if (noProxy !== void 0) process.env.NO_PROXY = noProxy;
2196
+ else delete process.env.NO_PROXY;
2197
+ if (noProxyLower !== void 0) process.env.no_proxy = noProxyLower;
2198
+ else delete process.env.no_proxy;
2199
+ }
2200
+ this.snapshot = null;
2201
+ }
2202
+ };
2203
+ var noProxyLeaseManager = new NoProxyLeaseManager();
2204
+ async function withNoProxyForCdpUrl(url, fn) {
2205
+ const release = noProxyLeaseManager.acquire(url);
2206
+ try {
2207
+ return await fn();
2208
+ } finally {
2209
+ release?.();
2210
+ }
2128
2211
  }
2129
- function applyStealthToPage(page) {
2130
- page.evaluate(STEALTH_SCRIPT).catch((e) => {
2131
- if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
2132
- console.warn("[browserclaw] stealth evaluate failed:", e instanceof Error ? e.message : String(e));
2133
- });
2212
+ new http__default.default.Agent();
2213
+ new https__default.default.Agent();
2214
+ function getHeadersWithAuth(endpoint, baseHeaders = {}) {
2215
+ const headers = { ...baseHeaders };
2216
+ try {
2217
+ const parsed = new URL(endpoint);
2218
+ if (Object.keys(headers).some((k) => k.toLowerCase() === "authorization")) return headers;
2219
+ if (parsed.username || parsed.password) {
2220
+ const credentials = Buffer.from(
2221
+ `${decodeURIComponent(parsed.username)}:${decodeURIComponent(parsed.password)}`
2222
+ ).toString("base64");
2223
+ headers.Authorization = `Basic ${credentials}`;
2224
+ }
2225
+ } catch {
2226
+ }
2227
+ return headers;
2134
2228
  }
2135
- function observeContext(context) {
2136
- if (observedContexts.has(context)) return;
2137
- observedContexts.add(context);
2138
- ensureContextState(context);
2139
- context.addInitScript(STEALTH_SCRIPT).catch((e) => {
2140
- if (process.env.DEBUG !== void 0 && process.env.DEBUG !== "")
2141
- console.warn("[browserclaw] stealth initScript failed:", e instanceof Error ? e.message : String(e));
2142
- });
2143
- for (const page of context.pages()) {
2144
- ensurePageState(page);
2145
- applyStealthToPage(page);
2229
+ var cachedByCdpUrl = /* @__PURE__ */ new Map();
2230
+ var connectingByCdpUrl = /* @__PURE__ */ new Map();
2231
+ var BlockedBrowserTargetError = class extends Error {
2232
+ constructor() {
2233
+ super("Browser target is unavailable after SSRF policy blocked its navigation.");
2234
+ this.name = "BlockedBrowserTargetError";
2146
2235
  }
2147
- context.on("page", (page) => {
2148
- ensurePageState(page);
2149
- applyStealthToPage(page);
2150
- });
2236
+ };
2237
+ var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
2238
+ var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
2239
+ function blockedTargetKey(cdpUrl, targetId) {
2240
+ return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
2151
2241
  }
2152
- function observeBrowser(browser) {
2153
- for (const context of browser.contexts()) observeContext(context);
2242
+ function isBlockedTarget(cdpUrl, targetId) {
2243
+ const normalized = targetId?.trim() ?? "";
2244
+ if (normalized === "") return false;
2245
+ return blockedTargetsByCdpUrl.has(blockedTargetKey(cdpUrl, normalized));
2154
2246
  }
2155
- function rememberRoleRefsForTarget(opts) {
2156
- const targetId = opts.targetId.trim();
2157
- if (targetId === "") return;
2158
- roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), {
2159
- refs: opts.refs,
2160
- ...opts.frameSelector !== void 0 && opts.frameSelector !== "" ? { frameSelector: opts.frameSelector } : {},
2161
- ...opts.mode !== void 0 ? { mode: opts.mode } : {}
2162
- });
2163
- while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) {
2164
- const first = roleRefsByTarget.keys().next();
2165
- if (first.done === true) break;
2166
- roleRefsByTarget.delete(first.value);
2247
+ function markTargetBlocked(cdpUrl, targetId) {
2248
+ const normalized = targetId?.trim() ?? "";
2249
+ if (normalized === "") return;
2250
+ blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
2251
+ }
2252
+ function clearBlockedTarget(cdpUrl, targetId) {
2253
+ const normalized = targetId?.trim() ?? "";
2254
+ if (normalized === "") return;
2255
+ blockedTargetsByCdpUrl.delete(blockedTargetKey(cdpUrl, normalized));
2256
+ }
2257
+ function hasBlockedTargetsForCdpUrl(cdpUrl) {
2258
+ const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
2259
+ for (const key of blockedTargetsByCdpUrl) {
2260
+ if (key.startsWith(prefix)) return true;
2167
2261
  }
2262
+ return false;
2168
2263
  }
2169
- function storeRoleRefsForTarget(opts) {
2170
- const state = ensurePageState(opts.page);
2171
- state.roleRefs = opts.refs;
2172
- state.roleRefsFrameSelector = opts.frameSelector;
2173
- state.roleRefsMode = opts.mode;
2174
- if (opts.targetId === void 0 || opts.targetId.trim() === "") return;
2175
- rememberRoleRefsForTarget({
2176
- cdpUrl: opts.cdpUrl,
2177
- targetId: opts.targetId,
2178
- refs: opts.refs,
2179
- frameSelector: opts.frameSelector,
2180
- mode: opts.mode
2181
- });
2264
+ function blockedPageRefsForCdpUrl(cdpUrl) {
2265
+ const normalized = normalizeCdpUrl(cdpUrl);
2266
+ const existing = blockedPageRefsByCdpUrl.get(normalized);
2267
+ if (existing) return existing;
2268
+ const created = /* @__PURE__ */ new WeakSet();
2269
+ blockedPageRefsByCdpUrl.set(normalized, created);
2270
+ return created;
2182
2271
  }
2183
- function restoreRoleRefsForTarget(opts) {
2184
- const targetId = opts.targetId?.trim() ?? "";
2185
- if (targetId === "") return;
2186
- const entry = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId));
2187
- if (!entry) return;
2188
- const state = ensurePageState(opts.page);
2189
- if (state.roleRefs) return;
2190
- state.roleRefs = entry.refs;
2191
- state.roleRefsFrameSelector = entry.frameSelector;
2192
- state.roleRefsMode = entry.mode;
2272
+ function isBlockedPageRef(cdpUrl, page) {
2273
+ return blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.has(page) ?? false;
2274
+ }
2275
+ function markPageRefBlocked(cdpUrl, page) {
2276
+ blockedPageRefsForCdpUrl(cdpUrl).add(page);
2277
+ }
2278
+ function clearBlockedPageRef(cdpUrl, page) {
2279
+ blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
2280
+ }
2281
+ async function setDialogHandler(opts) {
2282
+ const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2283
+ setDialogHandlerOnPage(page, opts.handler);
2193
2284
  }
2194
2285
  async function connectBrowser(cdpUrl, authToken) {
2195
2286
  const normalized = normalizeCdpUrl(cdpUrl);
@@ -2213,9 +2304,7 @@ async function connectBrowser(cdpUrl, authToken) {
2213
2304
  const onDisconnected = () => {
2214
2305
  if (cachedByCdpUrl.get(normalized)?.browser === browser) {
2215
2306
  cachedByCdpUrl.delete(normalized);
2216
- for (const key of roleRefsByTarget.keys()) {
2217
- if (key.startsWith(normalized + "::")) roleRefsByTarget.delete(key);
2218
- }
2307
+ clearRoleRefsForCdpUrl(normalized);
2219
2308
  }
2220
2309
  };
2221
2310
  const connected = { browser, cdpUrl: normalized, onDisconnected };
@@ -2479,87 +2568,12 @@ async function resolvePageByTargetIdOrThrow(opts) {
2479
2568
  if (!page) throw new BrowserTabNotFoundError();
2480
2569
  return page;
2481
2570
  }
2482
- function parseRoleRef(raw) {
2483
- const trimmed = raw.trim();
2484
- if (!trimmed) return null;
2485
- const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
2486
- return /^e\d+$/.test(normalized) ? normalized : null;
2487
- }
2488
- function requireRef(value) {
2489
- const raw = typeof value === "string" ? value.trim() : "";
2490
- const ref = (raw ? parseRoleRef(raw) : null) ?? (raw.startsWith("@") ? raw.slice(1) : raw);
2491
- if (!ref) throw new Error("ref is required");
2492
- return ref;
2493
- }
2494
- function requireRefOrSelector(ref, selector) {
2495
- const trimmedRef = typeof ref === "string" ? ref.trim() : "";
2496
- const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
2497
- if (!trimmedRef && !trimmedSelector) throw new Error("ref or selector is required");
2498
- return { ref: trimmedRef || void 0, selector: trimmedSelector || void 0 };
2499
- }
2500
- function resolveInteractionTimeoutMs(timeoutMs) {
2501
- return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
2502
- }
2503
- function resolveBoundedDelayMs(value, label, maxMs) {
2504
- const normalized = Math.floor(value ?? 0);
2505
- if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
2506
- if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${String(maxMs)}ms`);
2507
- return normalized;
2508
- }
2509
2571
  async function getRestoredPageForTarget(opts) {
2510
2572
  const page = await getPageForTargetId(opts);
2511
2573
  ensurePageState(page);
2512
2574
  restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2513
2575
  return page;
2514
2576
  }
2515
- function refLocator(page, ref) {
2516
- const normalized = ref.startsWith("@") ? ref.slice(1) : ref.startsWith("ref=") ? ref.slice(4) : ref;
2517
- if (normalized.trim() === "") throw new Error("ref is required");
2518
- if (/^e\d+$/.test(normalized)) {
2519
- const state = pageStates.get(page);
2520
- if (state?.roleRefsMode === "aria") {
2521
- return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
2522
- }
2523
- const info = state?.roleRefs?.[normalized];
2524
- if (!info) throw new Error(`Unknown ref "${normalized}". Run a new snapshot and use a ref from that snapshot.`);
2525
- const locAny = state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page;
2526
- const role = info.role;
2527
- const locator = info.name !== void 0 && info.name !== "" ? locAny.getByRole(role, { name: info.name, exact: true }) : locAny.getByRole(role);
2528
- return info.nth !== void 0 ? locator.nth(info.nth) : locator;
2529
- }
2530
- return page.locator(`aria-ref=${normalized}`);
2531
- }
2532
- function toAIFriendlyError(error, selector) {
2533
- const message = error instanceof Error ? error.message : String(error);
2534
- if (message.includes("strict mode violation")) {
2535
- const countMatch = /resolved to (\d+) elements/.exec(message);
2536
- const count = countMatch ? countMatch[1] : "multiple";
2537
- return new Error(
2538
- `Selector "${selector}" matched ${count} elements. Run a new snapshot to get updated refs, or use a different ref.`
2539
- );
2540
- }
2541
- if ((message.includes("Timeout") || message.includes("waiting for")) && (message.includes("to be visible") || message.includes("not visible"))) {
2542
- return new Error(
2543
- `Element "${selector}" not found or not visible. Run a new snapshot to see current page elements.`
2544
- );
2545
- }
2546
- if (message.includes("intercepts pointer events") || message.includes("not visible") || message.includes("not receive pointer events")) {
2547
- return new Error(
2548
- `Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
2549
- );
2550
- }
2551
- const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
2552
- if (timeoutMatch) {
2553
- return new Error(
2554
- `Element "${selector}" timed out after ${timeoutMatch[1]}ms \u2014 element may be hidden or not interactable. Run a new snapshot to see current page elements.`
2555
- );
2556
- }
2557
- const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
2558
- return new Error(cleaned || message);
2559
- }
2560
- function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
2561
- return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
2562
- }
2563
2577
 
2564
2578
  // src/actions/evaluate.ts
2565
2579
  async function evaluateInAllFramesViaPlaywright(opts) {
@@ -2584,7 +2598,8 @@ async function evaluateInAllFramesViaPlaywright(opts) {
2584
2598
  frameName: frame.name(),
2585
2599
  result
2586
2600
  });
2587
- } catch {
2601
+ } catch (err) {
2602
+ console.warn("[browserclaw] frame evaluate failed:", err instanceof Error ? err.message : String(err));
2588
2603
  }
2589
2604
  }
2590
2605
  return results;
@@ -3299,7 +3314,7 @@ async function pressAndHoldViaCdp(opts) {
3299
3314
  async function clickByTextViaPlaywright(opts) {
3300
3315
  const page = await getRestoredPageForTarget(opts);
3301
3316
  const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
3302
- const locator = page.getByText(opts.text, { exact: opts.exact }).and(page.locator(":visible")).or(page.getByTitle(opts.text, { exact: opts.exact })).first();
3317
+ const locator = page.getByText(opts.text, { exact: opts.exact }).or(page.getByTitle(opts.text, { exact: opts.exact })).and(page.locator(":visible")).first();
3303
3318
  try {
3304
3319
  await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
3305
3320
  } catch (err) {
@@ -5067,6 +5082,7 @@ function axValue(v) {
5067
5082
  return "";
5068
5083
  }
5069
5084
  function formatAriaNodes(nodes, limit) {
5085
+ if (nodes.length === 0) return [];
5070
5086
  const byId = /* @__PURE__ */ new Map();
5071
5087
  for (const n of nodes) if (n.nodeId) byId.set(n.nodeId, n);
5072
5088
  const referenced = /* @__PURE__ */ new Set();