@browserless/agent 14.0.2 → 14.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.
- package/package.json +2 -2
- package/src/model.js +8 -1
- package/src/snapshot.js +86 -3
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@browserless/agent",
|
|
3
3
|
"description": "Drive a Puppeteer page toward a natural-language goal using finite decisions.",
|
|
4
4
|
"homepage": "https://browserless.js.org",
|
|
5
|
-
"version": "14.0.
|
|
5
|
+
"version": "14.0.4",
|
|
6
6
|
"main": "src",
|
|
7
7
|
"author": {
|
|
8
8
|
"email": "hello@microlink.io",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
61
|
"types": "src/index.d.ts",
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "bf9a31cbe256022543902ec9e748dec6b5abf06b"
|
|
63
63
|
}
|
package/src/model.js
CHANGED
|
@@ -345,7 +345,14 @@ const fieldText = async (goal, action, state, history, model, options) => {
|
|
|
345
345
|
system: TEXT_VALUE,
|
|
346
346
|
prompt: JSON.stringify({
|
|
347
347
|
goal,
|
|
348
|
-
field: {
|
|
348
|
+
field: {
|
|
349
|
+
id: action.id,
|
|
350
|
+
node: action.node,
|
|
351
|
+
label: action.label,
|
|
352
|
+
role: action.role,
|
|
353
|
+
value: action.value,
|
|
354
|
+
context: state.scopes?.[action.node] ?? ''
|
|
355
|
+
},
|
|
349
356
|
page: { title: state.title, text: state.text },
|
|
350
357
|
recent_actions: history.slice(-TEXT_HISTORY_LENGTH)
|
|
351
358
|
}),
|
package/src/snapshot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global location, scrollX, scrollY, innerWidth, innerHeight */
|
|
1
|
+
/* global HTMLElement, getComputedStyle, location, scrollX, scrollY, innerWidth, innerHeight */
|
|
2
2
|
// Adapted from browser-use/jev-ultrafast snapshot.js (MIT). See README.md.
|
|
3
3
|
module.exports = function snapshot () {
|
|
4
4
|
return (() => {
|
|
@@ -169,6 +169,8 @@ module.exports = function snapshot () {
|
|
|
169
169
|
.filter(safe)
|
|
170
170
|
.map(e => [identity(e), e.value, e.checked, e.selectedIndex, e.disabled, e.readOnly])
|
|
171
171
|
]
|
|
172
|
+
// A named form control shadows the form's own innerText property.
|
|
173
|
+
const readInnerText = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerText').get
|
|
172
174
|
cache.guard = e => {
|
|
173
175
|
if (!e?.isConnected || !visibleNow(e)) return null
|
|
174
176
|
const scope =
|
|
@@ -195,7 +197,9 @@ module.exports = function snapshot () {
|
|
|
195
197
|
])
|
|
196
198
|
: null,
|
|
197
199
|
e.getAttribute('href'),
|
|
198
|
-
scope
|
|
200
|
+
(scope instanceof HTMLElement
|
|
201
|
+
? readInnerText.call(scope).slice(0, 6000)
|
|
202
|
+
: scope?.textContent?.slice(0, 6000)) || '',
|
|
199
203
|
[...(cache.closest(e, 'form')?.querySelectorAll('input,textarea,select') || [])]
|
|
200
204
|
.filter(safe)
|
|
201
205
|
.map(field => [
|
|
@@ -279,6 +283,74 @@ module.exports = function snapshot () {
|
|
|
279
283
|
}
|
|
280
284
|
}
|
|
281
285
|
}
|
|
286
|
+
const collected = new Set(actions.map(action => cache.nodes.get(action.node)))
|
|
287
|
+
const listSelector = 'ul,ol,[role="list"],[role="listbox"],[role="menu"],[role="tablist"]'
|
|
288
|
+
const pointer = element => {
|
|
289
|
+
if (getComputedStyle(element).cursor !== 'pointer') return false
|
|
290
|
+
const parent = flatParent(element)
|
|
291
|
+
if (!parent || getComputedStyle(parent).cursor !== 'pointer') return true
|
|
292
|
+
return parent.matches(listSelector)
|
|
293
|
+
}
|
|
294
|
+
const inLayer = element => {
|
|
295
|
+
for (let current = element; current; current = flatParent(current)) {
|
|
296
|
+
if (current === document.body || current === document.documentElement) return false
|
|
297
|
+
const position = getComputedStyle(current).position
|
|
298
|
+
if (position === 'absolute' || position === 'fixed' || position === 'sticky') return true
|
|
299
|
+
}
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
const pointerLists = new WeakMap()
|
|
303
|
+
const inPointerList = element => {
|
|
304
|
+
const parent = flatParent(element)
|
|
305
|
+
if (!parent?.matches(listSelector)) return false
|
|
306
|
+
let eligible = pointerLists.get(parent)
|
|
307
|
+
if (eligible == null) {
|
|
308
|
+
eligible = [...parent.children].filter(pointer).length >= 2
|
|
309
|
+
pointerLists.set(parent, eligible)
|
|
310
|
+
}
|
|
311
|
+
return eligible
|
|
312
|
+
}
|
|
313
|
+
const candidates = []
|
|
314
|
+
for (const element of queryAll('li,div,span,td,dd,p')) {
|
|
315
|
+
if (collected.has(element) || !visible(element) || !pointer(element)) continue
|
|
316
|
+
if (element.matches(':disabled') || cache.closest(element, '[aria-disabled="true"]')) continue
|
|
317
|
+
if (element.querySelector(selector)) continue
|
|
318
|
+
const rect = element.getBoundingClientRect()
|
|
319
|
+
const x = rect.x + rect.width / 2
|
|
320
|
+
const y = rect.y + rect.height / 2
|
|
321
|
+
if (
|
|
322
|
+
rect.width <= 0 ||
|
|
323
|
+
rect.height <= 0 ||
|
|
324
|
+
x < 0 ||
|
|
325
|
+
y < 0 ||
|
|
326
|
+
x >= innerWidth ||
|
|
327
|
+
y >= innerHeight
|
|
328
|
+
) {
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
const hit = cache.elementFromPoint(x, y)
|
|
332
|
+
if (hit && !cache.contains(element, hit)) continue
|
|
333
|
+
if (!inLayer(element) && !inPointerList(element)) continue
|
|
334
|
+
const label = (name(element) || '').replace(/\s+/g, ' ').trim().slice(0, 120)
|
|
335
|
+
if (!label) continue
|
|
336
|
+
candidates.push({ element, rect, label })
|
|
337
|
+
}
|
|
338
|
+
const rows = candidates.filter(
|
|
339
|
+
({ element }) =>
|
|
340
|
+
!candidates.some(
|
|
341
|
+
other => other.element !== element && cache.contains(element, other.element)
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
for (const { element, rect, label } of rows.slice(0, 40)) {
|
|
345
|
+
actions.push({
|
|
346
|
+
node: identity(element),
|
|
347
|
+
role: 'button',
|
|
348
|
+
label,
|
|
349
|
+
rect: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
|
|
350
|
+
kind: 'click',
|
|
351
|
+
value: ''
|
|
352
|
+
})
|
|
353
|
+
}
|
|
282
354
|
const words = []
|
|
283
355
|
const range = document.createRange()
|
|
284
356
|
let length = 0
|
|
@@ -350,8 +422,18 @@ module.exports = function snapshot () {
|
|
|
350
422
|
const height = document.documentElement.scrollHeight
|
|
351
423
|
const pageKey = cache.pageKey()
|
|
352
424
|
const guards = {}
|
|
425
|
+
const scopes = {}
|
|
353
426
|
for (const a of actions) {
|
|
354
|
-
if (
|
|
427
|
+
if (a.node in guards) continue
|
|
428
|
+
const element = cache.nodes.get(a.node)
|
|
429
|
+
guards[a.node] = cache.guard(element)
|
|
430
|
+
const scope =
|
|
431
|
+
cache.closest(element, 'form,dialog,[role="dialog"],article,li,tr,[role="row"]') ||
|
|
432
|
+
flatParent(element)
|
|
433
|
+
scopes[a.node] =
|
|
434
|
+
(scope instanceof HTMLElement
|
|
435
|
+
? readInnerText.call(scope).slice(0, 1000)
|
|
436
|
+
: scope?.textContent?.slice(0, 1000)) || ''
|
|
355
437
|
}
|
|
356
438
|
// Compare meaning and identity. Geometry is always resolved and hit-tested just before input.
|
|
357
439
|
const semantics = actions.map(({ rect, ...action }) => action)
|
|
@@ -391,6 +473,7 @@ module.exports = function snapshot () {
|
|
|
391
473
|
marker,
|
|
392
474
|
pageKey,
|
|
393
475
|
guards,
|
|
476
|
+
scopes,
|
|
394
477
|
omittedActions
|
|
395
478
|
}
|
|
396
479
|
})()
|