@agent-browser-io/browser 0.1.0 → 0.2.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.
Files changed (77) hide show
  1. package/README.md +70 -0
  2. package/bin/agent-browser-cli.cjs +2 -0
  3. package/bin/agent-browser-mcp.cjs +2 -0
  4. package/bin/index.cjs +54 -0
  5. package/dist/cjs/ai-sdk/createBrowserTools.d.ts +58 -0
  6. package/dist/cjs/ai-sdk/createBrowserTools.d.ts.map +1 -0
  7. package/dist/cjs/ai-sdk/createBrowserTools.js +192 -0
  8. package/dist/cjs/ai-sdk/createBrowserTools.js.map +1 -0
  9. package/dist/cjs/cli/cli.d.ts +6 -0
  10. package/dist/cjs/cli/cli.d.ts.map +1 -0
  11. package/dist/cjs/cli/cli.js +149 -0
  12. package/dist/cjs/cli/cli.js.map +1 -0
  13. package/dist/cjs/core/agent-browser/agent-browser.d.ts +51 -0
  14. package/dist/cjs/core/agent-browser/agent-browser.d.ts.map +1 -0
  15. package/dist/cjs/core/agent-browser/agent-browser.js +80 -0
  16. package/dist/cjs/core/agent-browser/agent-browser.js.map +1 -0
  17. package/dist/cjs/core/agent-browser/normalize-script.d.ts +3 -0
  18. package/dist/cjs/core/agent-browser/normalize-script.d.ts.map +1 -0
  19. package/dist/cjs/core/agent-browser/normalize-script.js +531 -0
  20. package/dist/cjs/core/agent-browser/normalize-script.js.map +1 -0
  21. package/dist/cjs/core/browser-backend/browser-backend.d.ts +23 -0
  22. package/dist/cjs/core/browser-backend/browser-backend.d.ts.map +1 -0
  23. package/dist/cjs/core/browser-backend/browser-backend.js +6 -0
  24. package/dist/cjs/core/browser-backend/browser-backend.js.map +1 -0
  25. package/dist/cjs/core/browser-backend/index.d.ts +3 -0
  26. package/dist/cjs/core/browser-backend/index.d.ts.map +1 -0
  27. package/dist/cjs/core/browser-backend/index.js +6 -0
  28. package/dist/cjs/core/browser-backend/index.js.map +1 -0
  29. package/dist/cjs/core/browser-backend/playwright-browser-backend.d.ts +27 -0
  30. package/dist/cjs/core/browser-backend/playwright-browser-backend.d.ts.map +1 -0
  31. package/dist/cjs/core/browser-backend/playwright-browser-backend.js +86 -0
  32. package/dist/cjs/core/browser-backend/playwright-browser-backend.js.map +1 -0
  33. package/dist/cjs/index.d.ts +5 -0
  34. package/dist/cjs/index.d.ts.map +1 -1
  35. package/dist/cjs/index.js +8 -3
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/mcp/server.d.ts +5 -0
  38. package/dist/cjs/mcp/server.d.ts.map +1 -0
  39. package/dist/cjs/mcp/server.js +284 -0
  40. package/dist/cjs/mcp/server.js.map +1 -0
  41. package/dist/esm/ai-sdk/createBrowserTools.d.ts +58 -0
  42. package/dist/esm/ai-sdk/createBrowserTools.d.ts.map +1 -0
  43. package/dist/esm/ai-sdk/createBrowserTools.js +189 -0
  44. package/dist/esm/ai-sdk/createBrowserTools.js.map +1 -0
  45. package/dist/esm/cli/cli.d.ts +6 -0
  46. package/dist/esm/cli/cli.d.ts.map +1 -0
  47. package/dist/esm/cli/cli.js +114 -0
  48. package/dist/esm/cli/cli.js.map +1 -0
  49. package/dist/esm/core/agent-browser/agent-browser.d.ts +51 -0
  50. package/dist/esm/core/agent-browser/agent-browser.d.ts.map +1 -0
  51. package/dist/esm/core/agent-browser/agent-browser.js +76 -0
  52. package/dist/esm/core/agent-browser/agent-browser.js.map +1 -0
  53. package/dist/esm/core/agent-browser/normalize-script.d.ts +3 -0
  54. package/dist/esm/core/agent-browser/normalize-script.d.ts.map +1 -0
  55. package/dist/esm/core/agent-browser/normalize-script.js +528 -0
  56. package/dist/esm/core/agent-browser/normalize-script.js.map +1 -0
  57. package/dist/esm/core/browser-backend/browser-backend.d.ts +23 -0
  58. package/dist/esm/core/browser-backend/browser-backend.d.ts.map +1 -0
  59. package/dist/esm/core/browser-backend/browser-backend.js +5 -0
  60. package/dist/esm/core/browser-backend/browser-backend.js.map +1 -0
  61. package/dist/esm/core/browser-backend/index.d.ts +3 -0
  62. package/dist/esm/core/browser-backend/index.d.ts.map +1 -0
  63. package/dist/esm/core/browser-backend/index.js +2 -0
  64. package/dist/esm/core/browser-backend/index.js.map +1 -0
  65. package/dist/esm/core/browser-backend/playwright-browser-backend.d.ts +27 -0
  66. package/dist/esm/core/browser-backend/playwright-browser-backend.d.ts.map +1 -0
  67. package/dist/esm/core/browser-backend/playwright-browser-backend.js +82 -0
  68. package/dist/esm/core/browser-backend/playwright-browser-backend.js.map +1 -0
  69. package/dist/esm/index.d.ts +5 -0
  70. package/dist/esm/index.d.ts.map +1 -1
  71. package/dist/esm/index.js +4 -2
  72. package/dist/esm/index.js.map +1 -1
  73. package/dist/esm/mcp/server.d.ts +5 -0
  74. package/dist/esm/mcp/server.d.ts.map +1 -0
  75. package/dist/esm/mcp/server.js +282 -0
  76. package/dist/esm/mcp/server.js.map +1 -0
  77. package/package.json +14 -5
