cclawd 1.0.2 → 1.0.4

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.
Files changed (68) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/plugin-sdk/active-listener-CN-tMEvN.js +35 -0
  3. package/dist/plugin-sdk/api-key-rotation-CimGYMBc.js +176 -0
  4. package/dist/plugin-sdk/audio-preflight-C-xXBoE2.js +51 -0
  5. package/dist/plugin-sdk/audio-transcription-runner-CTIHpebA.js +2173 -0
  6. package/dist/plugin-sdk/audit-membership-runtime-BFatB2LJ.js +58 -0
  7. package/dist/plugin-sdk/channel-activity-DO0FEzyj.js +95 -0
  8. package/dist/plugin-sdk/channel-web-Da-__nUF.js +2238 -0
  9. package/dist/plugin-sdk/commands-registry-6no2NNrY.js +1118 -0
  10. package/dist/plugin-sdk/compact.runtime-CCoclu5e.js +35 -0
  11. package/dist/plugin-sdk/config-B9ODwgpz.js +37426 -0
  12. package/dist/plugin-sdk/deliver-B1fFpKjV.js +1757 -0
  13. package/dist/plugin-sdk/deliver-runtime-DB-VRMe1.js +15 -0
  14. package/dist/plugin-sdk/deps-send-discord.runtime-DklqycYG.js +15 -0
  15. package/dist/plugin-sdk/deps-send-imessage.runtime-Chs8zeon.js +14 -0
  16. package/dist/plugin-sdk/deps-send-signal.runtime-clW9aSJP.js +13 -0
  17. package/dist/plugin-sdk/deps-send-slack.runtime-BUx0LYY1.js +13 -0
  18. package/dist/plugin-sdk/deps-send-telegram.runtime-LECSHgMG.js +16 -0
  19. package/dist/plugin-sdk/deps-send-whatsapp.runtime-D2d65fw0.js +40 -0
  20. package/dist/plugin-sdk/diagnostic-CxIvS-C2.js +315 -0
  21. package/dist/plugin-sdk/dispatch-BqlR4dPx.js +105863 -0
  22. package/dist/plugin-sdk/env-b9k1PHMI.js +34 -0
  23. package/dist/plugin-sdk/fetch-PoxzAANT.js +326 -0
  24. package/dist/plugin-sdk/fetch-guard-4UVSZ0uS.js +164 -0
  25. package/dist/plugin-sdk/image-Ch6M4tnJ.js +2420 -0
  26. package/dist/plugin-sdk/image-runtime-CSh2o5wY.js +8 -0
  27. package/dist/plugin-sdk/index.js +35 -35
  28. package/dist/plugin-sdk/ir-CugsqGH8.js +1312 -0
  29. package/dist/plugin-sdk/local-roots-adnEg9zb.js +217 -0
  30. package/dist/plugin-sdk/logger-D6zRubj0.js +1164 -0
  31. package/dist/plugin-sdk/login-CYvkQ0At.js +54 -0
  32. package/dist/plugin-sdk/login-qr-ll4NtaT5.js +316 -0
  33. package/dist/plugin-sdk/manager-CHy8IclH.js +3959 -0
  34. package/dist/plugin-sdk/manager-runtime-C70EkEr7.js +11 -0
  35. package/dist/plugin-sdk/outbound-Wzs2iN7X.js +216 -0
  36. package/dist/plugin-sdk/outbound-attachment-khXJwucX.js +17 -0
  37. package/dist/plugin-sdk/paths-BtVqCdw4.js +3063 -0
  38. package/dist/plugin-sdk/pi-model-discovery-Dh4ziodY.js +131 -0
  39. package/dist/plugin-sdk/pi-model-discovery-runtime-b83Xe-HT.js +8 -0
  40. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-C1z5CDBF.js +349 -0
  41. package/dist/plugin-sdk/proxy-fetch-CJEmoBxi.js +54 -0
  42. package/dist/plugin-sdk/pw-ai-Dj3Cvlzl.js +1990 -0
  43. package/dist/plugin-sdk/qmd-manager-egHUAseQ.js +1581 -0
  44. package/dist/plugin-sdk/resolve-outbound-target-BiICvIKs.js +38 -0
  45. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DNApufzW.js +9 -0
  46. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CBmtfIQ8.js +13 -0
  47. package/dist/plugin-sdk/send-CScblaI4.js +532 -0
  48. package/dist/plugin-sdk/send-CeHhnld6.js +407 -0
  49. package/dist/plugin-sdk/send-DP_c8JfR.js +3277 -0
  50. package/dist/plugin-sdk/send-Dc5fI6e8.js +495 -0
  51. package/dist/plugin-sdk/send-l-77_s1_.js +2507 -0
  52. package/dist/plugin-sdk/session-CkOKZaqa.js +166 -0
  53. package/dist/plugin-sdk/signal.js +2 -2
  54. package/dist/plugin-sdk/skill-commands-BohYCgkq.js +336 -0
  55. package/dist/plugin-sdk/slash-commands.runtime-DpLfVTM6.js +8 -0
  56. package/dist/plugin-sdk/slash-dispatch.runtime-CASMHwpm.js +35 -0
  57. package/dist/plugin-sdk/slash-skill-commands.runtime-D7rrJEci.js +9 -0
  58. package/dist/plugin-sdk/sqlite-CJE3X7Mv.js +1005 -0
  59. package/dist/plugin-sdk/subagent-registry-runtime-B1oo5bih.js +35 -0
  60. package/dist/plugin-sdk/tables-D5VgpTmm.js +53 -0
  61. package/dist/plugin-sdk/target-errors-C6zZ_OpA.js +191 -0
  62. package/dist/plugin-sdk/tokens-DUnJnpMS.js +50 -0
  63. package/dist/plugin-sdk/web-TfUM1nSi.js +39 -0
  64. package/dist/plugin-sdk/whatsapp-actions-DuWJ0j1r.js +71 -0
  65. package/extensions/mfa-auth/index.ts +36 -17
  66. package/extensions/mfa-auth/src/auth-manager.ts +4 -0
  67. package/extensions/mfa-auth/src/notification-service.ts +5 -1
  68. package/package.json +1 -1
