@volter-ai-dev/supercode-browser-playwright 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1016 @@
1
+ // src/executor.ts
2
+ import { errors } from "playwright-core";
3
+
4
+ // src/protocol.ts
5
+ var SUPERCODE_BROWSER_PROVIDER_PROTOCOL = "supercode/browser-provider-v1";
6
+ var SUPERCODE_BROWSER_OPERATION_PROTOCOL = "supercode/browser-operation-v1";
7
+ var BROWSER_OPERATION_NAMES = Object.freeze([
8
+ "browser.status",
9
+ "browser.snapshot",
10
+ "browser.query",
11
+ "browser.wait",
12
+ "browser.click",
13
+ "browser.fill",
14
+ "browser.press",
15
+ "browser.hover",
16
+ "browser.focus",
17
+ "browser.check",
18
+ "browser.uncheck",
19
+ "browser.select",
20
+ "browser.scroll",
21
+ "browser.back",
22
+ "browser.forward",
23
+ "browser.reload",
24
+ "browser.box",
25
+ "browser.mouse",
26
+ "browser.drag",
27
+ "browser.wheel",
28
+ "browser.script"
29
+ ]);
30
+ var OPERATION_NAMES = new Set(BROWSER_OPERATION_NAMES);
31
+ var BrowserActionRefusal = class extends Error {
32
+ constructor(code, message) {
33
+ super(message);
34
+ this.code = code;
35
+ this.name = "BrowserActionRefusal";
36
+ }
37
+ };
38
+ function record(value) {
39
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
40
+ }
41
+ function exactKeys(candidate, allowed) {
42
+ return Object.keys(candidate).every((key) => allowed.includes(key));
43
+ }
44
+ function parsePage(value) {
45
+ return value === void 0 ? void 0 : typeof value === "string" && value.length > 0 && value.length <= 512 ? value : null;
46
+ }
47
+ function parseBrowserLocator(value) {
48
+ const candidate = record(value);
49
+ if (!candidate || typeof candidate.by !== "string") return null;
50
+ if (candidate.by === "label" || candidate.by === "placeholder" || candidate.by === "altText" || candidate.by === "title") {
51
+ const { by, text, exact } = candidate;
52
+ return typeof text === "string" && (exact === void 0 || typeof exact === "boolean") && exactKeys(candidate, ["by", "text", "exact"]) ? { by, text, ...exact !== void 0 ? { exact } : {} } : null;
53
+ }
54
+ if (candidate.by === "css" || candidate.by === "ref" || candidate.by === "testId") {
55
+ return exactKeys(candidate, ["by", "value"]) && typeof candidate.value === "string" && candidate.value.length > 0 && candidate.value.length <= 2e3 ? { by: candidate.by, value: candidate.value } : null;
56
+ }
57
+ if (candidate.by === "role") {
58
+ if (!exactKeys(candidate, ["by", "role", "name", "exact"]) || typeof candidate.role !== "string" || !candidate.role || candidate.name !== void 0 && typeof candidate.name !== "string" || candidate.exact !== void 0 && typeof candidate.exact !== "boolean") return null;
59
+ return {
60
+ by: "role",
61
+ role: candidate.role,
62
+ ...typeof candidate.name === "string" ? { name: candidate.name } : {},
63
+ ...typeof candidate.exact === "boolean" ? { exact: candidate.exact } : {}
64
+ };
65
+ }
66
+ if (candidate.by === "text") {
67
+ if (!exactKeys(candidate, ["by", "text", "exact"]) || typeof candidate.text !== "string" || !candidate.text || candidate.exact !== void 0 && typeof candidate.exact !== "boolean") return null;
68
+ return {
69
+ by: "text",
70
+ text: candidate.text,
71
+ ...typeof candidate.exact === "boolean" ? { exact: candidate.exact } : {}
72
+ };
73
+ }
74
+ return null;
75
+ }
76
+ function parseTargetInput(value, options) {
77
+ const candidate = record(value);
78
+ if (!candidate) return null;
79
+ const allowed = ["page", "locator", "index", "expectedRevision"];
80
+ if (options.value) allowed.push("value");
81
+ if (options.values) allowed.push("values");
82
+ if (options.key) allowed.push("key");
83
+ if (options.wait) allowed.push("state", "timeout");
84
+ if (!exactKeys(candidate, allowed)) return null;
85
+ const page = parsePage(candidate.page);
86
+ const locator = candidate.locator === void 0 ? void 0 : parseBrowserLocator(candidate.locator);
87
+ const index = candidate.index;
88
+ const revision = candidate.expectedRevision;
89
+ if (page === null || options.locatorRequired && !locator || candidate.locator !== void 0 && !locator || index !== void 0 && (!Number.isInteger(index) || index < 0) || revision !== void 0 && (!Number.isInteger(revision) || revision < 0) || options.value && typeof candidate.value !== "string" || options.values && (!Array.isArray(candidate.values) || !candidate.values.every((item) => typeof item === "string")) || options.key && (typeof candidate.key !== "string" || !candidate.key || candidate.key.length > 100) || options.wait && candidate.state !== void 0 && candidate.state !== "attached" && candidate.state !== "visible" || options.wait && candidate.timeout !== void 0 && (typeof candidate.timeout !== "number" || candidate.timeout < 0 || candidate.timeout > 3e4)) return null;
90
+ return {
91
+ ...page ? { page } : {},
92
+ ...locator ? { locator } : {},
93
+ ...typeof index === "number" ? { index } : {},
94
+ ...typeof revision === "number" ? { expectedRevision: revision } : {},
95
+ ...options.value ? { value: candidate.value } : {},
96
+ ...options.values ? { values: candidate.values } : {},
97
+ ...options.key ? { key: candidate.key } : {},
98
+ ...options.wait && candidate.state ? { state: candidate.state } : {},
99
+ ...options.wait && typeof candidate.timeout === "number" ? { timeout: candidate.timeout } : {}
100
+ };
101
+ }
102
+ function parseBrowserOperationInput(operation, value) {
103
+ const candidate = record(value);
104
+ if (!candidate) return null;
105
+ if (operation === "browser.status") return exactKeys(candidate, []) ? {} : null;
106
+ if (operation === "browser.snapshot") {
107
+ const page = parsePage(candidate.page);
108
+ const locator = candidate.locator === void 0 ? void 0 : parseBrowserLocator(candidate.locator);
109
+ return exactKeys(candidate, ["page", "locator"]) && page !== null && (candidate.locator === void 0 || locator !== null) ? { ...page ? { page } : {}, ...locator ? { locator } : {} } : null;
110
+ }
111
+ if (operation === "browser.query" || operation === "browser.click" || operation === "browser.hover" || operation === "browser.focus" || operation === "browser.check" || operation === "browser.uncheck") {
112
+ return parseTargetInput(candidate, { locatorRequired: true });
113
+ }
114
+ if (operation === "browser.wait")
115
+ return parseTargetInput(candidate, { locatorRequired: true, wait: true });
116
+ if (operation === "browser.fill")
117
+ return parseTargetInput(candidate, { locatorRequired: true, value: true });
118
+ if (operation === "browser.select")
119
+ return parseTargetInput(candidate, { locatorRequired: true, values: true });
120
+ if (operation === "browser.press")
121
+ return parseTargetInput(candidate, { locatorRequired: false, key: true });
122
+ if (operation === "browser.script") {
123
+ const page = parsePage(candidate.page);
124
+ const { source, args, timeout } = candidate;
125
+ return exactKeys(candidate, ["page", "source", "args", "timeout"]) && page !== null && typeof source === "string" && source.length > 0 && source.length <= 1e5 && (args === void 0 || typeof args === "object" && args !== null && !Array.isArray(args)) && (timeout === void 0 || typeof timeout === "number" && timeout >= 0 && timeout <= 12e4) ? {
126
+ ...page ? { page } : {},
127
+ source,
128
+ ...args !== void 0 ? { args } : {},
129
+ ...typeof timeout === "number" ? { timeout } : {}
130
+ } : null;
131
+ }
132
+ if (operation === "browser.box") return parseTargetInput(candidate, { locatorRequired: true });
133
+ if (operation === "browser.mouse") {
134
+ const page = parsePage(candidate.page);
135
+ const { action, x, y, deltaX, deltaY } = candidate;
136
+ const point = (value2) => typeof value2 === "number" && Number.isFinite(value2);
137
+ const needsPoint = action === "move" || action === "click";
138
+ return exactKeys(candidate, ["page", "action", "x", "y", "deltaX", "deltaY"]) && page !== null && (action === "move" || action === "down" || action === "up" || action === "click") && (!needsPoint || point(x) && point(y)) && deltaX === void 0 && deltaY === void 0 ? { ...page ? { page } : {}, action, ...point(x) ? { x } : {}, ...point(y) ? { y } : {} } : null;
139
+ }
140
+ if (operation === "browser.wheel") {
141
+ const page = parsePage(candidate.page);
142
+ const { deltaX, deltaY } = candidate;
143
+ return exactKeys(candidate, ["page", "deltaX", "deltaY"]) && page !== null && (deltaX === void 0 || typeof deltaX === "number") && (deltaY === void 0 || typeof deltaY === "number") ? {
144
+ ...page ? { page } : {},
145
+ ...typeof deltaX === "number" ? { deltaX } : {},
146
+ ...typeof deltaY === "number" ? { deltaY } : {}
147
+ } : null;
148
+ }
149
+ if (operation === "browser.drag") {
150
+ const page = parsePage(candidate.page);
151
+ const { from, to, steps } = candidate;
152
+ const endpoint = (value2) => {
153
+ if (!value2 || typeof value2 !== "object") return false;
154
+ const record3 = value2;
155
+ if (record3.locator !== void 0) return parseBrowserLocator(record3.locator) !== null && exactKeys(record3, ["locator"]);
156
+ return typeof record3.x === "number" && typeof record3.y === "number" && exactKeys(record3, ["x", "y"]);
157
+ };
158
+ return exactKeys(candidate, ["page", "from", "to", "steps"]) && page !== null && endpoint(from) && endpoint(to) && (steps === void 0 || Number.isInteger(steps) && steps >= 1 && steps <= 100) ? { ...page ? { page } : {}, from, to, ...typeof steps === "number" ? { steps } : {} } : null;
159
+ }
160
+ if (operation === "browser.scroll") {
161
+ const page = parsePage(candidate.page);
162
+ const { direction, amount, expectedRevision } = candidate;
163
+ return exactKeys(candidate, ["page", "direction", "amount", "expectedRevision"]) && page !== null && (direction === "up" || direction === "down" || direction === "left" || direction === "right") && (amount === void 0 || typeof amount === "number" && amount >= 1 && amount <= 1e4) && (expectedRevision === void 0 || Number.isInteger(expectedRevision) && expectedRevision >= 0) ? {
164
+ ...page ? { page } : {},
165
+ direction,
166
+ ...typeof amount === "number" ? { amount } : {},
167
+ ...typeof expectedRevision === "number" ? { expectedRevision } : {}
168
+ } : null;
169
+ }
170
+ return parseTargetInput(candidate, { locatorRequired: false });
171
+ }
172
+ function parseBrowserOperationCall(value) {
173
+ const candidate = record(value);
174
+ if (!candidate || candidate.protocol !== SUPERCODE_BROWSER_OPERATION_PROTOCOL || typeof candidate.operation !== "string" || !OPERATION_NAMES.has(candidate.operation)) return null;
175
+ const operation = candidate.operation;
176
+ const input = parseBrowserOperationInput(operation, candidate.input);
177
+ return input === null ? null : { protocol: SUPERCODE_BROWSER_OPERATION_PROTOCOL, operation, input };
178
+ }
179
+ function parseBrowserOperationResult(value) {
180
+ const candidate = record(value);
181
+ if (!candidate || typeof candidate.ok !== "boolean" || typeof candidate.operation !== "string" || !OPERATION_NAMES.has(candidate.operation)) return null;
182
+ const operation = candidate.operation;
183
+ const targetCandidate = record(candidate.target);
184
+ const target = targetCandidate && (targetCandidate.page === void 0 || typeof targetCandidate.page === "string") && typeof targetCandidate.url === "string" && typeof targetCandidate.title === "string" && Number.isInteger(targetCandidate.revision) ? {
185
+ ...typeof targetCandidate.page === "string" ? { page: targetCandidate.page } : {},
186
+ url: targetCandidate.url,
187
+ title: targetCandidate.title,
188
+ revision: targetCandidate.revision
189
+ } : null;
190
+ if (candidate.ok) return target ? { ok: true, operation, target, value: candidate.value } : null;
191
+ const error = record(candidate.error);
192
+ const codes = /* @__PURE__ */ new Set([
193
+ "APPROVAL_REQUIRED",
194
+ "INVALID_INPUT",
195
+ "NOT_AVAILABLE",
196
+ "NOT_FOUND",
197
+ "STALE_PAGE",
198
+ "TIMED_OUT",
199
+ "UNSUPPORTED",
200
+ "FAILED"
201
+ ]);
202
+ if (!error || typeof error.code !== "string" || !codes.has(error.code) || typeof error.message !== "string") return null;
203
+ return {
204
+ ok: false,
205
+ operation,
206
+ error: { code: error.code, message: error.message },
207
+ ...target ? { target } : {}
208
+ };
209
+ }
210
+
211
+ // src/executor.ts
212
+ var PLAYWRIGHT_IMPLEMENTATION = "playwright-core 1.63.0";
213
+ var SCRIPT_TIMEOUT_CLAMP_MS = 9e3;
214
+ var ACTION_TIMEOUT_MS = 5e3;
215
+ var WAIT_TIMEOUT_CLAMP_MS = 9e3;
216
+ var PLAYWRIGHT_FEATURES = Object.freeze([
217
+ "page.ariaSnapshot",
218
+ "page.keyboard",
219
+ "page.navigation.history",
220
+ "locator.css",
221
+ "locator.ref",
222
+ "locator.role",
223
+ "locator.text",
224
+ "locator.testId",
225
+ "locator.label",
226
+ "locator.placeholder",
227
+ "locator.altText",
228
+ "locator.title",
229
+ "locator.first",
230
+ "locator.nth",
231
+ "locator.count",
232
+ "locator.waitFor",
233
+ "locator.textContent",
234
+ "locator.inputValue",
235
+ "locator.getAttribute",
236
+ "locator.isVisible",
237
+ "locator.click",
238
+ "locator.fill",
239
+ "locator.press",
240
+ "locator.hover",
241
+ "locator.focus",
242
+ "locator.check",
243
+ "locator.uncheck",
244
+ "locator.selectOption",
245
+ "locator.boundingBox",
246
+ "locator.dragTo",
247
+ "page.mouse",
248
+ "page.mouse.wheel",
249
+ "page.on.console",
250
+ "page.on.pageerror",
251
+ "page.consoleMessages",
252
+ "page.pageErrors",
253
+ "page.screenshot.png",
254
+ "page.evaluate"
255
+ ]);
256
+ var REVISION_KEY = "__supercodeBrowserRevision";
257
+ function installRevisionCounter(key) {
258
+ const scope = globalThis;
259
+ if (typeof scope[key] === "number") return;
260
+ scope[key] = 0;
261
+ new MutationObserver(() => {
262
+ scope[key] = scope[key] + 1;
263
+ }).observe(document, { attributes: true, childList: true, characterData: true, subtree: true });
264
+ }
265
+ function inspectInPage({ elements, limit }) {
266
+ const normalize = (value) => value.replace(/\s+/g, " ").trim();
267
+ const implied = (element) => {
268
+ const tag = element.tagName.toLowerCase();
269
+ if (tag === "button") return "button";
270
+ if (tag === "a" && element.hasAttribute("href")) return "link";
271
+ if (/^h[1-6]$/.test(tag)) return "heading";
272
+ if (tag === "textarea") return "textbox";
273
+ if (tag === "select") return "combobox";
274
+ if (tag === "option") return "option";
275
+ if (tag === "img") return "img";
276
+ if (tag === "ul" || tag === "ol") return "list";
277
+ if (tag === "li") return "listitem";
278
+ if (tag === "nav") return "navigation";
279
+ if (tag === "main") return "main";
280
+ if (tag === "form") return "form";
281
+ if (tag === "input") {
282
+ const type = (element.getAttribute("type") ?? "text").toLowerCase();
283
+ if (type === "button" || type === "submit" || type === "reset") return "button";
284
+ if (type === "checkbox") return "checkbox";
285
+ if (type === "radio") return "radio";
286
+ if (type === "range") return "slider";
287
+ return "textbox";
288
+ }
289
+ return null;
290
+ };
291
+ const nameOf = (element) => {
292
+ const aria = element.getAttribute("aria-label");
293
+ if (aria?.trim()) return normalize(aria);
294
+ const ids = element.getAttribute("aria-labelledby")?.split(/\s+/).filter(Boolean) ?? [];
295
+ const labelled = normalize(ids.map((id) => element.ownerDocument.getElementById(id)?.textContent ?? "").join(" "));
296
+ if (labelled) return labelled;
297
+ if (element instanceof HTMLInputElement && element.labels?.length) {
298
+ const label = normalize(Array.from(element.labels).map((candidate) => candidate.textContent ?? "").join(" "));
299
+ if (label) return label;
300
+ }
301
+ for (const attribute of ["alt", "title", "placeholder"]) {
302
+ const value = element.getAttribute(attribute);
303
+ if (value?.trim()) return normalize(value);
304
+ }
305
+ return normalize(element.textContent ?? "");
306
+ };
307
+ const visible = (element) => {
308
+ if (element.getAttribute("aria-hidden") === "true") return false;
309
+ if (!(element instanceof HTMLElement)) return true;
310
+ if (element.hidden) return false;
311
+ const style = getComputedStyle(element);
312
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
313
+ };
314
+ return elements.slice(0, limit).map((element) => {
315
+ const role = element.getAttribute("role")?.trim() || implied(element);
316
+ const name = nameOf(element).slice(0, 500);
317
+ const value = element instanceof HTMLInputElement ? element.type === "password" ? "<redacted>" : element.value : element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : void 0;
318
+ const checked = element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? element.checked : void 0;
319
+ return {
320
+ tag: element.tagName.toLowerCase(),
321
+ ...role ? { role } : {},
322
+ ...name ? { name } : {},
323
+ ...value !== void 0 ? { value: value.slice(0, 2e3) } : {},
324
+ disabled: (element instanceof HTMLButtonElement || element instanceof HTMLInputElement || element instanceof HTMLSelectElement) && element.disabled,
325
+ ...checked !== void 0 ? { checked } : {},
326
+ visible: visible(element)
327
+ };
328
+ });
329
+ }
330
+ function scrollInPage(delta) {
331
+ const amount = delta.amount ?? Math.max(240, Math.round(window.innerHeight * 0.8));
332
+ window.scrollBy({ left: delta.left * amount, top: delta.top * amount, behavior: "instant" });
333
+ return { x: window.scrollX, y: window.scrollY };
334
+ }
335
+ function pngSize(bytes) {
336
+ const signature = [137, 80, 78, 71, 13, 10, 26, 10];
337
+ if (bytes.length < 24 || !signature.every((byte, index) => bytes[index] === byte)) return null;
338
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
339
+ return { width: view.getUint32(16), height: view.getUint32(20) };
340
+ }
341
+ function base64(bytes) {
342
+ let binary = "";
343
+ for (let offset = 0; offset < bytes.length; offset += 32768)
344
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
345
+ return btoa(binary);
346
+ }
347
+ function serializableValue(value, syntheticEvents) {
348
+ if (value === void 0) return null;
349
+ const replacer = (_key, item) => {
350
+ const bytes = item instanceof Uint8Array ? item : item && typeof item === "object" && item.type === "Buffer" && Array.isArray(item.data) ? Uint8Array.from(item.data) : null;
351
+ if (!bytes) return item;
352
+ const size = pngSize(bytes);
353
+ const encoded = base64(bytes);
354
+ if (!size) return encoded;
355
+ const dataUrl = `data:image/png;base64,${encoded}`;
356
+ if (dataUrl.length > 768 * 1024) return { error: "Screenshot exceeds the 768 KiB image record bound.", width: size.width, height: size.height };
357
+ return {
358
+ dataUrl,
359
+ format: "png",
360
+ width: size.width,
361
+ height: size.height,
362
+ fidelity: syntheticEvents ? "Pixels rasterized by the CDP endpoint's own renderer." : "Native Chrome pixels captured over CDP."
363
+ };
364
+ };
365
+ try {
366
+ return JSON.parse(JSON.stringify(value, replacer));
367
+ } catch {
368
+ return String(value);
369
+ }
370
+ }
371
+ function errorMessage(error) {
372
+ return error instanceof Error ? error.message : String(error);
373
+ }
374
+ function classifyPlaywrightError(error) {
375
+ if (error instanceof BrowserActionRefusal) return error.code;
376
+ if (error instanceof errors.TimeoutError) return "TIMED_OUT";
377
+ const message = errorMessage(error);
378
+ if (/has been closed|Target closed|Target page, context or browser|Browser closed|browser has disconnected|Session closed|detached/i.test(message))
379
+ return "NOT_AVAILABLE";
380
+ if (/strict mode violation|resolved to 0 elements|no element matches|matched no element/i.test(message))
381
+ return "NOT_FOUND";
382
+ if (/unavailable|not supported/i.test(message)) return "UNSUPPORTED";
383
+ return "FAILED";
384
+ }
385
+ var PlaywrightOperationExecutor = class {
386
+ page;
387
+ options;
388
+ ready;
389
+ constructor(page, options) {
390
+ this.page = page;
391
+ this.options = options;
392
+ this.ready = (async () => {
393
+ await page.addInitScript(installRevisionCounter, REVISION_KEY);
394
+ await page.evaluate(installRevisionCounter, REVISION_KEY).catch(() => void 0);
395
+ })();
396
+ }
397
+ async execute(raw) {
398
+ const call = parseBrowserOperationCall(raw);
399
+ const operation = operationName(raw);
400
+ if (!call) return this.failure(operation, "INVALID_INPUT", "Invalid browser operation request.");
401
+ try {
402
+ await this.ready;
403
+ return await this.executeCall(call);
404
+ } catch (error) {
405
+ return this.failure(call.operation, classifyPlaywrightError(error), errorMessage(error));
406
+ }
407
+ }
408
+ locator(value, index) {
409
+ const locator = value;
410
+ const exact = (candidate) => candidate.exact !== void 0 ? { exact: candidate.exact } : {};
411
+ const page = this.page;
412
+ const base = locator.by === "css" ? page.locator(locator.value) : locator.by === "ref" ? page.locator(`aria-ref=${locator.value}`) : locator.by === "role" ? page.getByRole(locator.role, {
413
+ ...locator.name !== void 0 ? { name: locator.name } : {},
414
+ // A role's name matches exactly unless told otherwise.
415
+ exact: locator.exact ?? true
416
+ }) : locator.by === "text" ? page.getByText(locator.text, exact(locator)) : locator.by === "label" ? page.getByLabel(locator.text, exact(locator)) : locator.by === "placeholder" ? page.getByPlaceholder(locator.text, exact(locator)) : locator.by === "altText" ? page.getByAltText(locator.text, exact(locator)) : locator.by === "title" ? page.getByTitle(locator.text, exact(locator)) : page.getByTestId(locator.value);
417
+ return index === void 0 ? base : index === 0 ? base.first() : base.nth(index);
418
+ }
419
+ /**
420
+ * Playwright's `aria-ref` engine resolves against the last AI-mode snapshot
421
+ * the page took. Refs are stable per element, so refreshing the whole-page
422
+ * snapshot before resolving one keeps every ref from `browser.snapshot`
423
+ * addressable even after an element-scoped snapshot replaced the map.
424
+ */
425
+ async refreshRefs(value) {
426
+ if (value?.by === "ref")
427
+ await this.page.ariaSnapshot({ mode: "ai", timeout: ACTION_TIMEOUT_MS });
428
+ }
429
+ async refOf(locator) {
430
+ try {
431
+ const nodes = await locator.ariaSnapshotJSON({ mode: "ai", depth: 1, timeout: ACTION_TIMEOUT_MS });
432
+ const first = Array.isArray(nodes) ? nodes.find((node) => node && typeof node === "object") : void 0;
433
+ return typeof first?.ref === "string" ? first.ref : "";
434
+ } catch {
435
+ return "";
436
+ }
437
+ }
438
+ async inspectAll(locator, limit) {
439
+ const handles = (await locator.elementHandles()).slice(0, limit);
440
+ let fields;
441
+ try {
442
+ const inspect = inspectInPage;
443
+ fields = await this.page.evaluate(inspect, { elements: handles, limit });
444
+ } finally {
445
+ await Promise.all(handles.map((handle) => handle.dispose().catch(() => void 0)));
446
+ }
447
+ const refs = await Promise.all(fields.map((_, index) => this.refOf(locator.nth(index))));
448
+ return fields.map((field, index) => ({ ref: refs[index] ?? "", ...field }));
449
+ }
450
+ async inspect(locator) {
451
+ const [inspection] = await this.inspectAll(locator.first(), 1);
452
+ if (!inspection) throw new BrowserActionRefusal("NOT_FOUND", "The locator matched no element.");
453
+ return inspection;
454
+ }
455
+ /** Run a locator action; a failure on a locator that matches nothing is NOT_FOUND. */
456
+ async act(locator, action, run) {
457
+ try {
458
+ return await run();
459
+ } catch (error) {
460
+ if (!(error instanceof BrowserActionRefusal) && await locator.count().catch(() => 1) === 0)
461
+ throw new BrowserActionRefusal("NOT_FOUND", `${action} found no element for the locator.`);
462
+ throw error;
463
+ }
464
+ }
465
+ /** The host's policy for an action; an element that is not there is the action's own failure. */
466
+ async guard(action) {
467
+ if (!this.options.actionGuard || await action.locator.count().catch(() => 0) === 0) return;
468
+ await this.options.actionGuard(action);
469
+ }
470
+ async revision() {
471
+ const value = await this.page.evaluate((key) => globalThis[key], REVISION_KEY).catch(() => void 0);
472
+ return typeof value === "number" ? value : 0;
473
+ }
474
+ async executeCall(call) {
475
+ const input = call.input;
476
+ const page = this.page;
477
+ const expectedRevision = typeof input.expectedRevision === "number" ? input.expectedRevision : void 0;
478
+ if (expectedRevision !== void 0) {
479
+ const current = await this.revision();
480
+ if (expectedRevision !== current)
481
+ return this.failure(call.operation, "STALE_PAGE", `The page changed (expected revision ${expectedRevision}, current revision ${current}).`);
482
+ }
483
+ if (call.operation === "browser.status")
484
+ return this.success(call.operation, {
485
+ available: true,
486
+ features: PLAYWRIGHT_FEATURES,
487
+ syntheticEvents: this.options.syntheticEvents,
488
+ implementation: PLAYWRIGHT_IMPLEMENTATION,
489
+ endpoint: this.options.endpoint
490
+ });
491
+ if (call.operation === "browser.snapshot") {
492
+ await this.refreshRefs(input.locator);
493
+ const scoped = input.locator ? this.locator(input.locator).first() : null;
494
+ const text = scoped ? await this.act(scoped, "ariaSnapshot", () => scoped.ariaSnapshot({ mode: "ai", timeout: ACTION_TIMEOUT_MS })) : await page.ariaSnapshot({ mode: "ai", timeout: ACTION_TIMEOUT_MS });
495
+ const bounded = text.slice(0, 64e3);
496
+ return this.success(call.operation, { text: bounded, truncated: bounded.length < text.length });
497
+ }
498
+ if (call.operation === "browser.query") {
499
+ await this.refreshRefs(input.locator);
500
+ const locator2 = this.locator(input.locator, typeof input.index === "number" ? input.index : void 0);
501
+ const count = await locator2.count();
502
+ return this.success(call.operation, { count, matches: await this.inspectAll(locator2, 50), truncated: count > 50 });
503
+ }
504
+ if (call.operation === "browser.wait") {
505
+ await this.refreshRefs(input.locator);
506
+ const requested = typeof input.timeout === "number" ? input.timeout : 5e3;
507
+ const timeout2 = Math.min(requested, WAIT_TIMEOUT_CLAMP_MS);
508
+ const locator2 = this.locator(input.locator, typeof input.index === "number" ? input.index : 0);
509
+ try {
510
+ await locator2.waitFor({ state: input.state === "attached" ? "attached" : "visible", timeout: timeout2 });
511
+ } catch (error) {
512
+ if (error instanceof errors.TimeoutError)
513
+ throw new BrowserActionRefusal("TIMED_OUT", `waitFor timed out after ${timeout2}ms` + (timeout2 < requested ? ` (the requested ${requested}ms is clamped to ${WAIT_TIMEOUT_CLAMP_MS}ms by the provider call deadline)` : "") + ".");
514
+ throw error;
515
+ }
516
+ return this.success(call.operation, { ready: true });
517
+ }
518
+ if (call.operation === "browser.scroll") {
519
+ const direction = input.direction;
520
+ const position = await page.evaluate(scrollInPage, {
521
+ left: direction === "left" ? -1 : direction === "right" ? 1 : 0,
522
+ top: direction === "up" ? -1 : direction === "down" ? 1 : 0,
523
+ amount: typeof input.amount === "number" ? input.amount : null
524
+ });
525
+ return this.success(call.operation, position);
526
+ }
527
+ if (call.operation === "browser.script") return this.script(call);
528
+ if (call.operation === "browser.mouse") {
529
+ const action = input.action;
530
+ if (action === "move") await page.mouse.move(input.x, input.y);
531
+ else if (action === "click") await page.mouse.click(input.x, input.y);
532
+ else if (action === "down") await page.mouse.down();
533
+ else await page.mouse.up();
534
+ return this.success(call.operation, { action, ...typeof input.x === "number" ? { x: input.x, y: input.y } : {} });
535
+ }
536
+ if (call.operation === "browser.wheel") {
537
+ const deltaX = typeof input.deltaX === "number" ? input.deltaX : 0;
538
+ const deltaY = typeof input.deltaY === "number" ? input.deltaY : 0;
539
+ await page.mouse.wheel(deltaX, deltaY);
540
+ return this.success(call.operation, { deltaX, deltaY });
541
+ }
542
+ if (call.operation === "browser.drag") {
543
+ const resolve = async (endpoint) => {
544
+ if (endpoint.locator === void 0) return { x: endpoint.x, y: endpoint.y };
545
+ await this.refreshRefs(endpoint.locator);
546
+ const locator2 = this.locator(endpoint.locator).first();
547
+ const box = await locator2.count() === 0 ? null : await locator2.boundingBox({ timeout: ACTION_TIMEOUT_MS });
548
+ if (!box) throw new BrowserActionRefusal("NOT_FOUND", "The drag locator matched no element.");
549
+ return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
550
+ };
551
+ for (const end of [input.from, input.to])
552
+ if (end.locator !== void 0) await this.guard({ action: "drag", locator: this.locator(end.locator).first() });
553
+ const from = await resolve(input.from);
554
+ const to = await resolve(input.to);
555
+ await page.mouse.move(from.x, from.y);
556
+ await page.mouse.down();
557
+ await page.mouse.move(to.x, to.y, { steps: typeof input.steps === "number" ? input.steps : 8 });
558
+ await page.mouse.up();
559
+ return this.success(call.operation, { from, to });
560
+ }
561
+ if (call.operation === "browser.back") {
562
+ await page.goBack({ waitUntil: "commit", timeout: ACTION_TIMEOUT_MS });
563
+ return this.success(call.operation, { requested: true });
564
+ }
565
+ if (call.operation === "browser.forward") {
566
+ await page.goForward({ waitUntil: "commit", timeout: ACTION_TIMEOUT_MS });
567
+ return this.success(call.operation, { requested: true });
568
+ }
569
+ if (call.operation === "browser.reload") {
570
+ await page.reload({ waitUntil: "commit", timeout: ACTION_TIMEOUT_MS });
571
+ return this.success(call.operation, { requested: true });
572
+ }
573
+ await this.refreshRefs(input.locator);
574
+ const locator = input.locator ? this.locator(input.locator, typeof input.index === "number" ? input.index : 0) : null;
575
+ const timeout = ACTION_TIMEOUT_MS;
576
+ if (call.operation === "browser.box") {
577
+ const box = await locator.count() === 0 ? null : await locator.boundingBox({ timeout });
578
+ if (!box) return this.failure(call.operation, "NOT_FOUND", "The locator matched no element.");
579
+ return this.success(call.operation, box);
580
+ }
581
+ if (call.operation === "browser.press" && !locator) {
582
+ await this.guard({ action: "press", locator: page.locator("*:focus").first(), value: input.key });
583
+ await page.keyboard.press(input.key);
584
+ return this.success(call.operation, { key: input.key });
585
+ }
586
+ const target = locator;
587
+ const guarded = call.operation === "browser.select" ? "select" : call.operation.slice("browser.".length);
588
+ await this.guard({
589
+ action: guarded,
590
+ locator: target.first(),
591
+ ...call.operation === "browser.fill" ? { value: input.value } : call.operation === "browser.press" ? { value: input.key } : call.operation === "browser.select" ? { value: input.values } : {}
592
+ });
593
+ const before = await this.inspect(target).catch(() => null);
594
+ if (call.operation === "browser.click") await this.act(target, "click", () => target.click({ timeout }));
595
+ else if (call.operation === "browser.fill") await this.act(target, "fill", () => target.fill(input.value, { timeout }));
596
+ else if (call.operation === "browser.press") await this.act(target, "press", () => target.press(input.key, { timeout }));
597
+ else if (call.operation === "browser.hover") await this.act(target, "hover", () => target.hover({ timeout }));
598
+ else if (call.operation === "browser.focus") await this.act(target, "focus", () => target.focus({ timeout }));
599
+ else if (call.operation === "browser.check") await this.act(target, "check", () => target.check({ timeout }));
600
+ else if (call.operation === "browser.uncheck") await this.act(target, "uncheck", () => target.uncheck({ timeout }));
601
+ else if (call.operation === "browser.select") {
602
+ const values = await this.act(target, "selectOption", () => target.selectOption(input.values, { timeout }));
603
+ return this.success(call.operation, { values });
604
+ }
605
+ const after = await this.inspect(target).catch(() => null);
606
+ const inspection = after ?? before;
607
+ if (!inspection) throw new BrowserActionRefusal("NOT_FOUND", "The locator matched no element.");
608
+ return this.success(call.operation, inspection);
609
+ }
610
+ async script(call) {
611
+ const input = call.input;
612
+ const source = input.source;
613
+ const args = input.args ?? {};
614
+ const requested = typeof input.timeout === "number" ? input.timeout : 3e4;
615
+ const timeout = Math.min(requested, SCRIPT_TIMEOUT_CLAMP_MS);
616
+ const AsyncFunction = Object.getPrototypeOf(async function() {
617
+ }).constructor;
618
+ let run;
619
+ try {
620
+ run = new AsyncFunction("page", "args", source);
621
+ } catch (error) {
622
+ return this.failure(call.operation, "FAILED", `The script did not parse: ${errorMessage(error)}`);
623
+ }
624
+ const notice = timeout < requested ? `The script timeout is clamped to ${SCRIPT_TIMEOUT_CLAMP_MS}ms: the harness abandons a provider call at 12 s.` : void 0;
625
+ let timer;
626
+ try {
627
+ const value = await Promise.race([
628
+ run(this.page, args),
629
+ new Promise((_resolve, reject) => {
630
+ timer = setTimeout(() => reject(new BrowserActionRefusal("FAILED", `The script exceeded ${timeout}ms.` + (notice ? ` ${notice}` : ""))), timeout);
631
+ })
632
+ ]);
633
+ return this.success(call.operation, {
634
+ value: serializableValue(value, this.options.syntheticEvents),
635
+ ...notice ? { notice } : {}
636
+ });
637
+ } finally {
638
+ clearTimeout(timer);
639
+ this.page.removeAllListeners();
640
+ }
641
+ }
642
+ async target() {
643
+ if (this.page.isClosed()) return { url: "", title: "", revision: 0 };
644
+ const bounded = (promise, fallback) => {
645
+ let timer;
646
+ return Promise.race([
647
+ promise.catch(() => fallback),
648
+ new Promise((resolve) => {
649
+ timer = setTimeout(() => resolve(fallback), 1e3);
650
+ })
651
+ ]).finally(() => clearTimeout(timer));
652
+ };
653
+ const [title, revision] = await Promise.all([
654
+ bounded(this.page.title(), ""),
655
+ bounded(this.revision(), 0)
656
+ ]);
657
+ return { url: this.page.url(), title, revision };
658
+ }
659
+ async success(operation, value) {
660
+ return { ok: true, operation, target: await this.target(), value };
661
+ }
662
+ async failure(operation, code, message) {
663
+ return { ok: false, operation, error: { code, message }, target: await this.target() };
664
+ }
665
+ };
666
+ function operationName(raw) {
667
+ const candidate = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.operation : void 0;
668
+ return typeof candidate === "string" && BROWSER_OPERATION_NAMES.includes(candidate) ? candidate : "browser.status";
669
+ }
670
+
671
+ // src/provider.ts
672
+ import { timingSafeEqual } from "node:crypto";
673
+ import { chromium } from "playwright-core";
674
+ var MAX_REQUEST_BYTES = 256 * 1024;
675
+ var MAX_RESPONSE_BYTES = 1024 * 1024;
676
+ function record2(value) {
677
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
678
+ }
679
+ function tokenMatches(expected, actual) {
680
+ if (typeof actual !== "string") return false;
681
+ const left = Buffer.from(expected);
682
+ const right = Buffer.from(actual);
683
+ return left.length === right.length && timingSafeEqual(left, right);
684
+ }
685
+ function callOperation(call) {
686
+ const operation = record2(call)?.operation;
687
+ return typeof operation === "string" && operation.startsWith("browser.") ? operation : "browser.status";
688
+ }
689
+ function createPlaywrightBrowserProvider(options) {
690
+ if (!/^[0-9a-f]{32,}$/i.test(options.token))
691
+ throw new Error("The provider token must be at least 32 hexadecimal characters.");
692
+ let connection = null;
693
+ let queue = Promise.resolve();
694
+ const usable = (candidate) => candidate !== null && candidate.browser.isConnected() && !candidate.page.isClosed();
695
+ const connect = async () => {
696
+ const previous = connection;
697
+ connection = null;
698
+ const browser = previous?.browser.isConnected() ? previous.browser : await chromium.connectOverCDP(options.cdpUrl, { noDefaults: true });
699
+ const page = browser.contexts()[0]?.pages()[0];
700
+ if (!page) {
701
+ if (browser !== previous?.browser) await browser.close().catch(() => void 0);
702
+ throw new Error("The browser has no open page.");
703
+ }
704
+ const executor = new PlaywrightOperationExecutor(page, {
705
+ syntheticEvents: options.syntheticEvents,
706
+ endpoint: browser.version()
707
+ });
708
+ connection = { browser, page, executor };
709
+ return connection;
710
+ };
711
+ const failure = (operation, code, message) => ({ ok: false, operation, error: { code, message } });
712
+ const run = async (call) => {
713
+ let current = connection;
714
+ if (!usable(current)) {
715
+ try {
716
+ current = await connect();
717
+ } catch (error) {
718
+ return failure(
719
+ callOperation(call),
720
+ "NOT_AVAILABLE",
721
+ `The CDP endpoint is not available: ${error instanceof Error ? error.message : String(error)}`
722
+ );
723
+ }
724
+ }
725
+ return current.executor.execute(call);
726
+ };
727
+ return {
728
+ token: options.token,
729
+ async handle(request) {
730
+ const envelope = record2(request);
731
+ const id = typeof envelope?.id === "string" && envelope.id.length > 0 && envelope.id.length <= 256 ? envelope.id : null;
732
+ const respond = (result2) => ({ protocol: SUPERCODE_BROWSER_PROVIDER_PROTOCOL, id, result: result2 });
733
+ const call = envelope?.call;
734
+ const operation = callOperation(call);
735
+ if (!envelope || envelope.protocol !== SUPERCODE_BROWSER_PROVIDER_PROTOCOL || id === null || record2(call)?.protocol !== SUPERCODE_BROWSER_OPERATION_PROTOCOL)
736
+ return respond(failure(operation, "INVALID_INPUT", "Invalid browser provider request envelope."));
737
+ if (!tokenMatches(options.token, envelope.token))
738
+ return respond(failure(operation, "INVALID_INPUT", "Invalid browser provider token."));
739
+ const result = queue.then(() => run(call));
740
+ queue = result.catch(() => void 0);
741
+ return respond(await result);
742
+ },
743
+ async close() {
744
+ const current = connection;
745
+ connection = null;
746
+ await current?.browser.close().catch(() => void 0);
747
+ }
748
+ };
749
+ }
750
+
751
+ // src/serve.ts
752
+ import { randomBytes } from "node:crypto";
753
+ import * as fs from "node:fs";
754
+ import * as http from "node:http";
755
+ import * as net from "node:net";
756
+ import * as os from "node:os";
757
+ import * as path from "node:path";
758
+ var HOST = "127.0.0.1";
759
+ function defaultProvidersDirectory() {
760
+ const env = process.env;
761
+ const root = env.SUPERCODE_HOME ? env.SUPERCODE_HOME : env.XDG_CONFIG_HOME ? path.join(env.XDG_CONFIG_HOME, "supercode") : env.HOME ? path.join(env.HOME, ".config", "supercode") : path.join(os.tmpdir(), "supercode");
762
+ return path.join(root, "providers");
763
+ }
764
+ function invalid(id, message) {
765
+ return {
766
+ protocol: SUPERCODE_BROWSER_PROVIDER_PROTOCOL,
767
+ id,
768
+ result: { ok: false, operation: "browser.status", error: { code: "INVALID_INPUT", message } }
769
+ };
770
+ }
771
+ function encode(response) {
772
+ const text = JSON.stringify(response);
773
+ if (Buffer.byteLength(text) <= MAX_RESPONSE_BYTES) return text;
774
+ return JSON.stringify({
775
+ ...response,
776
+ result: {
777
+ ok: false,
778
+ operation: response.result.operation,
779
+ error: { code: "FAILED", message: "The operation result exceeds the provider's 1 MiB response bound." }
780
+ }
781
+ });
782
+ }
783
+ async function answer(provider, body) {
784
+ let request;
785
+ try {
786
+ request = JSON.parse(body);
787
+ } catch {
788
+ return invalid(null, "The request is not JSON.");
789
+ }
790
+ return provider.handle(request);
791
+ }
792
+ async function serveTcp(provider, options) {
793
+ const server = net.createServer((socket) => {
794
+ const chunks = [];
795
+ let size = 0;
796
+ let done = false;
797
+ const finish = (response) => {
798
+ if (socket.destroyed) return;
799
+ socket.end(`${encode(response)}
800
+ `);
801
+ };
802
+ socket.setTimeout(15e3, () => socket.destroy());
803
+ socket.on("error", () => socket.destroy());
804
+ socket.on("data", (chunk) => {
805
+ if (done) return;
806
+ const newline = chunk.indexOf(10);
807
+ const part = newline === -1 ? chunk : chunk.subarray(0, newline);
808
+ size += part.length + (newline === -1 ? 0 : 1);
809
+ if (size > MAX_REQUEST_BYTES) {
810
+ done = true;
811
+ finish(invalid(null, "The request exceeds 256 KiB."));
812
+ return;
813
+ }
814
+ chunks.push(part);
815
+ if (newline === -1) return;
816
+ done = true;
817
+ void answer(provider, Buffer.concat(chunks).toString("utf8")).then(finish, (error) => finish(invalid(null, error instanceof Error ? error.message : String(error))));
818
+ });
819
+ socket.on("end", () => {
820
+ if (done) return;
821
+ done = true;
822
+ void answer(provider, Buffer.concat(chunks).toString("utf8")).then(finish, () => socket.destroy());
823
+ });
824
+ });
825
+ await new Promise((resolve, reject) => {
826
+ server.once("error", reject);
827
+ server.listen(options.port, HOST, () => {
828
+ server.off("error", reject);
829
+ resolve();
830
+ });
831
+ });
832
+ const port2 = server.address().port;
833
+ const directory = path.join(options.providersDirectory ?? defaultProvidersDirectory(), "browser");
834
+ fs.mkdirSync(directory, { recursive: true, mode: 448 });
835
+ fs.chmodSync(directory, 448);
836
+ const id = `playwright.cdp.${port2}`;
837
+ const discoveryPath = path.join(directory, `${id}.json`);
838
+ const workspace = fs.realpathSync(options.workspace ?? process.cwd());
839
+ const discovery = {
840
+ protocol: SUPERCODE_BROWSER_PROVIDER_PROTOCOL,
841
+ workspace,
842
+ host: HOST,
843
+ port: port2,
844
+ token: provider.token,
845
+ provider: {
846
+ id,
847
+ name: "Playwright over CDP",
848
+ fidelity: {
849
+ implementation: PLAYWRIGHT_IMPLEMENTATION,
850
+ transport: "cdp",
851
+ accessibility: "playwright-aria-snapshot",
852
+ syntheticEvents: options.syntheticEvents,
853
+ trustedInput: !options.syntheticEvents,
854
+ script: "node-playwright-page"
855
+ }
856
+ }
857
+ };
858
+ const temporary = `${discoveryPath}.${randomBytes(6).toString("hex")}.tmp`;
859
+ fs.writeFileSync(temporary, `${JSON.stringify(discovery, null, 2)}
860
+ `, { mode: 384, flag: "wx" });
861
+ fs.renameSync(temporary, discoveryPath);
862
+ return {
863
+ framing: "tcp",
864
+ port: port2,
865
+ discoveryPath,
866
+ async stop() {
867
+ fs.rmSync(discoveryPath, { force: true });
868
+ await new Promise((resolve) => server.close(() => resolve()));
869
+ }
870
+ };
871
+ }
872
+ async function serveHttp(provider, options) {
873
+ const server = http.createServer((request, response) => {
874
+ const send = (status, body) => {
875
+ response.writeHead(status, body === void 0 ? {} : { "content-type": "application/json" });
876
+ response.end(body);
877
+ };
878
+ const pathname = (request.url ?? "/").split("?", 1)[0];
879
+ if (pathname !== "/") {
880
+ request.resume();
881
+ send(404);
882
+ return;
883
+ }
884
+ if (request.method !== "POST") {
885
+ request.resume();
886
+ response.setHeader("allow", "POST");
887
+ send(405);
888
+ return;
889
+ }
890
+ const chunks = [];
891
+ let size = 0;
892
+ let rejected = false;
893
+ request.on("data", (chunk) => {
894
+ if (rejected) return;
895
+ size += chunk.length;
896
+ if (size > MAX_REQUEST_BYTES) {
897
+ rejected = true;
898
+ send(413, encode(invalid(null, "The request exceeds 256 KiB.")));
899
+ request.destroy();
900
+ return;
901
+ }
902
+ chunks.push(chunk);
903
+ });
904
+ request.on("end", () => {
905
+ if (rejected) return;
906
+ let body;
907
+ try {
908
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
909
+ } catch {
910
+ send(400, encode(invalid(null, "The request is not JSON.")));
911
+ return;
912
+ }
913
+ void provider.handle(body).then(
914
+ (result) => send(200, encode(result)),
915
+ (error) => send(500, encode(invalid(null, error instanceof Error ? error.message : String(error))))
916
+ );
917
+ });
918
+ });
919
+ await new Promise((resolve, reject) => {
920
+ server.once("error", reject);
921
+ server.listen(options.port, HOST, () => {
922
+ server.off("error", reject);
923
+ resolve();
924
+ });
925
+ });
926
+ const port2 = server.address().port;
927
+ return {
928
+ framing: "http",
929
+ port: port2,
930
+ async stop() {
931
+ await new Promise((resolve) => server.close(() => resolve()));
932
+ }
933
+ };
934
+ }
935
+
936
+ // src/cli.ts
937
+ import { randomBytes as randomBytes2 } from "node:crypto";
938
+ import { parseArgs } from "node:util";
939
+ var USAGE = "usage: supercode-browser-playwright --cdp-url <url> (--tcp <port> [--providers-dir <dir>] [--workspace <path>] | --http <port>) [--token <hex>] [--synthetic-events]";
940
+ function port(value, flag) {
941
+ const parsed = Number(value);
942
+ if (!/^\d+$/.test(value) || parsed > 65535) throw new Error(`${flag} must be a port number (0 picks a free one).`);
943
+ return parsed;
944
+ }
945
+ async function main(argv) {
946
+ const { values } = parseArgs({
947
+ args: [...argv],
948
+ strict: true,
949
+ allowPositionals: false,
950
+ options: {
951
+ "cdp-url": { type: "string" },
952
+ tcp: { type: "string" },
953
+ http: { type: "string" },
954
+ "providers-dir": { type: "string" },
955
+ workspace: { type: "string" },
956
+ token: { type: "string" },
957
+ "synthetic-events": { type: "boolean", default: false }
958
+ }
959
+ });
960
+ const cdpUrl = values["cdp-url"];
961
+ if (!cdpUrl) throw new Error(`--cdp-url is required.
962
+ ${USAGE}`);
963
+ if (values.tcp === void 0 === (values.http === void 0))
964
+ throw new Error(`Exactly one of --tcp and --http is required.
965
+ ${USAGE}`);
966
+ if (values.http !== void 0 && (values["providers-dir"] !== void 0 || values.workspace !== void 0))
967
+ throw new Error(`--providers-dir and --workspace belong to the TCP framing.
968
+ ${USAGE}`);
969
+ if (values.http !== void 0 && values.token === void 0)
970
+ throw new Error("--http requires --token: the host that calls the provider passes it.");
971
+ const token = values.token ?? randomBytes2(32).toString("hex");
972
+ const syntheticEvents = values["synthetic-events"] ?? false;
973
+ const provider = createPlaywrightBrowserProvider({ cdpUrl, token, syntheticEvents });
974
+ const handle = values.tcp !== void 0 ? await serveTcp(provider, {
975
+ port: port(values.tcp, "--tcp"),
976
+ syntheticEvents,
977
+ ...values["providers-dir"] !== void 0 ? { providersDirectory: values["providers-dir"] } : {},
978
+ ...values.workspace !== void 0 ? { workspace: values.workspace } : {}
979
+ }) : await serveHttp(provider, { port: port(values.http, "--http") });
980
+ process.stdout.write(`${JSON.stringify({ listening: { framing: handle.framing, port: handle.port } })}
981
+ `);
982
+ let stopping = false;
983
+ const stop = () => {
984
+ if (stopping) return;
985
+ stopping = true;
986
+ void Promise.allSettled([handle.stop(), provider.close()]).then(() => process.exit(0));
987
+ };
988
+ process.once("SIGINT", stop);
989
+ process.once("SIGTERM", stop);
990
+ process.once("SIGHUP", stop);
991
+ return handle;
992
+ }
993
+ export {
994
+ ACTION_TIMEOUT_MS,
995
+ BROWSER_OPERATION_NAMES,
996
+ BrowserActionRefusal,
997
+ MAX_REQUEST_BYTES,
998
+ MAX_RESPONSE_BYTES,
999
+ PLAYWRIGHT_FEATURES,
1000
+ PLAYWRIGHT_IMPLEMENTATION,
1001
+ PlaywrightOperationExecutor,
1002
+ SCRIPT_TIMEOUT_CLAMP_MS,
1003
+ SUPERCODE_BROWSER_OPERATION_PROTOCOL,
1004
+ SUPERCODE_BROWSER_PROVIDER_PROTOCOL,
1005
+ WAIT_TIMEOUT_CLAMP_MS,
1006
+ classifyPlaywrightError,
1007
+ createPlaywrightBrowserProvider,
1008
+ defaultProvidersDirectory,
1009
+ main,
1010
+ parseBrowserLocator,
1011
+ parseBrowserOperationCall,
1012
+ parseBrowserOperationResult,
1013
+ serveHttp,
1014
+ serveTcp
1015
+ };
1016
+ //# sourceMappingURL=index.mjs.map