@@ -0,0 +1,528 @@
1
+ /** Injected browser script for wireframe normalization. Exported as string for evaluation in the page. */
2
+ export const NORMALIZE_SCRIPT = `
3
+ // This script is intended to be used in the browser.
4
+ // It is not intended to be used in node.
5
+ (function applyWireframeMode() {
6
+ // --- PART A: INJECT CSS STYLES (idempotent) ---
7
+ if (!document.getElementById("wf-normalize")) {
8
+ const cssStyles = \`
9
+ /* FORCE GLOBAL MONOSPACE & METRICS */
10
+ * {
11
+ font-family: "Courier New", Courier, monospace !important;
12
+ font-size: 12px !important;
13
+ font-weight: normal !important;
14
+ color: #000000 !important;
15
+ line-height: 18px !important;
16
+ letter-spacing: 0px !important;
17
+ box-shadow: none !important;
18
+ text-shadow: none !important;
19
+ border-radius: 0 !important;
20
+ transition: none !important;
21
+ }
22
+
23
+ /* HIGH CONTRAST THEME */
24
+ body {
25
+ background-color: #ffffff !important;
26
+ color: #000000 !important;
27
+ }
28
+
29
+ /* STRUCTURAL OUTLINES */
30
+ div, section, article, header, footer, nav, aside, main {
31
+ border: 1px dotted #cccccc !important;
32
+ }
33
+
34
+ /* KILL PSEUDO-ELEMENT BACKGROUNDS */
35
+ *::before, *::after {
36
+ background-image: none !important;
37
+ background-color: transparent !important;
38
+ border: none !important;
39
+ }
40
+
41
+ /* INTERACTIVE ELEMENTS */
42
+ a, button, input[type="submit"], input[type="button"], [role="button"] {
43
+ border: 2px solid #000000 !important;
44
+ background-color: #ffffff !important;
45
+ color: #000000 !important;
46
+ text-transform: uppercase !important;
47
+ text-decoration: none !important;
48
+ font-weight: bold !important;
49
+ }
50
+
51
+ /* FORM INPUTS */
52
+ input, textarea, select {
53
+ border: 1px solid #000000 !important;
54
+ background-color: #ffffff !important;
55
+ color: #000000 !important;
56
+ font-family: monospace !important;
57
+ }
58
+
59
+ /* HIDE DECORATIVE MEDIA */
60
+ img, video, canvas, svg {
61
+ opacity: 0.5 !important;
62
+ filter: grayscale(100%) !important;
63
+ border: 1px dashed #000 !important;
64
+ }
65
+
66
+ /* BACKGROUND IMAGE INDICATORS */
67
+ [data-bg-image="true"] {
68
+ background-color: #f4f4f4 !important;
69
+ border: 1px dashed #555555 !important;
70
+ position: relative !important;
71
+ }
72
+ [data-bg-image="true"]::before {
73
+ content: "[ BG IMAGE ]";
74
+ position: absolute;
75
+ top: 0; right: 0;
76
+ background: #000000; color: #ffffff;
77
+ font-size: 10px !important;
78
+ padding: 2px 4px;
79
+ opacity: 0.8;
80
+ z-index: 9999;
81
+ pointer-events: none;
82
+ font-weight: normal !important;
83
+ }
84
+ \`;
85
+
86
+ const styleSheet = document.createElement("style");
87
+ styleSheet.id = "wf-normalize";
88
+ styleSheet.type = "text/css";
89
+ styleSheet.innerText = cssStyles;
90
+ document.head.appendChild(styleSheet);
91
+ }
92
+
93
+ // --- PART B: NORMALIZE DOM CONTENT (idempotent) ---
94
+ if (!document.body) return; // page not ready
95
+
96
+ // Helper: check if element is the top-most (not behind an overlay) at its center
97
+ function isElementVisible(el) {
98
+ var rect = el.getBoundingClientRect();
99
+ var cx = rect.left + rect.width / 2;
100
+ var cy = rect.top + rect.height / 2;
101
+ if (cx < 0 || cy < 0 || cx >= window.innerWidth || cy >= window.innerHeight) return false;
102
+ var topEl = document.elementFromPoint(cx, cy);
103
+ if (!topEl) return false;
104
+ return el.contains(topEl) || topEl.contains(el);
105
+ }
106
+
107
+ // Remove old ref IDs and reset counter
108
+ document.querySelectorAll("[data-ref-id]").forEach(function(el) {
109
+ el.removeAttribute("data-ref-id");
110
+ });
111
+ var refCounter = 1;
112
+
113
+ // Only run DOM mutations (media replacement, accessibility labels, bg stripping) once
114
+ if (!document.body.hasAttribute("data-wf-normalized")) {
115
+ document.body.setAttribute("data-wf-normalized", "true");
116
+
117
+ // 1. REVEAL ACCESSIBILITY LABELS (ICON BUTTONS)
118
+ var interactives = document.querySelectorAll('a, button, [role="button"]');
119
+ interactives.forEach(function(el) {
120
+ if (el.innerText.trim() === '') {
121
+ var label = el.getAttribute('aria-label') || el.getAttribute('title');
122
+ // Also check child elements for hints (e.g. HN vote arrows: <a><div title="upvote"></a>)
123
+ if (!label) {
124
+ var hintEl = el.querySelector('[aria-label], [title]');
125
+ if (hintEl) label = hintEl.getAttribute('aria-label') || hintEl.getAttribute('title');
126
+ }
127
+ if (label) {
128
+ el.innerText = label;
129
+ el.style.display = 'inline-block';
130
+ el.style.width = 'auto';
131
+ el.style.height = 'auto';
132
+ }
133
+ }
134
+ });
135
+
136
+ // 2. REPLACE MEDIA WITH INTELLIGENT PLACEHOLDERS
137
+ var mediaElements = document.querySelectorAll('img, svg, video, canvas');
138
+ mediaElements.forEach(function(el) {
139
+ var rect = el.getBoundingClientRect();
140
+ if (rect.width < 10 || rect.height < 10) return;
141
+
142
+ var placeholder = document.createElement('div');
143
+ placeholder.style.cssText =
144
+ 'width:' + rect.width + 'px;' +
145
+ 'height:' + rect.height + 'px;' +
146
+ 'background:#f0f0f0;color:#000;font-family:monospace;font-weight:bold;' +
147
+ 'display:flex;align-items:center;justify-content:center;text-align:center;' +
148
+ 'overflow:hidden;box-sizing:border-box;';
149
+
150
+ var altText = el.getAttribute('alt') ||
151
+ el.getAttribute('title') ||
152
+ el.getAttribute('aria-label');
153
+
154
+ if (!altText && el.tagName.toLowerCase() === 'svg') {
155
+ var titleEl = el.querySelector('title');
156
+ if (titleEl) altText = titleEl.textContent;
157
+ }
158
+
159
+ if (rect.width <= 50 && rect.height <= 50) {
160
+ placeholder.style.border = '1px solid #333';
161
+ placeholder.style.fontSize = '8px';
162
+ placeholder.style.padding = '0';
163
+ // Use short label for tiny icons to avoid overflow
164
+ if (rect.width <= 20 || rect.height <= 20) {
165
+ placeholder.innerText = altText ? altText.substring(0, 1) : '*';
166
+ } else {
167
+ var iconLabel = altText ? altText.substring(0, 8) : "ICON";
168
+ placeholder.innerText = '[' + iconLabel + ']';
169
+ }
170
+ } else {
171
+ placeholder.style.border = '2px solid #000';
172
+ placeholder.style.fontSize = '10px';
173
+ placeholder.style.padding = '2px';
174
+ placeholder.style.wordBreak = 'break-word';
175
+ var imgLabel = altText ? altText : "IMAGE";
176
+ placeholder.innerText = '[' + imgLabel + ']';
177
+ }
178
+
179
+ if (el.parentNode) {
180
+ el.parentNode.replaceChild(placeholder, el);
181
+ }
182
+ });
183
+
184
+ // 3. CONVERT BACKGROUND IMAGES & STRIP COLORS
185
+ var allElements = document.querySelectorAll('*');
186
+ allElements.forEach(function(el) {
187
+ var style = window.getComputedStyle(el);
188
+ var hasBgImage = style.backgroundImage !== 'none' && style.backgroundImage !== '';
189
+ var hasBgColor = style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&
190
+ style.backgroundColor !== 'transparent' &&
191
+ style.backgroundColor !== 'rgb(255, 255, 255)';
192
+
193
+ if (hasBgImage) {
194
+ el.style.backgroundImage = 'none';
195
+ el.setAttribute('data-bg-image', 'true');
196
+ }
197
+
198
+ if (hasBgColor) {
199
+ el.style.backgroundColor = '#ffffff';
200
+ }
201
+ });
202
+ }
203
+
204
+ // 4. REMOVE TRANSPARENT OVERLAY BACKDROPS (run every time — new overlays can appear)
205
+ // Full-viewport high z-index divs with no meaningful text are just backdrop overlays
206
+ // that block elementFromPoint for content behind them
207
+ var overlayDivs = document.querySelectorAll('div');
208
+ overlayDivs.forEach(function(el) {
209
+ var style = window.getComputedStyle(el);
210
+ var z = parseInt(style.zIndex) || 0;
211
+ if (z < 100 || style.display === 'none') return;
212
+ var rect = el.getBoundingClientRect();
213
+ if (rect.width < window.innerWidth * 0.9 || rect.height < window.innerHeight * 0.9) return;
214
+ var text = (el.innerText || '').trim();
215
+ if (text.length < 5) {
216
+ el.style.display = 'none';
217
+ }
218
+ });
219
+
220
+ // Overflow buffer: capture high-z-index elements pushed below viewport by CSS normalization
221
+ var OVERFLOW_BUFFER = 200;
222
+ var captureHeight = window.innerHeight + OVERFLOW_BUFFER;
223
+
224
+ // 5. TAG INTERACTIVE ELEMENTS (always re-run since we cleared ref IDs)
225
+ // Only tag elements visible, in viewport (+ overflow buffer), and not behind overlays
226
+ function tagIfVisible(el) {
227
+ if (el.hasAttribute('data-ref-id')) return; // already tagged
228
+ var rect = el.getBoundingClientRect();
229
+ var style = window.getComputedStyle(el);
230
+ if (rect.width === 0 || rect.height === 0 || style.display === 'none' || style.visibility === 'hidden') return;
231
+ if (rect.bottom < 0 || rect.top >= captureHeight || rect.right < 0 || rect.left >= window.innerWidth) return;
232
+ // Elements in overflow zone (below viewport): can't use elementFromPoint, just tag them
233
+ if (rect.top >= window.innerHeight) {
234
+ el.setAttribute('data-ref-id', String(refCounter++));
235
+ return;
236
+ }
237
+ if (!isElementVisible(el)) return;
238
+ el.setAttribute('data-ref-id', String(refCounter++));
239
+ }
240
+
241
+ // Pass 1: semantic interactive elements
242
+ var allInteractives = document.querySelectorAll('a, button, input, textarea, select, [role="button"], [onclick], [tabindex="0"]');
243
+ allInteractives.forEach(tagIfVisible);
244
+
245
+ // Pass 2: non-semantic clickable elements (divs/spans with cursor:pointer and text)
246
+ // This catches sites that use <div> as buttons without proper ARIA roles
247
+ var potentialClickables = document.querySelectorAll('div, span');
248
+ potentialClickables.forEach(function(el) {
249
+ if (el.hasAttribute('data-ref-id')) return;
250
+ // Skip if already inside a tagged interactive element
251
+ if (el.closest && el.closest('[data-ref-id]')) return;
252
+ var style = window.getComputedStyle(el);
253
+ if (style.cursor !== 'pointer') return;
254
+ // Must have direct text content (not just children with text)
255
+ var hasDirectText = false;
256
+ for (var i = 0; i < el.childNodes.length; i++) {
257
+ if (el.childNodes[i].nodeType === 3 && el.childNodes[i].textContent.trim()) {
258
+ hasDirectText = true;
259
+ break;
260
+ }
261
+ }
262
+ if (!hasDirectText) return;
263
+ tagIfVisible(el);
264
+ });
265
+
266
+ console.log("Normalization Complete.");
267
+
268
+ // --- PART C: WIREFRAME STRING GENERATOR ---
269
+ function generateWireframeString() {
270
+ // 1. Measure CHAR_W dynamically
271
+ var probe = document.createElement('span');
272
+ probe.style.cssText = 'font-family:"Courier New",Courier,monospace;font-size:12px;line-height:18px;letter-spacing:0px;position:absolute;top:-9999px;left:-9999px;white-space:pre;visibility:hidden;';
273
+ probe.textContent = 'MMMMMMMMMM';
274
+ document.body.appendChild(probe);
275
+ var CHAR_W = probe.getBoundingClientRect().width / 10;
276
+ document.body.removeChild(probe);
277
+ if (CHAR_W <= 0) CHAR_W = 7.2; // fallback
278
+
279
+ var CHAR_H = 18;
280
+
281
+ // 2. Create grid (includes overflow buffer for elements pushed below viewport)
282
+ var OVERFLOW_BUFFER_C = 200;
283
+ var captureHeightC = window.innerHeight + OVERFLOW_BUFFER_C;
284
+ var gridWidth = Math.ceil(window.innerWidth / CHAR_W);
285
+ var gridHeight = Math.ceil(captureHeightC / CHAR_H);
286
+ var grid = [];
287
+ for (var r = 0; r < gridHeight; r++) {
288
+ var row = [];
289
+ for (var c = 0; c < gridWidth; c++) {
290
+ row.push(' ');
291
+ }
292
+ grid.push(row);
293
+ }
294
+
295
+ function writeToGrid(x, y, str) {
296
+ if (y < 0 || y >= gridHeight) return;
297
+ for (var i = 0; i < str.length; i++) {
298
+ var curX = x + i;
299
+ if (curX >= 0 && curX < gridWidth) {
300
+ grid[y][curX] = str[i];
301
+ }
302
+ }
303
+ }
304
+
305
+ // 3. Draw borders only for block-level interactive elements (buttons, form controls)
306
+ // Skip inline links — they just get ref labels.
307
+ var refElements = document.querySelectorAll('[data-ref-id]');
308
+ refElements.forEach(function(el) {
309
+ var rect = el.getBoundingClientRect();
310
+ var style = window.getComputedStyle(el);
311
+ if (style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0) return;
312
+ // Skip elements outside capture area
313
+ if (rect.bottom < 0 || rect.top >= captureHeightC || rect.right < 0 || rect.left >= window.innerWidth) return;
314
+
315
+ // Only draw borders for buttons, form elements, and block-level role=button
316
+ var tag = el.tagName;
317
+ var isFormEl = (tag === 'BUTTON' || tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT');
318
+ var isBlockButton = (el.getAttribute('role') === 'button' && style.display !== 'inline');
319
+ if (!isFormEl && !isBlockButton) return;
320
+
321
+ var x = Math.floor(rect.left / CHAR_W);
322
+ var y = Math.floor(rect.top / CHAR_H);
323
+ var w = Math.max(2, Math.ceil(rect.width / CHAR_W));
324
+ var h = Math.max(2, Math.ceil(rect.height / CHAR_H));
325
+
326
+ // Top border
327
+ writeToGrid(x, y, '+' + repeat('-', Math.max(0, w - 2)) + '+');
328
+ // Side borders
329
+ for (var i = 1; i < h - 1; i++) {
330
+ writeToGrid(x, y + i, '|');
331
+ writeToGrid(x + w - 1, y + i, '|');
332
+ }
333
+ // Bottom border
334
+ if (h > 1) {
335
+ writeToGrid(x, y + h - 1, '+' + repeat('-', Math.max(0, w - 2)) + '+');
336
+ }
337
+ });
338
+
339
+ // Helper: check if element is the top-most at a given point
340
+ function isVisibleAt(el, px, py) {
341
+ if (px < 0 || py < 0 || px >= window.innerWidth) return false;
342
+ // Overflow zone: elementFromPoint doesn't work below viewport, assume visible
343
+ if (py >= window.innerHeight) return py < captureHeightC;
344
+ var topEl = document.elementFromPoint(px, py);
345
+ if (!topEl) return false;
346
+ return el.contains(topEl) || topEl.contains(el);
347
+ }
348
+
349
+ // 4. Render text via TreeWalker (text only, no labels yet)
350
+ var labeledRefs = {}; // refId -> {row, col} where the element's first text starts
351
+ var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
352
+ var textNode;
353
+ while ((textNode = walker.nextNode())) {
354
+ var parent = textNode.parentElement;
355
+ if (!parent) continue;
356
+
357
+ // Skip script/style
358
+ var tag = parent.tagName;
359
+ if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT') continue;
360
+
361
+ // Skip invisible parents
362
+ var pStyle = window.getComputedStyle(parent);
363
+ if (pStyle.display === 'none' || pStyle.visibility === 'hidden' || pStyle.opacity === '0') continue;
364
+
365
+ // Skip empty text
366
+ var rawText = textNode.textContent;
367
+ if (!rawText || !rawText.trim()) continue;
368
+
369
+ // Skip form elements (handled separately)
370
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || tag === 'OPTION') continue;
371
+
372
+ // Collapse whitespace
373
+ var collapsed = rawText.replace(/\\s+/g, ' ').trim();
374
+ if (!collapsed) continue;
375
+
376
+ // Track first text position for ref label insertion
377
+ var refAncestor = parent.closest ? parent.closest('[data-ref-id]') : null;
378
+
379
+ // Use Range + getClientRects for accurate per-line positioning
380
+ var range = document.createRange();
381
+ range.selectNodeContents(textNode);
382
+ var rects = range.getClientRects();
383
+
384
+ if (rects.length === 0) continue;
385
+
386
+ var charOffset = 0;
387
+ var firstWritten = false;
388
+ for (var ri = 0; ri < rects.length; ri++) {
389
+ var rr = rects[ri];
390
+ if (rr.width < 1 || rr.height < 1) continue;
391
+
392
+ // Skip rects outside capture area or covered by overlays
393
+ if (rr.bottom < 0 || rr.top >= captureHeightC || rr.right < 0 || rr.left >= window.innerWidth) {
394
+ charOffset += Math.max(1, Math.ceil(rr.width / CHAR_W));
395
+ continue;
396
+ }
397
+ var rcx = rr.left + rr.width / 2;
398
+ var rcy = rr.top + rr.height / 2;
399
+ if (!isVisibleAt(parent, rcx, rcy)) {
400
+ charOffset += Math.max(1, Math.ceil(rr.width / CHAR_W));
401
+ continue;
402
+ }
403
+
404
+ var gx = Math.floor(rr.left / CHAR_W);
405
+ var gy = Math.floor(rr.top / CHAR_H);
406
+ var charsInLine = Math.max(1, Math.ceil(rr.width / CHAR_W));
407
+ // Cap to actual remaining text length
408
+ var remaining = collapsed.length - charOffset;
409
+ if (charsInLine > remaining) charsInLine = remaining;
410
+ var slice = collapsed.substring(charOffset, charOffset + charsInLine);
411
+ if (slice) {
412
+ writeToGrid(gx, gy, slice);
413
+
414
+ // Record insertion point for this ref's label
415
+ if (!firstWritten && refAncestor) {
416
+ var rid = refAncestor.getAttribute('data-ref-id');
417
+ if (!labeledRefs[rid]) {
418
+ labeledRefs[rid] = {row: gy, col: gx};
419
+ }
420
+ firstWritten = true;
421
+ }
422
+ }
423
+ charOffset += charsInLine;
424
+ }
425
+ }
426
+
427
+ // 5. Handle form inputs
428
+ var formEls = document.querySelectorAll('input, textarea, select');
429
+ formEls.forEach(function(el) {
430
+ var rect = el.getBoundingClientRect();
431
+ var style = window.getComputedStyle(el);
432
+ if (style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0) return;
433
+
434
+ var x = Math.floor(rect.left / CHAR_W);
435
+ var y = Math.floor(rect.top / CHAR_H);
436
+ var w = Math.max(2, Math.ceil(rect.width / CHAR_W));
437
+ var h = Math.max(2, Math.ceil(rect.height / CHAR_H));
438
+ var midY = y + Math.floor((h - 1) / 2);
439
+ var refId = el.getAttribute('data-ref-id');
440
+
441
+ if (el.tagName === 'SELECT') {
442
+ var selectedText = el.options && el.selectedIndex >= 0 ? el.options[el.selectedIndex].text : '';
443
+ var selectContent = selectedText.substring(0, Math.max(0, w - 4));
444
+ if (selectContent) writeToGrid(x + 1, midY, selectContent);
445
+ writeToGrid(x + w - 2, midY, 'v');
446
+ } else {
447
+ var val = el.value || el.getAttribute('placeholder') || '';
448
+ if (val) writeToGrid(x + 1, midY, val);
449
+ }
450
+
451
+ // Track form element position for label insertion
452
+ if (refId && !labeledRefs[refId]) {
453
+ labeledRefs[refId] = {row: midY, col: x};
454
+ }
455
+ });
456
+
457
+ // Also track ref elements with no text at all (empty buttons, icons)
458
+ refElements.forEach(function(el) {
459
+ var refId = el.getAttribute('data-ref-id');
460
+ if (labeledRefs[refId]) return;
461
+ var rect = el.getBoundingClientRect();
462
+ var style = window.getComputedStyle(el);
463
+ if (style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0) return;
464
+ labeledRefs[refId] = {row: Math.floor(rect.top / CHAR_H), col: Math.floor(rect.left / CHAR_W)};
465
+ });
466
+
467
+ // 6. Insert ref labels by splicing into rows (never overwrites text)
468
+ // Collect all labels per row, sorted right-to-left so insertions don't shift later positions
469
+ var rowInsertions = {}; // row -> [{col, label}]
470
+ refElements.forEach(function(el) {
471
+ var refId = el.getAttribute('data-ref-id');
472
+ if (!refId) return;
473
+ var pos = labeledRefs[refId];
474
+ if (!pos) return;
475
+ var label = '[' + refId + ']';
476
+ if (!rowInsertions[pos.row]) rowInsertions[pos.row] = [];
477
+ rowInsertions[pos.row].push({col: pos.col, label: label});
478
+ });
479
+
480
+ // For each row with insertions, splice labels into the row string
481
+ for (var rowIdx in rowInsertions) {
482
+ var r = parseInt(rowIdx, 10);
483
+ if (r < 0 || r >= gridHeight) continue;
484
+ var inserts = rowInsertions[r];
485
+ // Sort by column descending so we insert right-to-left (preserves earlier positions)
486
+ inserts.sort(function(a, b) { return b.col - a.col; });
487
+ // Convert row to string, splice in labels, write back
488
+ var rowStr = grid[r].join('');
489
+ for (var ii = 0; ii < inserts.length; ii++) {
490
+ var ins = inserts[ii];
491
+ var c = Math.max(0, Math.min(ins.col, rowStr.length));
492
+ // Avoid splitting inside a [...] bracket sequence — shift to before the '['
493
+ var openBracket = rowStr.lastIndexOf('[', c);
494
+ if (openBracket >= 0) {
495
+ var closeBracket = rowStr.indexOf(']', openBracket);
496
+ if (closeBracket >= c) {
497
+ // Insertion point is inside [..], shift to before the [
498
+ c = openBracket;
499
+ }
500
+ }
501
+ rowStr = rowStr.substring(0, c) + ins.label + rowStr.substring(c);
502
+ }
503
+ // Write back to grid as variable-width row (will be joined as string in step 7)
504
+ grid[r] = rowStr.split('');
505
+ }
506
+
507
+ // 7. Trim output
508
+ var lines = [];
509
+ for (var r = 0; r < gridHeight; r++) {
510
+ lines.push(grid[r].join('').replace(/\\s+$/, ''));
511
+ }
512
+ // Drop trailing empty rows
513
+ while (lines.length > 0 && lines[lines.length - 1] === '') {
514
+ lines.pop();
515
+ }
516
+ return lines.join('\\n');
517
+ }
518
+
519
+ function repeat(ch, count) {
520
+ var s = '';
521
+ for (var i = 0; i < count; i++) s += ch;
522
+ return s;
523
+ }
524
+
525
+ window.generateWireframeString = generateWireframeString;
526
+ })();
527
+ `;
528
+ //# sourceMappingURL=normalize-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-script.js","sourceRoot":"","sources":["../../../../src/core/agent-browser/normalize-script.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6gB/B,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Browser interface for agent-browser-io
3
+ */
4
+ export interface IBrowserBackend {
5
+ launch(): Promise<void>;
6
+ navigate(url: string): Promise<void>;
7
+ click(selector: string): Promise<void>;
8
+ type(selector: string, text: string): Promise<void>;
9
+ evaluate(script: string): Promise<any>;
10
+ dblclick(selector: string): Promise<void>;
11
+ fill(selector: string, text: string): Promise<void>;
12
+ press(key: string): Promise<void>;
13
+ hover(selector: string): Promise<void>;
14
+ select(selector: string, value: string): Promise<void>;
15
+ check(selector: string): Promise<void>;
16
+ uncheck(selector: string): Promise<void>;
17
+ scroll(direction: 'up' | 'down' | 'left' | 'right', pixels?: number): Promise<void>;
18
+ screenshot(path?: string, options?: {
19
+ fullPage?: boolean;
20
+ }): Promise<Buffer | void>;
21
+ close(): Promise<void>;
22
+ }
23
+ //# sourceMappingURL=browser-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-backend.d.ts","sourceRoot":"","sources":["../../../../src/core/browser-backend/browser-backend.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC5B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Browser interface for agent-browser-io
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=browser-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-backend.js","sourceRoot":"","sources":["../../../../src/core/browser-backend/browser-backend.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,3 @@
1
+ export type { IBrowserBackend } from './browser-backend.js';
2
+ export { PlaywrightBrowserBackend as DefaultBrowserBackend } from './playwright-browser-backend.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/browser-backend/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,IAAI,qBAAqB,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { PlaywrightBrowserBackend as DefaultBrowserBackend } from './playwright-browser-backend.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/browser-backend/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,IAAI,qBAAqB,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Default browser implementation using Playwright
3
+ */
4
+ import type { IBrowserBackend } from './browser-backend.js';
5
+ export declare class PlaywrightBrowserBackend implements IBrowserBackend {
6
+ private browser;
7
+ private page;
8
+ launch(): Promise<void>;
9
+ navigate(url: string): Promise<void>;
10
+ click(selector: string): Promise<void>;
11
+ type(selector: string, text: string): Promise<void>;
12
+ evaluate(script: string): Promise<unknown>;
13
+ private ensurePage;
14
+ dblclick(selector: string): Promise<void>;
15
+ fill(selector: string, text: string): Promise<void>;
16
+ press(key: string): Promise<void>;
17
+ hover(selector: string): Promise<void>;
18
+ select(selector: string, value: string): Promise<void>;
19
+ check(selector: string): Promise<void>;
20
+ uncheck(selector: string): Promise<void>;
21
+ scroll(direction: 'up' | 'down' | 'left' | 'right', pixels?: number): Promise<void>;
22
+ screenshot(path?: string, options?: {
23
+ fullPage?: boolean;
24
+ }): Promise<Buffer | void>;
25
+ close(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=playwright-browser-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-browser-backend.d.ts","sourceRoot":"","sources":["../../../../src/core/browser-backend/playwright-browser-backend.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,qBAAa,wBAAyB,YAAW,eAAe;IAC5D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAqB;IAE3B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAKlC,UAAU;IAMlB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,MAAM,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,MAAM,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhF,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMnF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B"}