@@ -0,0 +1,1990 @@
1
+ import { x as writeFileFromPathWithinRoot } from "./paths-BtVqCdw4.js";
2
+ import { Gs as BrowserTabNotFoundError, Hs as formatErrorMessage, Is as DEFAULT_TRACE_DIR, Js as assertBrowserNavigationResultAllowed, Ks as assertBrowserNavigationAllowed, Ls as DEFAULT_UPLOAD_DIR, Ul as formatCliCommand, Ys as withBrowserNavigationPolicy, _s as withCdpSocket, ds as formatAriaSnapshot, fs as normalizeCdpWsUrl, gs as normalizeCdpHttpBaseForJsonEndpoints, hs as getHeadersWithAuth, is as getChromeWebSocketUrl, ms as fetchJson, ps as appendCdpPath, qs as assertBrowserNavigationRedirectChainAllowed, ys as withNoProxyForCdpUrl, zs as resolveStrictExistingPathsWithinRoot } from "./config-B9ODwgpz.js";
3
+ import "./paths-eFexkPEh.js";
4
+ import "./github-copilot-token-Cxf8QYZb.js";
5
+ import { Q as resolvePreferredOpenClawTmpDir } from "./logger-D6zRubj0.js";
6
+ import "./env-b9k1PHMI.js";
7
+ import { t as markPwAiLoaded } from "./pw-ai-state-Ci-t1RlZ.js";
8
+ import path from "node:path";
9
+ import fs from "node:fs/promises";
10
+ import crypto from "node:crypto";
11
+ import { chromium, devices } from "playwright-core";
12
+ //#region src/browser/pw-session.page-cdp.ts
13
+ const OPENCLAW_EXTENSION_RELAY_BROWSER = "OpenClaw/extension-relay";
14
+ const extensionRelayByCdpUrl = /* @__PURE__ */ new Map();
15
+ function normalizeCdpUrl$1(raw) {
16
+ return raw.replace(/\/$/, "");
17
+ }
18
+ async function isExtensionRelayCdpEndpoint(cdpUrl) {
19
+ const normalized = normalizeCdpUrl$1(cdpUrl);
20
+ const cached = extensionRelayByCdpUrl.get(normalized);
21
+ if (cached !== void 0) return cached;
22
+ try {
23
+ const version = await fetchJson(appendCdpPath(normalizeCdpHttpBaseForJsonEndpoints(normalized), "/json/version"), 2e3);
24
+ const isRelay = String(version?.Browser ?? "").trim() === OPENCLAW_EXTENSION_RELAY_BROWSER;
25
+ extensionRelayByCdpUrl.set(normalized, isRelay);
26
+ return isRelay;
27
+ } catch {
28
+ extensionRelayByCdpUrl.set(normalized, false);
29
+ return false;
30
+ }
31
+ }
32
+ async function withPlaywrightPageCdpSession(page, fn) {
33
+ const session = await page.context().newCDPSession(page);
34
+ try {
35
+ return await fn(session);
36
+ } finally {
37
+ await session.detach().catch(() => {});
38
+ }
39
+ }
40
+ async function withPageScopedCdpClient(opts) {
41
+ const targetId = opts.targetId?.trim();
42
+ if (targetId && await isExtensionRelayCdpEndpoint(opts.cdpUrl)) {
43
+ const wsUrl = await getChromeWebSocketUrl(opts.cdpUrl, 2e3);
44
+ if (!wsUrl) throw new Error("CDP websocket unavailable");
45
+ return await withCdpSocket(wsUrl, async (send) => {
46
+ return await opts.fn((method, params) => send(method, {
47
+ ...params,
48
+ targetId
49
+ }));
50
+ });
51
+ }
52
+ return await withPlaywrightPageCdpSession(opts.page, async (session) => {
53
+ return await opts.fn((method, params) => session.send(method, params));
54
+ });
55
+ }
56
+ //#endregion
57
+ //#region src/browser/pw-session.ts
58
+ const pageStates = /* @__PURE__ */ new WeakMap();
59
+ const contextStates = /* @__PURE__ */ new WeakMap();
60
+ const observedContexts = /* @__PURE__ */ new WeakSet();
61
+ const observedPages = /* @__PURE__ */ new WeakSet();
62
+ const roleRefsByTarget = /* @__PURE__ */ new Map();
63
+ const MAX_ROLE_REFS_CACHE = 50;
64
+ const MAX_CONSOLE_MESSAGES = 500;
65
+ const MAX_PAGE_ERRORS = 200;
66
+ const MAX_NETWORK_REQUESTS = 500;
67
+ const cachedByCdpUrl = /* @__PURE__ */ new Map();
68
+ const connectingByCdpUrl = /* @__PURE__ */ new Map();
69
+ function normalizeCdpUrl(raw) {
70
+ return raw.replace(/\/$/, "");
71
+ }
72
+ function findNetworkRequestById(state, id) {
73
+ for (let i = state.requests.length - 1; i >= 0; i -= 1) {
74
+ const candidate = state.requests[i];
75
+ if (candidate && candidate.id === id) return candidate;
76
+ }
77
+ }
78
+ function roleRefsKey(cdpUrl, targetId) {
79
+ return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
80
+ }
81
+ function rememberRoleRefsForTarget(opts) {
82
+ const targetId = opts.targetId.trim();
83
+ if (!targetId) return;
84
+ roleRefsByTarget.set(roleRefsKey(opts.cdpUrl, targetId), {
85
+ refs: opts.refs,
86
+ ...opts.frameSelector ? { frameSelector: opts.frameSelector } : {},
87
+ ...opts.mode ? { mode: opts.mode } : {}
88
+ });
89
+ while (roleRefsByTarget.size > MAX_ROLE_REFS_CACHE) {
90
+ const first = roleRefsByTarget.keys().next();
91
+ if (first.done) break;
92
+ roleRefsByTarget.delete(first.value);
93
+ }
94
+ }
95
+ function storeRoleRefsForTarget(opts) {
96
+ const state = ensurePageState(opts.page);
97
+ state.roleRefs = opts.refs;
98
+ state.roleRefsFrameSelector = opts.frameSelector;
99
+ state.roleRefsMode = opts.mode;
100
+ if (!opts.targetId?.trim()) return;
101
+ rememberRoleRefsForTarget({
102
+ cdpUrl: opts.cdpUrl,
103
+ targetId: opts.targetId,
104
+ refs: opts.refs,
105
+ frameSelector: opts.frameSelector,
106
+ mode: opts.mode
107
+ });
108
+ }
109
+ function restoreRoleRefsForTarget(opts) {
110
+ const targetId = opts.targetId?.trim() || "";
111
+ if (!targetId) return;
112
+ const cached = roleRefsByTarget.get(roleRefsKey(opts.cdpUrl, targetId));
113
+ if (!cached) return;
114
+ const state = ensurePageState(opts.page);
115
+ if (state.roleRefs) return;
116
+ state.roleRefs = cached.refs;
117
+ state.roleRefsFrameSelector = cached.frameSelector;
118
+ state.roleRefsMode = cached.mode;
119
+ }
120
+ function ensurePageState(page) {
121
+ const existing = pageStates.get(page);
122
+ if (existing) return existing;
123
+ const state = {
124
+ console: [],
125
+ errors: [],
126
+ requests: [],
127
+ requestIds: /* @__PURE__ */ new WeakMap(),
128
+ nextRequestId: 0,
129
+ armIdUpload: 0,
130
+ armIdDialog: 0,
131
+ armIdDownload: 0
132
+ };
133
+ pageStates.set(page, state);
134
+ if (!observedPages.has(page)) {
135
+ observedPages.add(page);
136
+ page.on("console", (msg) => {
137
+ const entry = {
138
+ type: msg.type(),
139
+ text: msg.text(),
140
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
141
+ location: msg.location()
142
+ };
143
+ state.console.push(entry);
144
+ if (state.console.length > MAX_CONSOLE_MESSAGES) state.console.shift();
145
+ });
146
+ page.on("pageerror", (err) => {
147
+ state.errors.push({
148
+ message: err?.message ? String(err.message) : String(err),
149
+ name: err?.name ? String(err.name) : void 0,
150
+ stack: err?.stack ? String(err.stack) : void 0,
151
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
152
+ });
153
+ if (state.errors.length > MAX_PAGE_ERRORS) state.errors.shift();
154
+ });
155
+ page.on("request", (req) => {
156
+ state.nextRequestId += 1;
157
+ const id = `r${state.nextRequestId}`;
158
+ state.requestIds.set(req, id);
159
+ state.requests.push({
160
+ id,
161
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
162
+ method: req.method(),
163
+ url: req.url(),
164
+ resourceType: req.resourceType()
165
+ });
166
+ if (state.requests.length > MAX_NETWORK_REQUESTS) state.requests.shift();
167
+ });
168
+ page.on("response", (resp) => {
169
+ const req = resp.request();
170
+ const id = state.requestIds.get(req);
171
+ if (!id) return;
172
+ const rec = findNetworkRequestById(state, id);
173
+ if (!rec) return;
174
+ rec.status = resp.status();
175
+ rec.ok = resp.ok();
176
+ });
177
+ page.on("requestfailed", (req) => {
178
+ const id = state.requestIds.get(req);
179
+ if (!id) return;
180
+ const rec = findNetworkRequestById(state, id);
181
+ if (!rec) return;
182
+ rec.failureText = req.failure()?.errorText;
183
+ rec.ok = false;
184
+ });
185
+ page.on("close", () => {
186
+ pageStates.delete(page);
187
+ observedPages.delete(page);
188
+ });
189
+ }
190
+ return state;
191
+ }
192
+ function observeContext(context) {
193
+ if (observedContexts.has(context)) return;
194
+ observedContexts.add(context);
195
+ ensureContextState(context);
196
+ for (const page of context.pages()) ensurePageState(page);
197
+ context.on("page", (page) => ensurePageState(page));
198
+ }
199
+ function ensureContextState(context) {
200
+ const existing = contextStates.get(context);
201
+ if (existing) return existing;
202
+ const state = { traceActive: false };
203
+ contextStates.set(context, state);
204
+ return state;
205
+ }
206
+ function observeBrowser(browser) {
207
+ for (const context of browser.contexts()) observeContext(context);
208
+ }
209
+ async function connectBrowser(cdpUrl) {
210
+ const normalized = normalizeCdpUrl(cdpUrl);
211
+ const cached = cachedByCdpUrl.get(normalized);
212
+ if (cached) return cached;
213
+ const connecting = connectingByCdpUrl.get(normalized);
214
+ if (connecting) return await connecting;
215
+ const connectWithRetry = async () => {
216
+ let lastErr;
217
+ for (let attempt = 0; attempt < 3; attempt += 1) try {
218
+ const timeout = 5e3 + attempt * 2e3;
219
+ const endpoint = await getChromeWebSocketUrl(normalized, timeout).catch(() => null) ?? normalized;
220
+ const headers = getHeadersWithAuth(endpoint);
221
+ const browser = await withNoProxyForCdpUrl(endpoint, () => chromium.connectOverCDP(endpoint, {
222
+ timeout,
223
+ headers
224
+ }));
225
+ const onDisconnected = () => {
226
+ if (cachedByCdpUrl.get(normalized)?.browser === browser) cachedByCdpUrl.delete(normalized);
227
+ };
228
+ const connected = {
229
+ browser,
230
+ cdpUrl: normalized,
231
+ onDisconnected
232
+ };
233
+ cachedByCdpUrl.set(normalized, connected);
234
+ browser.on("disconnected", onDisconnected);
235
+ observeBrowser(browser);
236
+ return connected;
237
+ } catch (err) {
238
+ lastErr = err;
239
+ const delay = 250 + attempt * 250;
240
+ await new Promise((r) => setTimeout(r, delay));
241
+ }
242
+ if (lastErr instanceof Error) throw lastErr;
243
+ const message = lastErr ? formatErrorMessage(lastErr) : "CDP connect failed";
244
+ throw new Error(message);
245
+ };
246
+ const pending = connectWithRetry().finally(() => {
247
+ connectingByCdpUrl.delete(normalized);
248
+ });
249
+ connectingByCdpUrl.set(normalized, pending);
250
+ return await pending;
251
+ }
252
+ async function getAllPages(browser) {
253
+ return browser.contexts().flatMap((c) => c.pages());
254
+ }
255
+ async function pageTargetId(page) {
256
+ const session = await page.context().newCDPSession(page);
257
+ try {
258
+ const info = await session.send("Target.getTargetInfo");
259
+ return String(info?.targetInfo?.targetId ?? "").trim() || null;
260
+ } finally {
261
+ await session.detach().catch(() => {});
262
+ }
263
+ }
264
+ function matchPageByTargetList(pages, targets, targetId) {
265
+ const target = targets.find((entry) => entry.id === targetId);
266
+ if (!target) return null;
267
+ const urlMatch = pages.filter((page) => page.url() === target.url);
268
+ if (urlMatch.length === 1) return urlMatch[0] ?? null;
269
+ if (urlMatch.length > 1) {
270
+ const sameUrlTargets = targets.filter((entry) => entry.url === target.url);
271
+ if (sameUrlTargets.length === urlMatch.length) {
272
+ const idx = sameUrlTargets.findIndex((entry) => entry.id === targetId);
273
+ if (idx >= 0 && idx < urlMatch.length) return urlMatch[idx] ?? null;
274
+ }
275
+ }
276
+ return null;
277
+ }
278
+ async function findPageByTargetIdViaTargetList(pages, targetId, cdpUrl) {
279
+ return matchPageByTargetList(pages, await fetchJson(appendCdpPath(normalizeCdpHttpBaseForJsonEndpoints(cdpUrl), "/json/list"), 2e3), targetId);
280
+ }
281
+ async function findPageByTargetId(browser, targetId, cdpUrl) {
282
+ const pages = await getAllPages(browser);
283
+ const isExtensionRelay = cdpUrl ? await isExtensionRelayCdpEndpoint(cdpUrl).catch(() => false) : false;
284
+ if (cdpUrl && isExtensionRelay) {
285
+ try {
286
+ const matched = await findPageByTargetIdViaTargetList(pages, targetId, cdpUrl);
287
+ if (matched) return matched;
288
+ } catch {}
289
+ return pages.length === 1 ? pages[0] ?? null : null;
290
+ }
291
+ let resolvedViaCdp = false;
292
+ for (const page of pages) {
293
+ let tid = null;
294
+ try {
295
+ tid = await pageTargetId(page);
296
+ resolvedViaCdp = true;
297
+ } catch {
298
+ tid = null;
299
+ }
300
+ if (tid && tid === targetId) return page;
301
+ }
302
+ if (cdpUrl) try {
303
+ return await findPageByTargetIdViaTargetList(pages, targetId, cdpUrl);
304
+ } catch {}
305
+ if (!resolvedViaCdp && pages.length === 1) return pages[0] ?? null;
306
+ return null;
307
+ }
308
+ async function resolvePageByTargetIdOrThrow(opts) {
309
+ const { browser } = await connectBrowser(opts.cdpUrl);
310
+ const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
311
+ if (!page) throw new BrowserTabNotFoundError();
312
+ return page;
313
+ }
314
+ async function getPageForTargetId(opts) {
315
+ const { browser } = await connectBrowser(opts.cdpUrl);
316
+ const pages = await getAllPages(browser);
317
+ if (!pages.length) throw new Error("No pages available in the connected browser.");
318
+ const first = pages[0];
319
+ if (!opts.targetId) return first;
320
+ const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
321
+ if (!found) {
322
+ if (pages.length === 1) return first;
323
+ throw new BrowserTabNotFoundError();
324
+ }
325
+ return found;
326
+ }
327
+ function refLocator(page, ref) {
328
+ const normalized = ref.startsWith("@") ? ref.slice(1) : ref.startsWith("ref=") ? ref.slice(4) : ref;
329
+ if (/^e\d+$/.test(normalized)) {
330
+ const state = pageStates.get(page);
331
+ if (state?.roleRefsMode === "aria") return (state.roleRefsFrameSelector ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
332
+ const info = state?.roleRefs?.[normalized];
333
+ if (!info) throw new Error(`Unknown ref "${normalized}". Run a new snapshot and use a ref from that snapshot.`);
334
+ const locAny = state?.roleRefsFrameSelector ? page.frameLocator(state.roleRefsFrameSelector) : page;
335
+ const locator = info.name ? locAny.getByRole(info.role, {
336
+ name: info.name,
337
+ exact: true
338
+ }) : locAny.getByRole(info.role);
339
+ return info.nth !== void 0 ? locator.nth(info.nth) : locator;
340
+ }
341
+ return page.locator(`aria-ref=${normalized}`);
342
+ }
343
+ async function closePlaywrightBrowserConnection(opts) {
344
+ const normalized = opts?.cdpUrl ? normalizeCdpUrl(opts.cdpUrl) : null;
345
+ if (normalized) {
346
+ const cur = cachedByCdpUrl.get(normalized);
347
+ cachedByCdpUrl.delete(normalized);
348
+ connectingByCdpUrl.delete(normalized);
349
+ if (!cur) return;
350
+ if (cur.onDisconnected && typeof cur.browser.off === "function") cur.browser.off("disconnected", cur.onDisconnected);
351
+ await cur.browser.close().catch(() => {});
352
+ return;
353
+ }
354
+ const connections = Array.from(cachedByCdpUrl.values());
355
+ cachedByCdpUrl.clear();
356
+ connectingByCdpUrl.clear();
357
+ for (const cur of connections) {
358
+ if (cur.onDisconnected && typeof cur.browser.off === "function") cur.browser.off("disconnected", cur.onDisconnected);
359
+ await cur.browser.close().catch(() => {});
360
+ }
361
+ }
362
+ function cdpSocketNeedsAttach(wsUrl) {
363
+ try {
364
+ const pathname = new URL(wsUrl).pathname;
365
+ return pathname === "/cdp" || pathname.endsWith("/cdp") || pathname.includes("/devtools/browser/");
366
+ } catch {
367
+ return false;
368
+ }
369
+ }
370
+ async function tryTerminateExecutionViaCdp(opts) {
371
+ const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints(opts.cdpUrl);
372
+ const pages = await fetchJson(appendCdpPath(cdpHttpBase, "/json/list"), 2e3).catch(() => null);
373
+ if (!pages || pages.length === 0) return;
374
+ const target = pages.find((p) => String(p.id ?? "").trim() === opts.targetId);
375
+ const wsUrlRaw = String(target?.webSocketDebuggerUrl ?? "").trim();
376
+ if (!wsUrlRaw) return;
377
+ const wsUrl = normalizeCdpWsUrl(wsUrlRaw, cdpHttpBase);
378
+ const needsAttach = cdpSocketNeedsAttach(wsUrl);
379
+ const runWithTimeout = async (work, ms) => {
380
+ let timer;
381
+ const timeoutPromise = new Promise((_, reject) => {
382
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("CDP command timed out")), ms);
383
+ });
384
+ try {
385
+ return await Promise.race([work, timeoutPromise]);
386
+ } finally {
387
+ if (timer) clearTimeout(timer);
388
+ }
389
+ };
390
+ await withCdpSocket(wsUrl, async (send) => {
391
+ let sessionId;
392
+ try {
393
+ if (needsAttach) {
394
+ const attached = await runWithTimeout(send("Target.attachToTarget", {
395
+ targetId: opts.targetId,
396
+ flatten: true
397
+ }), 1500);
398
+ if (typeof attached?.sessionId === "string" && attached.sessionId.trim()) sessionId = attached.sessionId;
399
+ }
400
+ await runWithTimeout(send("Runtime.terminateExecution", void 0, sessionId), 1500);
401
+ if (sessionId) send("Target.detachFromTarget", { sessionId }).catch(() => {});
402
+ } catch {}
403
+ }, { handshakeTimeoutMs: 2e3 }).catch(() => {});
404
+ }
405
+ /**
406
+ * Best-effort cancellation for stuck page operations.
407
+ *
408
+ * Playwright serializes CDP commands per page; a long-running or stuck operation (notably evaluate)
409
+ * can block all subsequent commands. We cannot safely "cancel" an individual command, and we do
410
+ * not want to close the actual Chromium tab. Instead, we disconnect Playwright's CDP connection
411
+ * so in-flight commands fail fast and the next request reconnects transparently.
412
+ *
413
+ * IMPORTANT: We CANNOT call Connection.close() because Playwright shares a single Connection
414
+ * across all objects (BrowserType, Browser, etc.). Closing it corrupts the entire Playwright
415
+ * instance, preventing reconnection.
416
+ *
417
+ * Instead we:
418
+ * 1. Null out `cached` so the next call triggers a fresh connectOverCDP
419
+ * 2. Fire-and-forget browser.close() — it may hang but won't block us
420
+ * 3. The next connectBrowser() creates a completely new CDP WebSocket connection
421
+ *
422
+ * The old browser.close() eventually resolves when the in-browser evaluate timeout fires,
423
+ * or the old connection gets GC'd. Either way, it doesn't affect the fresh connection.
424
+ */
425
+ async function forceDisconnectPlaywrightForTarget(opts) {
426
+ const normalized = normalizeCdpUrl(opts.cdpUrl);
427
+ const cur = cachedByCdpUrl.get(normalized);
428
+ if (!cur) return;
429
+ cachedByCdpUrl.delete(normalized);
430
+ connectingByCdpUrl.delete(normalized);
431
+ if (cur.onDisconnected && typeof cur.browser.off === "function") cur.browser.off("disconnected", cur.onDisconnected);
432
+ const targetId = opts.targetId?.trim() || "";
433
+ if (targetId) await tryTerminateExecutionViaCdp({
434
+ cdpUrl: normalized,
435
+ targetId
436
+ }).catch(() => {});
437
+ cur.browser.close().catch(() => {});
438
+ }
439
+ /**
440
+ * List all pages/tabs from the persistent Playwright connection.
441
+ * Used for remote profiles where HTTP-based /json/list is ephemeral.
442
+ */
443
+ async function listPagesViaPlaywright(opts) {
444
+ const { browser } = await connectBrowser(opts.cdpUrl);
445
+ const pages = await getAllPages(browser);
446
+ const results = [];
447
+ for (const page of pages) {
448
+ const tid = await pageTargetId(page).catch(() => null);
449
+ if (tid) results.push({
450
+ targetId: tid,
451
+ title: await page.title().catch(() => ""),
452
+ url: page.url(),
453
+ type: "page"
454
+ });
455
+ }
456
+ return results;
457
+ }
458
+ /**
459
+ * Create a new page/tab using the persistent Playwright connection.
460
+ * Used for remote profiles where HTTP-based /json/new is ephemeral.
461
+ * Returns the new page's targetId and metadata.
462
+ */
463
+ async function createPageViaPlaywright(opts) {
464
+ const { browser } = await connectBrowser(opts.cdpUrl);
465
+ const context = browser.contexts()[0] ?? await browser.newContext();
466
+ ensureContextState(context);
467
+ const page = await context.newPage();
468
+ ensurePageState(page);
469
+ const targetUrl = opts.url.trim() || "about:blank";
470
+ if (targetUrl !== "about:blank") {
471
+ const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy);
472
+ await assertBrowserNavigationAllowed({
473
+ url: targetUrl,
474
+ ...navigationPolicy
475
+ });
476
+ await assertBrowserNavigationRedirectChainAllowed({
477
+ request: (await page.goto(targetUrl, { timeout: 3e4 }).catch(() => {
478
+ return null;
479
+ }))?.request(),
480
+ ...navigationPolicy
481
+ });
482
+ await assertBrowserNavigationResultAllowed({
483
+ url: page.url(),
484
+ ...navigationPolicy
485
+ });
486
+ }
487
+ const tid = await pageTargetId(page).catch(() => null);
488
+ if (!tid) throw new Error("Failed to get targetId for new page");
489
+ return {
490
+ targetId: tid,
491
+ title: await page.title().catch(() => ""),
492
+ url: page.url(),
493
+ type: "page"
494
+ };
495
+ }
496
+ /**
497
+ * Close a page/tab by targetId using the persistent Playwright connection.
498
+ * Used for remote profiles where HTTP-based /json/close is ephemeral.
499
+ */
500
+ async function closePageByTargetIdViaPlaywright(opts) {
501
+ await (await resolvePageByTargetIdOrThrow(opts)).close();
502
+ }
503
+ /**
504
+ * Focus a page/tab by targetId using the persistent Playwright connection.
505
+ * Used for remote profiles where HTTP-based /json/activate can be ephemeral.
506
+ */
507
+ async function focusPageByTargetIdViaPlaywright(opts) {
508
+ const page = await resolvePageByTargetIdOrThrow(opts);
509
+ try {
510
+ await page.bringToFront();
511
+ } catch (err) {
512
+ try {
513
+ await withPageScopedCdpClient({
514
+ cdpUrl: opts.cdpUrl,
515
+ page,
516
+ targetId: opts.targetId,
517
+ fn: async (send) => {
518
+ await send("Page.bringToFront");
519
+ }
520
+ });
521
+ return;
522
+ } catch {
523
+ throw err;
524
+ }
525
+ }
526
+ }
527
+ //#endregion
528
+ //#region src/browser/pw-tools-core.activity.ts
529
+ async function getPageErrorsViaPlaywright(opts) {
530
+ const state = ensurePageState(await getPageForTargetId(opts));
531
+ const errors = [...state.errors];
532
+ if (opts.clear) state.errors = [];
533
+ return { errors };
534
+ }
535
+ async function getNetworkRequestsViaPlaywright(opts) {
536
+ const state = ensurePageState(await getPageForTargetId(opts));
537
+ const raw = [...state.requests];
538
+ const filter = typeof opts.filter === "string" ? opts.filter.trim() : "";
539
+ const requests = filter ? raw.filter((r) => r.url.includes(filter)) : raw;
540
+ if (opts.clear) {
541
+ state.requests = [];
542
+ state.requestIds = /* @__PURE__ */ new WeakMap();
543
+ }
544
+ return { requests };
545
+ }
546
+ function consolePriority(level) {
547
+ switch (level) {
548
+ case "error": return 3;
549
+ case "warning": return 2;
550
+ case "info":
551
+ case "log": return 1;
552
+ case "debug": return 0;
553
+ default: return 1;
554
+ }
555
+ }
556
+ async function getConsoleMessagesViaPlaywright(opts) {
557
+ const state = ensurePageState(await getPageForTargetId(opts));
558
+ if (!opts.level) return [...state.console];
559
+ const min = consolePriority(opts.level);
560
+ return state.console.filter((msg) => consolePriority(msg.type) >= min);
561
+ }
562
+ //#endregion
563
+ //#region src/browser/safe-filename.ts
564
+ function sanitizeUntrustedFileName(fileName, fallbackName) {
565
+ const trimmed = String(fileName ?? "").trim();
566
+ if (!trimmed) return fallbackName;
567
+ let base = path.posix.basename(trimmed);
568
+ base = path.win32.basename(base);
569
+ let cleaned = "";
570
+ for (let i = 0; i < base.length; i++) {
571
+ const code = base.charCodeAt(i);
572
+ if (code < 32 || code === 127) continue;
573
+ cleaned += base[i];
574
+ }
575
+ base = cleaned.trim();
576
+ if (!base || base === "." || base === "..") return fallbackName;
577
+ if (base.length > 200) base = base.slice(0, 200);
578
+ return base;
579
+ }
580
+ //#endregion
581
+ //#region src/browser/output-atomic.ts
582
+ function buildSiblingTempPath(targetPath) {
583
+ const id = crypto.randomUUID();
584
+ const safeTail = sanitizeUntrustedFileName(path.basename(targetPath), "output.bin");
585
+ return path.join(path.dirname(targetPath), `.openclaw-output-${id}-${safeTail}.part`);
586
+ }
587
+ async function writeViaSiblingTempPath(params) {
588
+ const rootDir = await fs.realpath(path.resolve(params.rootDir)).catch(() => path.resolve(params.rootDir));
589
+ const requestedTargetPath = path.resolve(params.targetPath);
590
+ const targetPath = await fs.realpath(path.dirname(requestedTargetPath)).then((realDir) => path.join(realDir, path.basename(requestedTargetPath))).catch(() => requestedTargetPath);
591
+ const relativeTargetPath = path.relative(rootDir, targetPath);
592
+ if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeTargetPath)) throw new Error("Target path is outside the allowed root");
593
+ const tempPath = buildSiblingTempPath(targetPath);
594
+ let renameSucceeded = false;
595
+ try {
596
+ await params.writeTemp(tempPath);
597
+ await writeFileFromPathWithinRoot({
598
+ rootDir,
599
+ relativePath: relativeTargetPath,
600
+ sourcePath: tempPath,
601
+ mkdir: false
602
+ });
603
+ renameSucceeded = true;
604
+ } finally {
605
+ if (!renameSucceeded) await fs.rm(tempPath, { force: true }).catch(() => {});
606
+ }
607
+ }
608
+ //#endregion
609
+ //#region src/browser/pw-role-snapshot.ts
610
+ const INTERACTIVE_ROLES = new Set([
611
+ "button",
612
+ "link",
613
+ "textbox",
614
+ "checkbox",
615
+ "radio",
616
+ "combobox",
617
+ "listbox",
618
+ "menuitem",
619
+ "menuitemcheckbox",
620
+ "menuitemradio",
621
+ "option",
622
+ "searchbox",
623
+ "slider",
624
+ "spinbutton",
625
+ "switch",
626
+ "tab",
627
+ "treeitem"
628
+ ]);
629
+ const CONTENT_ROLES = new Set([
630
+ "heading",
631
+ "cell",
632
+ "gridcell",
633
+ "columnheader",
634
+ "rowheader",
635
+ "listitem",
636
+ "article",
637
+ "region",
638
+ "main",
639
+ "navigation"
640
+ ]);
641
+ const STRUCTURAL_ROLES = new Set([
642
+ "generic",
643
+ "group",
644
+ "list",
645
+ "table",
646
+ "row",
647
+ "rowgroup",
648
+ "grid",
649
+ "treegrid",
650
+ "menu",
651
+ "menubar",
652
+ "toolbar",
653
+ "tablist",
654
+ "tree",
655
+ "directory",
656
+ "document",
657
+ "application",
658
+ "presentation",
659
+ "none"
660
+ ]);
661
+ function getRoleSnapshotStats(snapshot, refs) {
662
+ const interactive = Object.values(refs).filter((r) => INTERACTIVE_ROLES.has(r.role)).length;
663
+ return {
664
+ lines: snapshot.split("\n").length,
665
+ chars: snapshot.length,
666
+ refs: Object.keys(refs).length,
667
+ interactive
668
+ };
669
+ }
670
+ function getIndentLevel(line) {
671
+ const match = line.match(/^(\s*)/);
672
+ return match ? Math.floor(match[1].length / 2) : 0;
673
+ }
674
+ function matchInteractiveSnapshotLine(line, options) {
675
+ const depth = getIndentLevel(line);
676
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) return null;
677
+ const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
678
+ if (!match) return null;
679
+ const [, , roleRaw, name, suffix] = match;
680
+ if (roleRaw.startsWith("/")) return null;
681
+ return {
682
+ roleRaw,
683
+ role: roleRaw.toLowerCase(),
684
+ ...name ? { name } : {},
685
+ suffix
686
+ };
687
+ }
688
+ function createRoleNameTracker() {
689
+ const counts = /* @__PURE__ */ new Map();
690
+ const refsByKey = /* @__PURE__ */ new Map();
691
+ return {
692
+ counts,
693
+ refsByKey,
694
+ getKey(role, name) {
695
+ return `${role}:${name ?? ""}`;
696
+ },
697
+ getNextIndex(role, name) {
698
+ const key = this.getKey(role, name);
699
+ const current = counts.get(key) ?? 0;
700
+ counts.set(key, current + 1);
701
+ return current;
702
+ },
703
+ trackRef(role, name, ref) {
704
+ const key = this.getKey(role, name);
705
+ const list = refsByKey.get(key) ?? [];
706
+ list.push(ref);
707
+ refsByKey.set(key, list);
708
+ },
709
+ getDuplicateKeys() {
710
+ const out = /* @__PURE__ */ new Set();
711
+ for (const [key, refs] of refsByKey) if (refs.length > 1) out.add(key);
712
+ return out;
713
+ }
714
+ };
715
+ }
716
+ function removeNthFromNonDuplicates(refs, tracker) {
717
+ const duplicates = tracker.getDuplicateKeys();
718
+ for (const [ref, data] of Object.entries(refs)) {
719
+ const key = tracker.getKey(data.role, data.name);
720
+ if (!duplicates.has(key)) delete refs[ref]?.nth;
721
+ }
722
+ }
723
+ function compactTree(tree) {
724
+ const lines = tree.split("\n");
725
+ const result = [];
726
+ for (let i = 0; i < lines.length; i += 1) {
727
+ const line = lines[i];
728
+ if (line.includes("[ref=")) {
729
+ result.push(line);
730
+ continue;
731
+ }
732
+ if (line.includes(":") && !line.trimEnd().endsWith(":")) {
733
+ result.push(line);
734
+ continue;
735
+ }
736
+ const currentIndent = getIndentLevel(line);
737
+ let hasRelevantChildren = false;
738
+ for (let j = i + 1; j < lines.length; j += 1) {
739
+ if (getIndentLevel(lines[j]) <= currentIndent) break;
740
+ if (lines[j]?.includes("[ref=")) {
741
+ hasRelevantChildren = true;
742
+ break;
743
+ }
744
+ }
745
+ if (hasRelevantChildren) result.push(line);
746
+ }
747
+ return result.join("\n");
748
+ }
749
+ function processLine(line, refs, options, tracker, nextRef) {
750
+ const depth = getIndentLevel(line);
751
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) return null;
752
+ const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
753
+ if (!match) return options.interactive ? null : line;
754
+ const [, prefix, roleRaw, name, suffix] = match;
755
+ if (roleRaw.startsWith("/")) return options.interactive ? null : line;
756
+ const role = roleRaw.toLowerCase();
757
+ const isInteractive = INTERACTIVE_ROLES.has(role);
758
+ const isContent = CONTENT_ROLES.has(role);
759
+ const isStructural = STRUCTURAL_ROLES.has(role);
760
+ if (options.interactive && !isInteractive) return null;
761
+ if (options.compact && isStructural && !name) return null;
762
+ if (!(isInteractive || isContent && name)) return line;
763
+ const ref = nextRef();
764
+ const nth = tracker.getNextIndex(role, name);
765
+ tracker.trackRef(role, name, ref);
766
+ refs[ref] = {
767
+ role,
768
+ name,
769
+ nth
770
+ };
771
+ let enhanced = `${prefix}${roleRaw}`;
772
+ if (name) enhanced += ` "${name}"`;
773
+ enhanced += ` [ref=${ref}]`;
774
+ if (nth > 0) enhanced += ` [nth=${nth}]`;
775
+ if (suffix) enhanced += suffix;
776
+ return enhanced;
777
+ }
778
+ function buildInteractiveSnapshotLines(params) {
779
+ const out = [];
780
+ for (const line of params.lines) {
781
+ const parsed = matchInteractiveSnapshotLine(line, params.options);
782
+ if (!parsed) continue;
783
+ if (!INTERACTIVE_ROLES.has(parsed.role)) continue;
784
+ const resolved = params.resolveRef(parsed);
785
+ if (!resolved?.ref) continue;
786
+ params.recordRef(parsed, resolved.ref, resolved.nth);
787
+ let enhanced = `- ${parsed.roleRaw}`;
788
+ if (parsed.name) enhanced += ` "${parsed.name}"`;
789
+ enhanced += ` [ref=${resolved.ref}]`;
790
+ if ((resolved.nth ?? 0) > 0) enhanced += ` [nth=${resolved.nth}]`;
791
+ if (params.includeSuffix(parsed.suffix)) enhanced += parsed.suffix;
792
+ out.push(enhanced);
793
+ }
794
+ return out;
795
+ }
796
+ function parseRoleRef(raw) {
797
+ const trimmed = raw.trim();
798
+ if (!trimmed) return null;
799
+ const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
800
+ return /^e\d+$/.test(normalized) ? normalized : null;
801
+ }
802
+ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
803
+ const lines = ariaSnapshot.split("\n");
804
+ const refs = {};
805
+ const tracker = createRoleNameTracker();
806
+ let counter = 0;
807
+ const nextRef = () => {
808
+ counter += 1;
809
+ return `e${counter}`;
810
+ };
811
+ if (options.interactive) {
812
+ const result = buildInteractiveSnapshotLines({
813
+ lines,
814
+ options,
815
+ resolveRef: ({ role, name }) => {
816
+ const ref = nextRef();
817
+ const nth = tracker.getNextIndex(role, name);
818
+ tracker.trackRef(role, name, ref);
819
+ return {
820
+ ref,
821
+ nth
822
+ };
823
+ },
824
+ recordRef: ({ role, name }, ref, nth) => {
825
+ refs[ref] = {
826
+ role,
827
+ name,
828
+ nth
829
+ };
830
+ },
831
+ includeSuffix: (suffix) => suffix.includes("[")
832
+ });
833
+ removeNthFromNonDuplicates(refs, tracker);
834
+ return {
835
+ snapshot: result.join("\n") || "(no interactive elements)",
836
+ refs
837
+ };
838
+ }
839
+ const result = [];
840
+ for (const line of lines) {
841
+ const processed = processLine(line, refs, options, tracker, nextRef);
842
+ if (processed !== null) result.push(processed);
843
+ }
844
+ removeNthFromNonDuplicates(refs, tracker);
845
+ const tree = result.join("\n") || "(empty)";
846
+ return {
847
+ snapshot: options.compact ? compactTree(tree) : tree,
848
+ refs
849
+ };
850
+ }
851
+ function parseAiSnapshotRef(suffix) {
852
+ const match = suffix.match(/\[ref=(e\d+)\]/i);
853
+ return match ? match[1] : null;
854
+ }
855
+ /**
856
+ * Build a role snapshot from Playwright's AI snapshot output while preserving Playwright's own
857
+ * aria-ref ids (e.g. ref=e13). This makes the refs self-resolving across calls.
858
+ */
859
+ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
860
+ const lines = String(aiSnapshot ?? "").split("\n");
861
+ const refs = {};
862
+ if (options.interactive) return {
863
+ snapshot: buildInteractiveSnapshotLines({
864
+ lines,
865
+ options,
866
+ resolveRef: ({ suffix }) => {
867
+ const ref = parseAiSnapshotRef(suffix);
868
+ return ref ? { ref } : null;
869
+ },
870
+ recordRef: ({ role, name }, ref) => {
871
+ refs[ref] = {
872
+ role,
873
+ ...name ? { name } : {}
874
+ };
875
+ },
876
+ includeSuffix: () => true
877
+ }).join("\n") || "(no interactive elements)",
878
+ refs
879
+ };
880
+ const out = [];
881
+ for (const line of lines) {
882
+ const depth = getIndentLevel(line);
883
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) continue;
884
+ const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
885
+ if (!match) {
886
+ out.push(line);
887
+ continue;
888
+ }
889
+ const [, , roleRaw, name, suffix] = match;
890
+ if (roleRaw.startsWith("/")) {
891
+ out.push(line);
892
+ continue;
893
+ }
894
+ const role = roleRaw.toLowerCase();
895
+ const isStructural = STRUCTURAL_ROLES.has(role);
896
+ if (options.compact && isStructural && !name) continue;
897
+ const ref = parseAiSnapshotRef(suffix);
898
+ if (ref) refs[ref] = {
899
+ role,
900
+ ...name ? { name } : {}
901
+ };
902
+ out.push(line);
903
+ }
904
+ const tree = out.join("\n") || "(empty)";
905
+ return {
906
+ snapshot: options.compact ? compactTree(tree) : tree,
907
+ refs
908
+ };
909
+ }
910
+ //#endregion
911
+ //#region src/browser/pw-tools-core.shared.ts
912
+ let nextUploadArmId = 0;
913
+ let nextDialogArmId = 0;
914
+ let nextDownloadArmId = 0;
915
+ function bumpUploadArmId() {
916
+ nextUploadArmId += 1;
917
+ return nextUploadArmId;
918
+ }
919
+ function bumpDialogArmId() {
920
+ nextDialogArmId += 1;
921
+ return nextDialogArmId;
922
+ }
923
+ function bumpDownloadArmId() {
924
+ nextDownloadArmId += 1;
925
+ return nextDownloadArmId;
926
+ }
927
+ function requireRef(value) {
928
+ const raw = typeof value === "string" ? value.trim() : "";
929
+ const ref = (raw ? parseRoleRef(raw) : null) ?? (raw.startsWith("@") ? raw.slice(1) : raw);
930
+ if (!ref) throw new Error("ref is required");
931
+ return ref;
932
+ }
933
+ function normalizeTimeoutMs(timeoutMs, fallback) {
934
+ return Math.max(500, Math.min(12e4, timeoutMs ?? fallback));
935
+ }
936
+ function toAIFriendlyError(error, selector) {
937
+ const message = error instanceof Error ? error.message : String(error);
938
+ if (message.includes("strict mode violation")) {
939
+ const countMatch = message.match(/resolved to (\d+) elements/);
940
+ const count = countMatch ? countMatch[1] : "multiple";
941
+ return /* @__PURE__ */ new Error(`Selector "${selector}" matched ${count} elements. Run a new snapshot to get updated refs, or use a different ref.`);
942
+ }
943
+ if ((message.includes("Timeout") || message.includes("waiting for")) && (message.includes("to be visible") || message.includes("not visible"))) return /* @__PURE__ */ new Error(`Element "${selector}" not found or not visible. Run a new snapshot to see current page elements.`);
944
+ if (message.includes("intercepts pointer events") || message.includes("not visible") || message.includes("not receive pointer events")) return /* @__PURE__ */ new Error(`Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`);
945
+ return error instanceof Error ? error : new Error(message);
946
+ }
947
+ //#endregion
948
+ //#region src/browser/pw-tools-core.downloads.ts
949
+ function buildTempDownloadPath(fileName) {
950
+ const id = crypto.randomUUID();
951
+ const safeName = sanitizeUntrustedFileName(fileName, "download.bin");
952
+ return path.join(resolvePreferredOpenClawTmpDir(), "downloads", `${id}-${safeName}`);
953
+ }
954
+ function createPageDownloadWaiter(page, timeoutMs) {
955
+ let done = false;
956
+ let timer;
957
+ let handler;
958
+ const cleanup = () => {
959
+ if (timer) clearTimeout(timer);
960
+ timer = void 0;
961
+ if (handler) {
962
+ page.off("download", handler);
963
+ handler = void 0;
964
+ }
965
+ };
966
+ return {
967
+ promise: new Promise((resolve, reject) => {
968
+ handler = (download) => {
969
+ if (done) return;
970
+ done = true;
971
+ cleanup();
972
+ resolve(download);
973
+ };
974
+ page.on("download", handler);
975
+ timer = setTimeout(() => {
976
+ if (done) return;
977
+ done = true;
978
+ cleanup();
979
+ reject(/* @__PURE__ */ new Error("Timeout waiting for download"));
980
+ }, timeoutMs);
981
+ }),
982
+ cancel: () => {
983
+ if (done) return;
984
+ done = true;
985
+ cleanup();
986
+ }
987
+ };
988
+ }
989
+ async function saveDownloadPayload(download, outPath) {
990
+ const suggested = download.suggestedFilename?.() || "download.bin";
991
+ const requestedPath = outPath?.trim();
992
+ const resolvedOutPath = path.resolve(requestedPath || buildTempDownloadPath(suggested));
993
+ await fs.mkdir(path.dirname(resolvedOutPath), { recursive: true });
994
+ if (!requestedPath) await download.saveAs?.(resolvedOutPath);
995
+ else await writeViaSiblingTempPath({
996
+ rootDir: path.dirname(resolvedOutPath),
997
+ targetPath: resolvedOutPath,
998
+ writeTemp: async (tempPath) => {
999
+ await download.saveAs?.(tempPath);
1000
+ }
1001
+ });
1002
+ return {
1003
+ url: download.url?.() || "",
1004
+ suggestedFilename: suggested,
1005
+ path: resolvedOutPath
1006
+ };
1007
+ }
1008
+ async function awaitDownloadPayload(params) {
1009
+ try {
1010
+ const download = await params.waiter.promise;
1011
+ if (params.state.armIdDownload !== params.armId) throw new Error("Download was superseded by another waiter");
1012
+ return await saveDownloadPayload(download, params.outPath ?? "");
1013
+ } catch (err) {
1014
+ params.waiter.cancel();
1015
+ throw err;
1016
+ }
1017
+ }
1018
+ async function armFileUploadViaPlaywright(opts) {
1019
+ const page = await getPageForTargetId(opts);
1020
+ const state = ensurePageState(page);
1021
+ const timeout = Math.max(500, Math.min(12e4, opts.timeoutMs ?? 12e4));
1022
+ state.armIdUpload = bumpUploadArmId();
1023
+ const armId = state.armIdUpload;
1024
+ page.waitForEvent("filechooser", { timeout }).then(async (fileChooser) => {
1025
+ if (state.armIdUpload !== armId) return;
1026
+ if (!opts.paths?.length) {
1027
+ try {
1028
+ await page.keyboard.press("Escape");
1029
+ } catch {}
1030
+ return;
1031
+ }
1032
+ const uploadPathsResult = await resolveStrictExistingPathsWithinRoot({
1033
+ rootDir: DEFAULT_UPLOAD_DIR,
1034
+ requestedPaths: opts.paths,
1035
+ scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`
1036
+ });
1037
+ if (!uploadPathsResult.ok) {
1038
+ try {
1039
+ await page.keyboard.press("Escape");
1040
+ } catch {}
1041
+ return;
1042
+ }
1043
+ await fileChooser.setFiles(uploadPathsResult.paths);
1044
+ try {
1045
+ const input = typeof fileChooser.element === "function" ? await Promise.resolve(fileChooser.element()) : null;
1046
+ if (input) await input.evaluate((el) => {
1047
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1048
+ el.dispatchEvent(new Event("change", { bubbles: true }));
1049
+ });
1050
+ } catch {}
1051
+ }).catch(() => {});
1052
+ }
1053
+ async function armDialogViaPlaywright(opts) {
1054
+ const page = await getPageForTargetId(opts);
1055
+ const state = ensurePageState(page);
1056
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
1057
+ state.armIdDialog = bumpDialogArmId();
1058
+ const armId = state.armIdDialog;
1059
+ page.waitForEvent("dialog", { timeout }).then(async (dialog) => {
1060
+ if (state.armIdDialog !== armId) return;
1061
+ if (opts.accept) await dialog.accept(opts.promptText);
1062
+ else await dialog.dismiss();
1063
+ }).catch(() => {});
1064
+ }
1065
+ async function waitForDownloadViaPlaywright(opts) {
1066
+ const page = await getPageForTargetId(opts);
1067
+ const state = ensurePageState(page);
1068
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
1069
+ state.armIdDownload = bumpDownloadArmId();
1070
+ const armId = state.armIdDownload;
1071
+ return await awaitDownloadPayload({
1072
+ waiter: createPageDownloadWaiter(page, timeout),
1073
+ state,
1074
+ armId,
1075
+ outPath: opts.path
1076
+ });
1077
+ }
1078
+ async function downloadViaPlaywright(opts) {
1079
+ const page = await getPageForTargetId(opts);
1080
+ const state = ensurePageState(page);
1081
+ restoreRoleRefsForTarget({
1082
+ cdpUrl: opts.cdpUrl,
1083
+ targetId: opts.targetId,
1084
+ page
1085
+ });
1086
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
1087
+ const ref = requireRef(opts.ref);
1088
+ const outPath = String(opts.path ?? "").trim();
1089
+ if (!outPath) throw new Error("path is required");
1090
+ state.armIdDownload = bumpDownloadArmId();
1091
+ const armId = state.armIdDownload;
1092
+ const waiter = createPageDownloadWaiter(page, timeout);
1093
+ try {
1094
+ const locator = refLocator(page, ref);
1095
+ try {
1096
+ await locator.click({ timeout });
1097
+ } catch (err) {
1098
+ throw toAIFriendlyError(err, ref);
1099
+ }
1100
+ return await awaitDownloadPayload({
1101
+ waiter,
1102
+ state,
1103
+ armId,
1104
+ outPath
1105
+ });
1106
+ } catch (err) {
1107
+ waiter.cancel();
1108
+ throw err;
1109
+ }
1110
+ }
1111
+ //#endregion
1112
+ //#region src/browser/pw-tools-core.interactions.ts
1113
+ async function getRestoredPageForTarget(opts) {
1114
+ const page = await getPageForTargetId(opts);
1115
+ ensurePageState(page);
1116
+ restoreRoleRefsForTarget({
1117
+ cdpUrl: opts.cdpUrl,
1118
+ targetId: opts.targetId,
1119
+ page
1120
+ });
1121
+ return page;
1122
+ }
1123
+ function resolveInteractionTimeoutMs(timeoutMs) {
1124
+ return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
1125
+ }
1126
+ async function awaitEvalWithAbort(evalPromise, abortPromise) {
1127
+ if (!abortPromise) return await evalPromise;
1128
+ try {
1129
+ return await Promise.race([evalPromise, abortPromise]);
1130
+ } catch (err) {
1131
+ evalPromise.catch(() => {});
1132
+ throw err;
1133
+ }
1134
+ }
1135
+ async function highlightViaPlaywright(opts) {
1136
+ const page = await getRestoredPageForTarget(opts);
1137
+ const ref = requireRef(opts.ref);
1138
+ try {
1139
+ await refLocator(page, ref).highlight();
1140
+ } catch (err) {
1141
+ throw toAIFriendlyError(err, ref);
1142
+ }
1143
+ }
1144
+ async function clickViaPlaywright(opts) {
1145
+ const page = await getRestoredPageForTarget(opts);
1146
+ const ref = requireRef(opts.ref);
1147
+ const locator = refLocator(page, ref);
1148
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
1149
+ try {
1150
+ if (opts.doubleClick) await locator.dblclick({
1151
+ timeout,
1152
+ button: opts.button,
1153
+ modifiers: opts.modifiers
1154
+ });
1155
+ else await locator.click({
1156
+ timeout,
1157
+ button: opts.button,
1158
+ modifiers: opts.modifiers
1159
+ });
1160
+ } catch (err) {
1161
+ throw toAIFriendlyError(err, ref);
1162
+ }
1163
+ }
1164
+ async function hoverViaPlaywright(opts) {
1165
+ const ref = requireRef(opts.ref);
1166
+ const page = await getRestoredPageForTarget(opts);
1167
+ try {
1168
+ await refLocator(page, ref).hover({ timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1169
+ } catch (err) {
1170
+ throw toAIFriendlyError(err, ref);
1171
+ }
1172
+ }
1173
+ async function dragViaPlaywright(opts) {
1174
+ const startRef = requireRef(opts.startRef);
1175
+ const endRef = requireRef(opts.endRef);
1176
+ if (!startRef || !endRef) throw new Error("startRef and endRef are required");
1177
+ const page = await getRestoredPageForTarget(opts);
1178
+ try {
1179
+ await refLocator(page, startRef).dragTo(refLocator(page, endRef), { timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1180
+ } catch (err) {
1181
+ throw toAIFriendlyError(err, `${startRef} -> ${endRef}`);
1182
+ }
1183
+ }
1184
+ async function selectOptionViaPlaywright(opts) {
1185
+ const ref = requireRef(opts.ref);
1186
+ if (!opts.values?.length) throw new Error("values are required");
1187
+ const page = await getRestoredPageForTarget(opts);
1188
+ try {
1189
+ await refLocator(page, ref).selectOption(opts.values, { timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1190
+ } catch (err) {
1191
+ throw toAIFriendlyError(err, ref);
1192
+ }
1193
+ }
1194
+ async function pressKeyViaPlaywright(opts) {
1195
+ const key = String(opts.key ?? "").trim();
1196
+ if (!key) throw new Error("key is required");
1197
+ const page = await getPageForTargetId(opts);
1198
+ ensurePageState(page);
1199
+ await page.keyboard.press(key, { delay: Math.max(0, Math.floor(opts.delayMs ?? 0)) });
1200
+ }
1201
+ async function typeViaPlaywright(opts) {
1202
+ const text = String(opts.text ?? "");
1203
+ const page = await getRestoredPageForTarget(opts);
1204
+ const ref = requireRef(opts.ref);
1205
+ const locator = refLocator(page, ref);
1206
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
1207
+ try {
1208
+ if (opts.slowly) {
1209
+ await locator.click({ timeout });
1210
+ await locator.type(text, {
1211
+ timeout,
1212
+ delay: 75
1213
+ });
1214
+ } else await locator.fill(text, { timeout });
1215
+ if (opts.submit) await locator.press("Enter", { timeout });
1216
+ } catch (err) {
1217
+ throw toAIFriendlyError(err, ref);
1218
+ }
1219
+ }
1220
+ async function fillFormViaPlaywright(opts) {
1221
+ const page = await getRestoredPageForTarget(opts);
1222
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
1223
+ for (const field of opts.fields) {
1224
+ const ref = field.ref.trim();
1225
+ const type = (field.type || "text").trim() || "text";
1226
+ const rawValue = field.value;
1227
+ const value = typeof rawValue === "string" ? rawValue : typeof rawValue === "number" || typeof rawValue === "boolean" ? String(rawValue) : "";
1228
+ if (!ref) continue;
1229
+ const locator = refLocator(page, ref);
1230
+ if (type === "checkbox" || type === "radio") {
1231
+ const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
1232
+ try {
1233
+ await locator.setChecked(checked, { timeout });
1234
+ } catch (err) {
1235
+ throw toAIFriendlyError(err, ref);
1236
+ }
1237
+ continue;
1238
+ }
1239
+ try {
1240
+ await locator.fill(value, { timeout });
1241
+ } catch (err) {
1242
+ throw toAIFriendlyError(err, ref);
1243
+ }
1244
+ }
1245
+ }
1246
+ async function evaluateViaPlaywright(opts) {
1247
+ const fnText = String(opts.fn ?? "").trim();
1248
+ if (!fnText) throw new Error("function is required");
1249
+ const page = await getRestoredPageForTarget(opts);
1250
+ const outerTimeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
1251
+ let evaluateTimeout = Math.max(1e3, Math.min(12e4, outerTimeout - 500));
1252
+ evaluateTimeout = Math.min(evaluateTimeout, outerTimeout);
1253
+ const signal = opts.signal;
1254
+ let abortListener;
1255
+ let abortReject;
1256
+ let abortPromise;
1257
+ if (signal) {
1258
+ abortPromise = new Promise((_, reject) => {
1259
+ abortReject = reject;
1260
+ });
1261
+ abortPromise.catch(() => {});
1262
+ }
1263
+ if (signal) {
1264
+ const disconnect = () => {
1265
+ forceDisconnectPlaywrightForTarget({
1266
+ cdpUrl: opts.cdpUrl,
1267
+ targetId: opts.targetId,
1268
+ reason: "evaluate aborted"
1269
+ }).catch(() => {});
1270
+ };
1271
+ if (signal.aborted) {
1272
+ disconnect();
1273
+ throw signal.reason ?? /* @__PURE__ */ new Error("aborted");
1274
+ }
1275
+ abortListener = () => {
1276
+ disconnect();
1277
+ abortReject?.(signal.reason ?? /* @__PURE__ */ new Error("aborted"));
1278
+ };
1279
+ signal.addEventListener("abort", abortListener, { once: true });
1280
+ if (signal.aborted) {
1281
+ abortListener();
1282
+ throw signal.reason ?? /* @__PURE__ */ new Error("aborted");
1283
+ }
1284
+ }
1285
+ try {
1286
+ if (opts.ref) {
1287
+ const locator = refLocator(page, opts.ref);
1288
+ const elementEvaluator = new Function("el", "args", `
1289
+ "use strict";
1290
+ var fnBody = args.fnBody, timeoutMs = args.timeoutMs;
1291
+ try {
1292
+ var candidate = eval("(" + fnBody + ")");
1293
+ var result = typeof candidate === "function" ? candidate(el) : candidate;
1294
+ if (result && typeof result.then === "function") {
1295
+ return Promise.race([
1296
+ result,
1297
+ new Promise(function(_, reject) {
1298
+ setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);
1299
+ })
1300
+ ]);
1301
+ }
1302
+ return result;
1303
+ } catch (err) {
1304
+ throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
1305
+ }
1306
+ `);
1307
+ return await awaitEvalWithAbort(locator.evaluate(elementEvaluator, {
1308
+ fnBody: fnText,
1309
+ timeoutMs: evaluateTimeout
1310
+ }), abortPromise);
1311
+ }
1312
+ const browserEvaluator = new Function("args", `
1313
+ "use strict";
1314
+ var fnBody = args.fnBody, timeoutMs = args.timeoutMs;
1315
+ try {
1316
+ var candidate = eval("(" + fnBody + ")");
1317
+ var result = typeof candidate === "function" ? candidate() : candidate;
1318
+ if (result && typeof result.then === "function") {
1319
+ return Promise.race([
1320
+ result,
1321
+ new Promise(function(_, reject) {
1322
+ setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);
1323
+ })
1324
+ ]);
1325
+ }
1326
+ return result;
1327
+ } catch (err) {
1328
+ throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
1329
+ }
1330
+ `);
1331
+ return await awaitEvalWithAbort(page.evaluate(browserEvaluator, {
1332
+ fnBody: fnText,
1333
+ timeoutMs: evaluateTimeout
1334
+ }), abortPromise);
1335
+ } finally {
1336
+ if (signal && abortListener) signal.removeEventListener("abort", abortListener);
1337
+ }
1338
+ }
1339
+ async function scrollIntoViewViaPlaywright(opts) {
1340
+ const page = await getRestoredPageForTarget(opts);
1341
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
1342
+ const ref = requireRef(opts.ref);
1343
+ const locator = refLocator(page, ref);
1344
+ try {
1345
+ await locator.scrollIntoViewIfNeeded({ timeout });
1346
+ } catch (err) {
1347
+ throw toAIFriendlyError(err, ref);
1348
+ }
1349
+ }
1350
+ async function waitForViaPlaywright(opts) {
1351
+ const page = await getPageForTargetId(opts);
1352
+ ensurePageState(page);
1353
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
1354
+ if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) await page.waitForTimeout(Math.max(0, opts.timeMs));
1355
+ if (opts.text) await page.getByText(opts.text).first().waitFor({
1356
+ state: "visible",
1357
+ timeout
1358
+ });
1359
+ if (opts.textGone) await page.getByText(opts.textGone).first().waitFor({
1360
+ state: "hidden",
1361
+ timeout
1362
+ });
1363
+ if (opts.selector) {
1364
+ const selector = String(opts.selector).trim();
1365
+ if (selector) await page.locator(selector).first().waitFor({
1366
+ state: "visible",
1367
+ timeout
1368
+ });
1369
+ }
1370
+ if (opts.url) {
1371
+ const url = String(opts.url).trim();
1372
+ if (url) await page.waitForURL(url, { timeout });
1373
+ }
1374
+ if (opts.loadState) await page.waitForLoadState(opts.loadState, { timeout });
1375
+ if (opts.fn) {
1376
+ const fn = String(opts.fn).trim();
1377
+ if (fn) await page.waitForFunction(fn, { timeout });
1378
+ }
1379
+ }
1380
+ async function takeScreenshotViaPlaywright(opts) {
1381
+ const page = await getPageForTargetId(opts);
1382
+ ensurePageState(page);
1383
+ restoreRoleRefsForTarget({
1384
+ cdpUrl: opts.cdpUrl,
1385
+ targetId: opts.targetId,
1386
+ page
1387
+ });
1388
+ const type = opts.type ?? "png";
1389
+ if (opts.ref) {
1390
+ if (opts.fullPage) throw new Error("fullPage is not supported for element screenshots");
1391
+ return { buffer: await refLocator(page, opts.ref).screenshot({ type }) };
1392
+ }
1393
+ if (opts.element) {
1394
+ if (opts.fullPage) throw new Error("fullPage is not supported for element screenshots");
1395
+ return { buffer: await page.locator(opts.element).first().screenshot({ type }) };
1396
+ }
1397
+ return { buffer: await page.screenshot({
1398
+ type,
1399
+ fullPage: Boolean(opts.fullPage)
1400
+ }) };
1401
+ }
1402
+ async function screenshotWithLabelsViaPlaywright(opts) {
1403
+ const page = await getPageForTargetId(opts);
1404
+ ensurePageState(page);
1405
+ restoreRoleRefsForTarget({
1406
+ cdpUrl: opts.cdpUrl,
1407
+ targetId: opts.targetId,
1408
+ page
1409
+ });
1410
+ const type = opts.type ?? "png";
1411
+ const maxLabels = typeof opts.maxLabels === "number" && Number.isFinite(opts.maxLabels) ? Math.max(1, Math.floor(opts.maxLabels)) : 150;
1412
+ const viewport = await page.evaluate(() => ({
1413
+ scrollX: window.scrollX || 0,
1414
+ scrollY: window.scrollY || 0,
1415
+ width: window.innerWidth || 0,
1416
+ height: window.innerHeight || 0
1417
+ }));
1418
+ const refs = Object.keys(opts.refs ?? {});
1419
+ const boxes = [];
1420
+ let skipped = 0;
1421
+ for (const ref of refs) {
1422
+ if (boxes.length >= maxLabels) {
1423
+ skipped += 1;
1424
+ continue;
1425
+ }
1426
+ try {
1427
+ const box = await refLocator(page, ref).boundingBox();
1428
+ if (!box) {
1429
+ skipped += 1;
1430
+ continue;
1431
+ }
1432
+ const x0 = box.x;
1433
+ const y0 = box.y;
1434
+ const x1 = box.x + box.width;
1435
+ const y1 = box.y + box.height;
1436
+ const vx0 = viewport.scrollX;
1437
+ const vy0 = viewport.scrollY;
1438
+ const vx1 = viewport.scrollX + viewport.width;
1439
+ const vy1 = viewport.scrollY + viewport.height;
1440
+ if (x1 < vx0 || x0 > vx1 || y1 < vy0 || y0 > vy1) {
1441
+ skipped += 1;
1442
+ continue;
1443
+ }
1444
+ boxes.push({
1445
+ ref,
1446
+ x: x0 - viewport.scrollX,
1447
+ y: y0 - viewport.scrollY,
1448
+ w: Math.max(1, box.width),
1449
+ h: Math.max(1, box.height)
1450
+ });
1451
+ } catch {
1452
+ skipped += 1;
1453
+ }
1454
+ }
1455
+ try {
1456
+ if (boxes.length > 0) await page.evaluate((labels) => {
1457
+ document.querySelectorAll("[data-openclaw-labels]").forEach((el) => el.remove());
1458
+ const root = document.createElement("div");
1459
+ root.setAttribute("data-openclaw-labels", "1");
1460
+ root.style.position = "fixed";
1461
+ root.style.left = "0";
1462
+ root.style.top = "0";
1463
+ root.style.zIndex = "2147483647";
1464
+ root.style.pointerEvents = "none";
1465
+ root.style.fontFamily = "\"SF Mono\",\"SFMono-Regular\",Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace";
1466
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
1467
+ for (const label of labels) {
1468
+ const box = document.createElement("div");
1469
+ box.setAttribute("data-openclaw-labels", "1");
1470
+ box.style.position = "absolute";
1471
+ box.style.left = `${label.x}px`;
1472
+ box.style.top = `${label.y}px`;
1473
+ box.style.width = `${label.w}px`;
1474
+ box.style.height = `${label.h}px`;
1475
+ box.style.border = "2px solid #ffb020";
1476
+ box.style.boxSizing = "border-box";
1477
+ const tag = document.createElement("div");
1478
+ tag.setAttribute("data-openclaw-labels", "1");
1479
+ tag.textContent = label.ref;
1480
+ tag.style.position = "absolute";
1481
+ tag.style.left = `${label.x}px`;
1482
+ tag.style.top = `${clamp(label.y - 18, 0, 2e4)}px`;
1483
+ tag.style.background = "#ffb020";
1484
+ tag.style.color = "#1a1a1a";
1485
+ tag.style.fontSize = "12px";
1486
+ tag.style.lineHeight = "14px";
1487
+ tag.style.padding = "1px 4px";
1488
+ tag.style.borderRadius = "3px";
1489
+ tag.style.boxShadow = "0 1px 2px rgba(0,0,0,0.35)";
1490
+ tag.style.whiteSpace = "nowrap";
1491
+ root.appendChild(box);
1492
+ root.appendChild(tag);
1493
+ }
1494
+ document.documentElement.appendChild(root);
1495
+ }, boxes);
1496
+ return {
1497
+ buffer: await page.screenshot({ type }),
1498
+ labels: boxes.length,
1499
+ skipped
1500
+ };
1501
+ } finally {
1502
+ await page.evaluate(() => {
1503
+ document.querySelectorAll("[data-openclaw-labels]").forEach((el) => el.remove());
1504
+ }).catch(() => {});
1505
+ }
1506
+ }
1507
+ async function setInputFilesViaPlaywright(opts) {
1508
+ const page = await getPageForTargetId(opts);
1509
+ ensurePageState(page);
1510
+ restoreRoleRefsForTarget({
1511
+ cdpUrl: opts.cdpUrl,
1512
+ targetId: opts.targetId,
1513
+ page
1514
+ });
1515
+ if (!opts.paths.length) throw new Error("paths are required");
1516
+ const inputRef = typeof opts.inputRef === "string" ? opts.inputRef.trim() : "";
1517
+ const element = typeof opts.element === "string" ? opts.element.trim() : "";
1518
+ if (inputRef && element) throw new Error("inputRef and element are mutually exclusive");
1519
+ if (!inputRef && !element) throw new Error("inputRef or element is required");
1520
+ const locator = inputRef ? refLocator(page, inputRef) : page.locator(element).first();
1521
+ const uploadPathsResult = await resolveStrictExistingPathsWithinRoot({
1522
+ rootDir: DEFAULT_UPLOAD_DIR,
1523
+ requestedPaths: opts.paths,
1524
+ scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`
1525
+ });
1526
+ if (!uploadPathsResult.ok) throw new Error(uploadPathsResult.error);
1527
+ const resolvedPaths = uploadPathsResult.paths;
1528
+ try {
1529
+ await locator.setInputFiles(resolvedPaths);
1530
+ } catch (err) {
1531
+ throw toAIFriendlyError(err, inputRef || element);
1532
+ }
1533
+ try {
1534
+ const handle = await locator.elementHandle();
1535
+ if (handle) await handle.evaluate((el) => {
1536
+ el.dispatchEvent(new Event("input", { bubbles: true }));
1537
+ el.dispatchEvent(new Event("change", { bubbles: true }));
1538
+ });
1539
+ } catch {}
1540
+ }
1541
+ //#endregion
1542
+ //#region src/browser/pw-tools-core.responses.ts
1543
+ function matchUrlPattern(pattern, url) {
1544
+ const p = pattern.trim();
1545
+ if (!p) return false;
1546
+ if (p === url) return true;
1547
+ if (p.includes("*")) {
1548
+ const escaped = p.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
1549
+ return new RegExp(`^${escaped.replace(/\*\*/g, ".*").replace(/\*/g, ".*")}$`).test(url);
1550
+ }
1551
+ return url.includes(p);
1552
+ }
1553
+ async function responseBodyViaPlaywright(opts) {
1554
+ const pattern = String(opts.url ?? "").trim();
1555
+ if (!pattern) throw new Error("url is required");
1556
+ const maxChars = typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars) ? Math.max(1, Math.min(5e6, Math.floor(opts.maxChars))) : 2e5;
1557
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
1558
+ const page = await getPageForTargetId(opts);
1559
+ ensurePageState(page);
1560
+ const resp = await new Promise((resolve, reject) => {
1561
+ let done = false;
1562
+ let timer;
1563
+ let handler;
1564
+ const cleanup = () => {
1565
+ if (timer) clearTimeout(timer);
1566
+ timer = void 0;
1567
+ if (handler) page.off("response", handler);
1568
+ };
1569
+ handler = (resp) => {
1570
+ if (done) return;
1571
+ if (!matchUrlPattern(pattern, resp.url?.() || "")) return;
1572
+ done = true;
1573
+ cleanup();
1574
+ resolve(resp);
1575
+ };
1576
+ page.on("response", handler);
1577
+ timer = setTimeout(() => {
1578
+ if (done) return;
1579
+ done = true;
1580
+ cleanup();
1581
+ reject(/* @__PURE__ */ new Error(`Response not found for url pattern "${pattern}". Run '${formatCliCommand("openclaw browser requests")}' to inspect recent network activity.`));
1582
+ }, timeout);
1583
+ });
1584
+ const url = resp.url?.() || "";
1585
+ const status = resp.status?.();
1586
+ const headers = resp.headers?.();
1587
+ let bodyText = "";
1588
+ try {
1589
+ if (typeof resp.text === "function") bodyText = await resp.text();
1590
+ else if (typeof resp.body === "function") {
1591
+ const buf = await resp.body();
1592
+ bodyText = new TextDecoder("utf-8").decode(buf);
1593
+ }
1594
+ } catch (err) {
1595
+ throw new Error(`Failed to read response body for "${url}": ${String(err)}`, { cause: err });
1596
+ }
1597
+ return {
1598
+ url,
1599
+ status,
1600
+ headers,
1601
+ body: bodyText.length > maxChars ? bodyText.slice(0, maxChars) : bodyText,
1602
+ truncated: bodyText.length > maxChars ? true : void 0
1603
+ };
1604
+ }
1605
+ //#endregion
1606
+ //#region src/browser/pw-tools-core.snapshot.ts
1607
+ async function snapshotAriaViaPlaywright(opts) {
1608
+ const limit = Math.max(1, Math.min(2e3, Math.floor(opts.limit ?? 500)));
1609
+ const page = await getPageForTargetId({
1610
+ cdpUrl: opts.cdpUrl,
1611
+ targetId: opts.targetId
1612
+ });
1613
+ ensurePageState(page);
1614
+ const res = await withPageScopedCdpClient({
1615
+ cdpUrl: opts.cdpUrl,
1616
+ page,
1617
+ targetId: opts.targetId,
1618
+ fn: async (send) => {
1619
+ await send("Accessibility.enable").catch(() => {});
1620
+ return await send("Accessibility.getFullAXTree");
1621
+ }
1622
+ });
1623
+ return { nodes: formatAriaSnapshot(Array.isArray(res?.nodes) ? res.nodes : [], limit) };
1624
+ }
1625
+ async function snapshotAiViaPlaywright(opts) {
1626
+ const page = await getPageForTargetId({
1627
+ cdpUrl: opts.cdpUrl,
1628
+ targetId: opts.targetId
1629
+ });
1630
+ ensurePageState(page);
1631
+ const maybe = page;
1632
+ if (!maybe._snapshotForAI) throw new Error("Playwright _snapshotForAI is not available. Upgrade playwright-core.");
1633
+ const result = await maybe._snapshotForAI({
1634
+ timeout: Math.max(500, Math.min(6e4, Math.floor(opts.timeoutMs ?? 5e3))),
1635
+ track: "response"
1636
+ });
1637
+ let snapshot = String(result?.full ?? "");
1638
+ const maxChars = opts.maxChars;
1639
+ const limit = typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : void 0;
1640
+ let truncated = false;
1641
+ if (limit && snapshot.length > limit) {
1642
+ snapshot = `${snapshot.slice(0, limit)}\n\n[...TRUNCATED - page too large]`;
1643
+ truncated = true;
1644
+ }
1645
+ const built = buildRoleSnapshotFromAiSnapshot(snapshot);
1646
+ storeRoleRefsForTarget({
1647
+ page,
1648
+ cdpUrl: opts.cdpUrl,
1649
+ targetId: opts.targetId,
1650
+ refs: built.refs,
1651
+ mode: "aria"
1652
+ });
1653
+ return truncated ? {
1654
+ snapshot,
1655
+ truncated,
1656
+ refs: built.refs
1657
+ } : {
1658
+ snapshot,
1659
+ refs: built.refs
1660
+ };
1661
+ }
1662
+ async function snapshotRoleViaPlaywright(opts) {
1663
+ const page = await getPageForTargetId({
1664
+ cdpUrl: opts.cdpUrl,
1665
+ targetId: opts.targetId
1666
+ });
1667
+ ensurePageState(page);
1668
+ if (opts.refsMode === "aria") {
1669
+ if (opts.selector?.trim() || opts.frameSelector?.trim()) throw new Error("refs=aria does not support selector/frame snapshots yet.");
1670
+ const maybe = page;
1671
+ if (!maybe._snapshotForAI) throw new Error("refs=aria requires Playwright _snapshotForAI support.");
1672
+ const result = await maybe._snapshotForAI({
1673
+ timeout: 5e3,
1674
+ track: "response"
1675
+ });
1676
+ const built = buildRoleSnapshotFromAiSnapshot(String(result?.full ?? ""), opts.options);
1677
+ storeRoleRefsForTarget({
1678
+ page,
1679
+ cdpUrl: opts.cdpUrl,
1680
+ targetId: opts.targetId,
1681
+ refs: built.refs,
1682
+ mode: "aria"
1683
+ });
1684
+ return {
1685
+ snapshot: built.snapshot,
1686
+ refs: built.refs,
1687
+ stats: getRoleSnapshotStats(built.snapshot, built.refs)
1688
+ };
1689
+ }
1690
+ const frameSelector = opts.frameSelector?.trim() || "";
1691
+ const selector = opts.selector?.trim() || "";
1692
+ const ariaSnapshot = await (frameSelector ? selector ? page.frameLocator(frameSelector).locator(selector) : page.frameLocator(frameSelector).locator(":root") : selector ? page.locator(selector) : page.locator(":root")).ariaSnapshot();
1693
+ const built = buildRoleSnapshotFromAriaSnapshot(String(ariaSnapshot ?? ""), opts.options);
1694
+ storeRoleRefsForTarget({
1695
+ page,
1696
+ cdpUrl: opts.cdpUrl,
1697
+ targetId: opts.targetId,
1698
+ refs: built.refs,
1699
+ frameSelector: frameSelector || void 0,
1700
+ mode: "role"
1701
+ });
1702
+ return {
1703
+ snapshot: built.snapshot,
1704
+ refs: built.refs,
1705
+ stats: getRoleSnapshotStats(built.snapshot, built.refs)
1706
+ };
1707
+ }
1708
+ async function navigateViaPlaywright(opts) {
1709
+ const isRetryableNavigateError = (err) => {
1710
+ const msg = typeof err === "string" ? err.toLowerCase() : err instanceof Error ? err.message.toLowerCase() : "";
1711
+ return msg.includes("frame has been detached") || msg.includes("target page, context or browser has been closed");
1712
+ };
1713
+ const url = String(opts.url ?? "").trim();
1714
+ if (!url) throw new Error("url is required");
1715
+ await assertBrowserNavigationAllowed({
1716
+ url,
1717
+ ...withBrowserNavigationPolicy(opts.ssrfPolicy)
1718
+ });
1719
+ const timeout = Math.max(1e3, Math.min(12e4, opts.timeoutMs ?? 2e4));
1720
+ let page = await getPageForTargetId(opts);
1721
+ ensurePageState(page);
1722
+ const navigate = async () => await page.goto(url, { timeout });
1723
+ let response;
1724
+ try {
1725
+ response = await navigate();
1726
+ } catch (err) {
1727
+ if (!isRetryableNavigateError(err)) throw err;
1728
+ await forceDisconnectPlaywrightForTarget({
1729
+ cdpUrl: opts.cdpUrl,
1730
+ targetId: opts.targetId,
1731
+ reason: "retry navigate after detached frame"
1732
+ }).catch(() => {});
1733
+ page = await getPageForTargetId(opts);
1734
+ ensurePageState(page);
1735
+ response = await navigate();
1736
+ }
1737
+ await assertBrowserNavigationRedirectChainAllowed({
1738
+ request: response?.request(),
1739
+ ...withBrowserNavigationPolicy(opts.ssrfPolicy)
1740
+ });
1741
+ const finalUrl = page.url();
1742
+ await assertBrowserNavigationResultAllowed({
1743
+ url: finalUrl,
1744
+ ...withBrowserNavigationPolicy(opts.ssrfPolicy)
1745
+ });
1746
+ return { url: finalUrl };
1747
+ }
1748
+ async function resizeViewportViaPlaywright(opts) {
1749
+ const page = await getPageForTargetId(opts);
1750
+ ensurePageState(page);
1751
+ await page.setViewportSize({
1752
+ width: Math.max(1, Math.floor(opts.width)),
1753
+ height: Math.max(1, Math.floor(opts.height))
1754
+ });
1755
+ }
1756
+ async function closePageViaPlaywright(opts) {
1757
+ const page = await getPageForTargetId(opts);
1758
+ ensurePageState(page);
1759
+ await page.close();
1760
+ }
1761
+ async function pdfViaPlaywright(opts) {
1762
+ const page = await getPageForTargetId(opts);
1763
+ ensurePageState(page);
1764
+ return { buffer: await page.pdf({ printBackground: true }) };
1765
+ }
1766
+ //#endregion
1767
+ //#region src/browser/pw-tools-core.state.ts
1768
+ async function setOfflineViaPlaywright(opts) {
1769
+ const page = await getPageForTargetId(opts);
1770
+ ensurePageState(page);
1771
+ await page.context().setOffline(Boolean(opts.offline));
1772
+ }
1773
+ async function setExtraHTTPHeadersViaPlaywright(opts) {
1774
+ const page = await getPageForTargetId(opts);
1775
+ ensurePageState(page);
1776
+ await page.context().setExtraHTTPHeaders(opts.headers);
1777
+ }
1778
+ async function setHttpCredentialsViaPlaywright(opts) {
1779
+ const page = await getPageForTargetId(opts);
1780
+ ensurePageState(page);
1781
+ if (opts.clear) {
1782
+ await page.context().setHTTPCredentials(null);
1783
+ return;
1784
+ }
1785
+ const username = String(opts.username ?? "");
1786
+ const password = String(opts.password ?? "");
1787
+ if (!username) throw new Error("username is required (or set clear=true)");
1788
+ await page.context().setHTTPCredentials({
1789
+ username,
1790
+ password
1791
+ });
1792
+ }
1793
+ async function setGeolocationViaPlaywright(opts) {
1794
+ const page = await getPageForTargetId(opts);
1795
+ ensurePageState(page);
1796
+ const context = page.context();
1797
+ if (opts.clear) {
1798
+ await context.setGeolocation(null);
1799
+ await context.clearPermissions().catch(() => {});
1800
+ return;
1801
+ }
1802
+ if (typeof opts.latitude !== "number" || typeof opts.longitude !== "number") throw new Error("latitude and longitude are required (or set clear=true)");
1803
+ await context.setGeolocation({
1804
+ latitude: opts.latitude,
1805
+ longitude: opts.longitude,
1806
+ accuracy: typeof opts.accuracy === "number" ? opts.accuracy : void 0
1807
+ });
1808
+ const origin = opts.origin?.trim() || (() => {
1809
+ try {
1810
+ return new URL(page.url()).origin;
1811
+ } catch {
1812
+ return "";
1813
+ }
1814
+ })();
1815
+ if (origin) await context.grantPermissions(["geolocation"], { origin }).catch(() => {});
1816
+ }
1817
+ async function emulateMediaViaPlaywright(opts) {
1818
+ const page = await getPageForTargetId(opts);
1819
+ ensurePageState(page);
1820
+ await page.emulateMedia({ colorScheme: opts.colorScheme });
1821
+ }
1822
+ async function setLocaleViaPlaywright(opts) {
1823
+ const page = await getPageForTargetId(opts);
1824
+ ensurePageState(page);
1825
+ const locale = String(opts.locale ?? "").trim();
1826
+ if (!locale) throw new Error("locale is required");
1827
+ await withPageScopedCdpClient({
1828
+ cdpUrl: opts.cdpUrl,
1829
+ page,
1830
+ targetId: opts.targetId,
1831
+ fn: async (send) => {
1832
+ try {
1833
+ await send("Emulation.setLocaleOverride", { locale });
1834
+ } catch (err) {
1835
+ if (String(err).includes("Another locale override is already in effect")) return;
1836
+ throw err;
1837
+ }
1838
+ }
1839
+ });
1840
+ }
1841
+ async function setTimezoneViaPlaywright(opts) {
1842
+ const page = await getPageForTargetId(opts);
1843
+ ensurePageState(page);
1844
+ const timezoneId = String(opts.timezoneId ?? "").trim();
1845
+ if (!timezoneId) throw new Error("timezoneId is required");
1846
+ await withPageScopedCdpClient({
1847
+ cdpUrl: opts.cdpUrl,
1848
+ page,
1849
+ targetId: opts.targetId,
1850
+ fn: async (send) => {
1851
+ try {
1852
+ await send("Emulation.setTimezoneOverride", { timezoneId });
1853
+ } catch (err) {
1854
+ const msg = String(err);
1855
+ if (msg.includes("Timezone override is already in effect")) return;
1856
+ if (msg.includes("Invalid timezone")) throw new Error(`Invalid timezone ID: ${timezoneId}`, { cause: err });
1857
+ throw err;
1858
+ }
1859
+ }
1860
+ });
1861
+ }
1862
+ async function setDeviceViaPlaywright(opts) {
1863
+ const page = await getPageForTargetId(opts);
1864
+ ensurePageState(page);
1865
+ const name = String(opts.name ?? "").trim();
1866
+ if (!name) throw new Error("device name is required");
1867
+ const descriptor = devices[name];
1868
+ if (!descriptor) throw new Error(`Unknown device "${name}".`);
1869
+ if (descriptor.viewport) await page.setViewportSize({
1870
+ width: descriptor.viewport.width,
1871
+ height: descriptor.viewport.height
1872
+ });
1873
+ await withPageScopedCdpClient({
1874
+ cdpUrl: opts.cdpUrl,
1875
+ page,
1876
+ targetId: opts.targetId,
1877
+ fn: async (send) => {
1878
+ if (descriptor.userAgent || descriptor.locale) await send("Emulation.setUserAgentOverride", {
1879
+ userAgent: descriptor.userAgent ?? "",
1880
+ acceptLanguage: descriptor.locale ?? void 0
1881
+ });
1882
+ if (descriptor.viewport) await send("Emulation.setDeviceMetricsOverride", {
1883
+ mobile: Boolean(descriptor.isMobile),
1884
+ width: descriptor.viewport.width,
1885
+ height: descriptor.viewport.height,
1886
+ deviceScaleFactor: descriptor.deviceScaleFactor ?? 1,
1887
+ screenWidth: descriptor.viewport.width,
1888
+ screenHeight: descriptor.viewport.height
1889
+ });
1890
+ if (descriptor.hasTouch) await send("Emulation.setTouchEmulationEnabled", { enabled: true });
1891
+ }
1892
+ });
1893
+ }
1894
+ //#endregion
1895
+ //#region src/browser/pw-tools-core.storage.ts
1896
+ async function cookiesGetViaPlaywright(opts) {
1897
+ const page = await getPageForTargetId(opts);
1898
+ ensurePageState(page);
1899
+ return { cookies: await page.context().cookies() };
1900
+ }
1901
+ async function cookiesSetViaPlaywright(opts) {
1902
+ const page = await getPageForTargetId(opts);
1903
+ ensurePageState(page);
1904
+ const cookie = opts.cookie;
1905
+ if (!cookie.name || cookie.value === void 0) throw new Error("cookie name and value are required");
1906
+ const hasUrl = typeof cookie.url === "string" && cookie.url.trim();
1907
+ const hasDomainPath = typeof cookie.domain === "string" && cookie.domain.trim() && typeof cookie.path === "string" && cookie.path.trim();
1908
+ if (!hasUrl && !hasDomainPath) throw new Error("cookie requires url, or domain+path");
1909
+ await page.context().addCookies([cookie]);
1910
+ }
1911
+ async function cookiesClearViaPlaywright(opts) {
1912
+ const page = await getPageForTargetId(opts);
1913
+ ensurePageState(page);
1914
+ await page.context().clearCookies();
1915
+ }
1916
+ async function storageGetViaPlaywright(opts) {
1917
+ const page = await getPageForTargetId(opts);
1918
+ ensurePageState(page);
1919
+ const kind = opts.kind;
1920
+ const key = typeof opts.key === "string" ? opts.key : void 0;
1921
+ return { values: await page.evaluate(({ kind: kind2, key: key2 }) => {
1922
+ const store = kind2 === "session" ? window.sessionStorage : window.localStorage;
1923
+ if (key2) {
1924
+ const value = store.getItem(key2);
1925
+ return value === null ? {} : { [key2]: value };
1926
+ }
1927
+ const out = {};
1928
+ for (let i = 0; i < store.length; i += 1) {
1929
+ const k = store.key(i);
1930
+ if (!k) continue;
1931
+ const v = store.getItem(k);
1932
+ if (v !== null) out[k] = v;
1933
+ }
1934
+ return out;
1935
+ }, {
1936
+ kind,
1937
+ key
1938
+ }) ?? {} };
1939
+ }
1940
+ async function storageSetViaPlaywright(opts) {
1941
+ const page = await getPageForTargetId(opts);
1942
+ ensurePageState(page);
1943
+ const key = String(opts.key ?? "");
1944
+ if (!key) throw new Error("key is required");
1945
+ await page.evaluate(({ kind, key: k, value }) => {
1946
+ (kind === "session" ? window.sessionStorage : window.localStorage).setItem(k, value);
1947
+ }, {
1948
+ kind: opts.kind,
1949
+ key,
1950
+ value: String(opts.value ?? "")
1951
+ });
1952
+ }
1953
+ async function storageClearViaPlaywright(opts) {
1954
+ const page = await getPageForTargetId(opts);
1955
+ ensurePageState(page);
1956
+ await page.evaluate(({ kind }) => {
1957
+ (kind === "session" ? window.sessionStorage : window.localStorage).clear();
1958
+ }, { kind: opts.kind });
1959
+ }
1960
+ //#endregion
1961
+ //#region src/browser/pw-tools-core.trace.ts
1962
+ async function traceStartViaPlaywright(opts) {
1963
+ const context = (await getPageForTargetId(opts)).context();
1964
+ const ctxState = ensureContextState(context);
1965
+ if (ctxState.traceActive) throw new Error("Trace already running. Stop the current trace before starting a new one.");
1966
+ await context.tracing.start({
1967
+ screenshots: opts.screenshots ?? true,
1968
+ snapshots: opts.snapshots ?? true,
1969
+ sources: opts.sources ?? false
1970
+ });
1971
+ ctxState.traceActive = true;
1972
+ }
1973
+ async function traceStopViaPlaywright(opts) {
1974
+ const context = (await getPageForTargetId(opts)).context();
1975
+ const ctxState = ensureContextState(context);
1976
+ if (!ctxState.traceActive) throw new Error("No active trace. Start a trace before stopping it.");
1977
+ await writeViaSiblingTempPath({
1978
+ rootDir: DEFAULT_TRACE_DIR,
1979
+ targetPath: opts.path,
1980
+ writeTemp: async (tempPath) => {
1981
+ await context.tracing.stop({ path: tempPath });
1982
+ }
1983
+ });
1984
+ ctxState.traceActive = false;
1985
+ }
1986
+ //#endregion
1987
+ //#region src/browser/pw-ai.ts
1988
+ markPwAiLoaded();
1989
+ //#endregion
1990
+ export { armDialogViaPlaywright, armFileUploadViaPlaywright, clickViaPlaywright, closePageByTargetIdViaPlaywright, closePageViaPlaywright, closePlaywrightBrowserConnection, cookiesClearViaPlaywright, cookiesGetViaPlaywright, cookiesSetViaPlaywright, createPageViaPlaywright, downloadViaPlaywright, dragViaPlaywright, emulateMediaViaPlaywright, ensurePageState, evaluateViaPlaywright, fillFormViaPlaywright, focusPageByTargetIdViaPlaywright, forceDisconnectPlaywrightForTarget, getConsoleMessagesViaPlaywright, getNetworkRequestsViaPlaywright, getPageErrorsViaPlaywright, getPageForTargetId, highlightViaPlaywright, hoverViaPlaywright, listPagesViaPlaywright, navigateViaPlaywright, pdfViaPlaywright, pressKeyViaPlaywright, refLocator, resizeViewportViaPlaywright, responseBodyViaPlaywright, screenshotWithLabelsViaPlaywright, scrollIntoViewViaPlaywright, selectOptionViaPlaywright, setDeviceViaPlaywright, setExtraHTTPHeadersViaPlaywright, setGeolocationViaPlaywright, setHttpCredentialsViaPlaywright, setInputFilesViaPlaywright, setLocaleViaPlaywright, setOfflineViaPlaywright, setTimezoneViaPlaywright, snapshotAiViaPlaywright, snapshotAriaViaPlaywright, snapshotRoleViaPlaywright, storageClearViaPlaywright, storageGetViaPlaywright, storageSetViaPlaywright, takeScreenshotViaPlaywright, traceStartViaPlaywright, traceStopViaPlaywright, typeViaPlaywright, waitForDownloadViaPlaywright, waitForViaPlaywright };