@orkestrel/browser 0.0.20 → 0.0.22
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/README.md +6 -3
- package/dist/src/browser/index.d.ts +77 -19
- package/dist/src/browser/index.js +361 -21
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +963 -173
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +354 -78
- package/dist/src/core/index.d.ts +354 -78
- package/dist/src/core/index.js +957 -175
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +36 -11
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +2 -2
- package/dist/src/server/index.d.ts +2 -2
- package/dist/src/server/index.js +36 -11
- package/dist/src/server/index.js.map +1 -1
- package/package.json +5 -5
package/dist/src/core/index.cjs
CHANGED
|
@@ -128,6 +128,16 @@ var BROWSER_SNAPSHOT_NODE_LIMIT = 1e5;
|
|
|
128
128
|
var BROWSER_FRAME_WORLD_NAME = "__orkestrelBrowserFrame";
|
|
129
129
|
/** Sets the number of animation frames whose element bounds must agree before trusted input. */
|
|
130
130
|
var BROWSER_STABLE_FRAME_COUNT = 2;
|
|
131
|
+
/**
|
|
132
|
+
* Names the finished transitions and animations that wake a parked wait.
|
|
133
|
+
*
|
|
134
|
+
* @remarks
|
|
135
|
+
* Chromium 154.0.4258.53 delivers these events in main and isolated worlds after a
|
|
136
|
+
* visibility or discrete display exit. Opacity alone leaves text in `innerText`.
|
|
137
|
+
*/
|
|
138
|
+
var BROWSER_WAIT_EVENTS = Object.freeze(["transitionend", "animationend"]);
|
|
139
|
+
/** Matches protocol errors caused by replacement or closure of an execution context. */
|
|
140
|
+
var BROWSER_CONTEXT_LOSS_PATTERN = /execution context was destroyed|cannot find context with specified id|inspected target navigated or closed/i;
|
|
131
141
|
/** Maps a canonical modifier name to its CDP Input modifier bit value. */
|
|
132
142
|
var BROWSER_KEY_MODIFIERS = Object.freeze({
|
|
133
143
|
Alt: 1,
|
|
@@ -153,7 +163,7 @@ var BROWSER_MOUSE_BUTTON_MASKS = Object.freeze({
|
|
|
153
163
|
*/
|
|
154
164
|
var BROWSER_HAR_CREATOR = Object.freeze({
|
|
155
165
|
name: "@orkestrel/browser",
|
|
156
|
-
version: "0.0.
|
|
166
|
+
version: "0.0.22"
|
|
157
167
|
});
|
|
158
168
|
/** Names the attribute that tags temporary screenshot styles and masks. */
|
|
159
169
|
var BROWSER_SCREENSHOT_ATTRIBUTE = "data-orkestrel-screenshot";
|
|
@@ -263,6 +273,15 @@ var BROWSER_REGISTRY_OUTPUT_LIMIT = 4096;
|
|
|
263
273
|
var BROWSER_REFERENCE_PREFIX = "e";
|
|
264
274
|
/** Bounds the default number of actionable elements in an outline. */
|
|
265
275
|
var BROWSER_OUTLINE_LIMIT = 150;
|
|
276
|
+
/**
|
|
277
|
+
* Matches one word of an outline search and of a row's role and name: a run of at least 3 letters
|
|
278
|
+
* or digits.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* The 3-character floor drops words such as `a`, `to`, and `of`, which would otherwise score a row
|
|
282
|
+
* whose name shares them with the search.
|
|
283
|
+
*/
|
|
284
|
+
var BROWSER_SEARCH_PATTERN = /[\p{L}\p{N}]{3,}/gu;
|
|
266
285
|
/** Names accessibility roles that receive actionable outline references. */
|
|
267
286
|
var BROWSER_INTERACTIVE_ROLES = Object.freeze(/* @__PURE__ */ new Set([
|
|
268
287
|
"button",
|
|
@@ -384,6 +403,7 @@ var BROWSER_TOOL_PENDING_NOTE = "An earlier input is still pending; call look.";
|
|
|
384
403
|
var BROWSER_OBSERVATION_TOOL_NAMES = Object.freeze([
|
|
385
404
|
"look",
|
|
386
405
|
"read",
|
|
406
|
+
"plain",
|
|
387
407
|
"tabs"
|
|
388
408
|
]);
|
|
389
409
|
/**
|
|
@@ -393,15 +413,15 @@ var BROWSER_OBSERVATION_TOOL_NAMES = Object.freeze([
|
|
|
393
413
|
*/
|
|
394
414
|
var BROWSER_TOOL_CHANGED_NOTE = "(The page changed before the view could be read; call look.)";
|
|
395
415
|
/**
|
|
396
|
-
* Holds the clause that ends the footer of a cut result
|
|
397
|
-
*
|
|
416
|
+
* Holds the clause that ends the footer of a cut result other than an action or `dialog` receipt
|
|
417
|
+
* that carries a view, including `look` and `read` results.
|
|
398
418
|
*/
|
|
399
419
|
var BROWSER_TOOL_CUT_FOOTER = "the rest was cut";
|
|
400
420
|
/**
|
|
401
|
-
* Holds the clause that ends the footer of a cut
|
|
402
|
-
*
|
|
421
|
+
* Holds the clause that ends the footer of a cut action or `dialog` receipt that carries a view,
|
|
422
|
+
* and names `look` as the call that finds an element the cut view leaves out.
|
|
403
423
|
*/
|
|
404
|
-
var BROWSER_TOOL_VIEW_FOOTER = "the rest was cut; call
|
|
424
|
+
var BROWSER_TOOL_VIEW_FOOTER = "the rest was cut; call look with words to find";
|
|
405
425
|
/**
|
|
406
426
|
* Names the accessibility roles the `type` tool writes to: `textbox`, `searchbox`, and
|
|
407
427
|
* `spinbutton` take typed text, and `combobox` and `listbox` take a select control's option
|
|
@@ -423,12 +443,13 @@ var BROWSER_TYPED_ROLES = Object.freeze(/* @__PURE__ */ new Set([
|
|
|
423
443
|
/** Caps the `wait` tool's `timeout` parameter at `30_000` milliseconds. */
|
|
424
444
|
var BROWSER_TOOL_TIMEOUT_LIMIT_MS = 3e4;
|
|
425
445
|
/**
|
|
426
|
-
* Names every tool the browser toolset reserves: `look`, `read`, `click`, `type`, `press`,
|
|
446
|
+
* Names every tool the browser toolset reserves: `look`, `read`, `plain`, `click`, `type`, `press`,
|
|
427
447
|
* `navigate`, `wait`, `dialog`, `tabs`, and `switch`.
|
|
428
448
|
*/
|
|
429
449
|
var BROWSER_TOOL_NAMES = Object.freeze([
|
|
430
450
|
"look",
|
|
431
451
|
"read",
|
|
452
|
+
"plain",
|
|
432
453
|
"click",
|
|
433
454
|
"type",
|
|
434
455
|
"press",
|
|
@@ -459,14 +480,15 @@ var BROWSER_SCHEMES = Object.freeze(["http:", "https:"]);
|
|
|
459
480
|
*
|
|
460
481
|
* @remarks
|
|
461
482
|
* Every tool description is at most 25 words and says what the tool shows or does, and for
|
|
462
|
-
* `look` and `
|
|
483
|
+
* `look`, `read`, and `plain` when to call it; every parameter description is at most 100 characters.
|
|
463
484
|
* Every tool declares at least one required parameter, because the streamed tool-call parser of
|
|
464
|
-
* Ollama 0.34.4 rejects a call to a tool that declares no parameter. `look`
|
|
465
|
-
*
|
|
466
|
-
*
|
|
485
|
+
* Ollama 0.34.4 rejects a call to a tool that declares no parameter. `look`, `read`, and `plain` take
|
|
486
|
+
* `search` and `offset`; an element's own reading is `BrowserElementInterface.read`.
|
|
487
|
+
* `look`, `read`, `plain`, and `journeys` annotate `pure` and
|
|
467
488
|
* `untrusted`, `wait` and `tabs` annotate `pure`, and the rest carry no annotation. `type` takes
|
|
468
489
|
* `secret` beside `ref`, `text`, and `submit`. The journey tools `record`, `save`, `journeys`,
|
|
469
490
|
* `edit`, and `replay` are advertised only by a toolset constructed with `journeys`.
|
|
491
|
+
* `wait` takes `absent` beside `text` and `timeout`.
|
|
470
492
|
*/
|
|
471
493
|
var BROWSER_TOOL_COPY = Object.freeze({
|
|
472
494
|
look: Object.freeze({
|
|
@@ -474,11 +496,17 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
474
496
|
description: "Shows the page's text and the elements you can act on, each with a reference like e4. Call it first and after the page changes.",
|
|
475
497
|
parameters: Object.freeze({
|
|
476
498
|
type: "object",
|
|
477
|
-
properties: Object.freeze({
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
499
|
+
properties: Object.freeze({
|
|
500
|
+
search: Object.freeze({
|
|
501
|
+
type: "string",
|
|
502
|
+
description: "Words to find on this page; matching elements come first."
|
|
503
|
+
}),
|
|
504
|
+
offset: Object.freeze({
|
|
505
|
+
type: "integer",
|
|
506
|
+
description: "The character to continue from, as the last reply names. Default: 0."
|
|
507
|
+
})
|
|
508
|
+
}),
|
|
509
|
+
required: Object.freeze(["search"])
|
|
482
510
|
}),
|
|
483
511
|
annotations: Object.freeze({
|
|
484
512
|
pure: true,
|
|
@@ -487,20 +515,42 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
487
515
|
}),
|
|
488
516
|
read: Object.freeze({
|
|
489
517
|
name: "read",
|
|
490
|
-
description: "Reads the page
|
|
518
|
+
description: "Reads the page as Markdown, with headings, tables, and link addresses. Call it to learn a fact; continue with the offset a cut result names.",
|
|
519
|
+
parameters: Object.freeze({
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: Object.freeze({
|
|
522
|
+
search: Object.freeze({
|
|
523
|
+
type: "string",
|
|
524
|
+
description: "Words to find on this page; the lines that share them come first."
|
|
525
|
+
}),
|
|
526
|
+
offset: Object.freeze({
|
|
527
|
+
type: "integer",
|
|
528
|
+
description: "The character to continue from, as the last reply names. Default: 0."
|
|
529
|
+
})
|
|
530
|
+
}),
|
|
531
|
+
required: Object.freeze(["search"])
|
|
532
|
+
}),
|
|
533
|
+
annotations: Object.freeze({
|
|
534
|
+
pure: true,
|
|
535
|
+
untrusted: true
|
|
536
|
+
})
|
|
537
|
+
}),
|
|
538
|
+
plain: Object.freeze({
|
|
539
|
+
name: "plain",
|
|
540
|
+
description: "Reads the page as plain text, without Markdown, link addresses, or image text. Call it for words to pass to wait or type.",
|
|
491
541
|
parameters: Object.freeze({
|
|
492
542
|
type: "object",
|
|
493
543
|
properties: Object.freeze({
|
|
494
|
-
|
|
544
|
+
search: Object.freeze({
|
|
495
545
|
type: "string",
|
|
496
|
-
description: "
|
|
546
|
+
description: "Words to find on this page; the lines that share them come first."
|
|
497
547
|
}),
|
|
498
548
|
offset: Object.freeze({
|
|
499
549
|
type: "integer",
|
|
500
550
|
description: "The character to continue from, as the last reply names. Default: 0."
|
|
501
551
|
})
|
|
502
552
|
}),
|
|
503
|
-
required: Object.freeze(["
|
|
553
|
+
required: Object.freeze(["search"])
|
|
504
554
|
}),
|
|
505
555
|
annotations: Object.freeze({
|
|
506
556
|
pure: true,
|
|
@@ -571,7 +621,7 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
571
621
|
}),
|
|
572
622
|
wait: Object.freeze({
|
|
573
623
|
name: "wait",
|
|
574
|
-
description: "Waits for
|
|
624
|
+
description: "Waits for text to appear; set absent to true to wait for it to leave.",
|
|
575
625
|
parameters: Object.freeze({
|
|
576
626
|
type: "object",
|
|
577
627
|
properties: Object.freeze({
|
|
@@ -579,6 +629,10 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
579
629
|
type: "string",
|
|
580
630
|
description: "The text to wait for."
|
|
581
631
|
}),
|
|
632
|
+
absent: Object.freeze({
|
|
633
|
+
type: "boolean",
|
|
634
|
+
description: "True to wait for the text to leave the page."
|
|
635
|
+
}),
|
|
582
636
|
timeout: Object.freeze({
|
|
583
637
|
type: "integer",
|
|
584
638
|
description: "The most seconds to wait, at most 30. Default: 5."
|
|
@@ -611,11 +665,11 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
611
665
|
description: "Lists the open tabs; the current one is marked.",
|
|
612
666
|
parameters: Object.freeze({
|
|
613
667
|
type: "object",
|
|
614
|
-
properties: Object.freeze({
|
|
668
|
+
properties: Object.freeze({ search: Object.freeze({
|
|
615
669
|
type: "string",
|
|
616
|
-
description: "
|
|
670
|
+
description: "Words to find; matching tabs come first."
|
|
617
671
|
}) }),
|
|
618
|
-
required: Object.freeze(["
|
|
672
|
+
required: Object.freeze(["search"])
|
|
619
673
|
}),
|
|
620
674
|
annotations: Object.freeze({ pure: true })
|
|
621
675
|
}),
|
|
@@ -661,16 +715,16 @@ var BROWSER_TOOL_COPY = Object.freeze({
|
|
|
661
715
|
parameters: Object.freeze({
|
|
662
716
|
type: "object",
|
|
663
717
|
properties: Object.freeze({
|
|
664
|
-
|
|
718
|
+
search: Object.freeze({
|
|
665
719
|
type: "string",
|
|
666
|
-
description: "
|
|
720
|
+
description: "Words to find; matching journeys come first."
|
|
667
721
|
}),
|
|
668
722
|
offset: Object.freeze({
|
|
669
723
|
type: "integer",
|
|
670
724
|
description: "The character to continue from, as the last reply names. Default: 0."
|
|
671
725
|
})
|
|
672
726
|
}),
|
|
673
|
-
required: Object.freeze(["
|
|
727
|
+
required: Object.freeze(["search"])
|
|
674
728
|
}),
|
|
675
729
|
annotations: Object.freeze({
|
|
676
730
|
pure: true,
|
|
@@ -1627,17 +1681,265 @@ function filterBrowserOutline(nodes, query) {
|
|
|
1627
1681
|
return nodes.filter((node) => !node.ignored && !BROWSER_OUTLINE_OMITTED_ROLES.has(node.role ?? "") && (query.role === void 0 || node.role === query.role) && (query.name === void 0 || (query.exact === true ? normalizeBrowserName(node.name ?? "") === normalizeBrowserName(query.name) : normalizeBrowserName(node.name ?? "").toLowerCase().includes(normalizeBrowserName(query.name).toLowerCase()))));
|
|
1628
1682
|
}
|
|
1629
1683
|
/**
|
|
1684
|
+
* Renders one outline row: the node's reference, its role, its quoted accessible name, and the
|
|
1685
|
+
* states it carries.
|
|
1686
|
+
*
|
|
1687
|
+
* @remarks
|
|
1688
|
+
* After the name the row appends `value="V"` when the node carries a non-empty value,
|
|
1689
|
+
* `pressed=true|false|mixed`, `expanded=true|false`, and `selected=true|false` when present,
|
|
1690
|
+
* `[checked]` when checked, `[disabled]` when disabled, and `[tool=NAME]` for a page tool's form,
|
|
1691
|
+
* in that order. A node without a reference renders without one.
|
|
1692
|
+
*
|
|
1693
|
+
* @param node - The outline node
|
|
1694
|
+
* @returns The rendered row
|
|
1695
|
+
*
|
|
1696
|
+
* @example
|
|
1697
|
+
* ```ts
|
|
1698
|
+
* import { renderBrowserOutlineRow } from '@orkestrel/browser'
|
|
1699
|
+
*
|
|
1700
|
+
* renderBrowserOutlineRow({
|
|
1701
|
+
* id: '4',
|
|
1702
|
+
* parent: undefined,
|
|
1703
|
+
* children: [],
|
|
1704
|
+
* backend: undefined,
|
|
1705
|
+
* frame: undefined,
|
|
1706
|
+
* ignored: false,
|
|
1707
|
+
* role: 'checkbox',
|
|
1708
|
+
* name: ' Gift wrap ',
|
|
1709
|
+
* description: undefined,
|
|
1710
|
+
* value: undefined,
|
|
1711
|
+
* properties: { checked: 'true' },
|
|
1712
|
+
* session: 'main',
|
|
1713
|
+
* reference: 'e4',
|
|
1714
|
+
* }) // 'e4 checkbox "Gift wrap" [checked]'
|
|
1715
|
+
* ```
|
|
1716
|
+
*/
|
|
1717
|
+
function renderBrowserOutlineRow(node) {
|
|
1718
|
+
const name = JSON.stringify(normalizeBrowserName(node.name ?? ""));
|
|
1719
|
+
const role = node.role ?? "unknown";
|
|
1720
|
+
let row = node.reference === void 0 ? `${role} ${name}` : `${node.reference} ${role} ${name}`;
|
|
1721
|
+
if (node.value !== void 0 && node.value !== "") row += ` value=${JSON.stringify(String(node.value))}`;
|
|
1722
|
+
for (const key of [
|
|
1723
|
+
"pressed",
|
|
1724
|
+
"expanded",
|
|
1725
|
+
"selected"
|
|
1726
|
+
]) {
|
|
1727
|
+
const value = node.properties[key];
|
|
1728
|
+
if ((0, _orkestrel_contract.isString)(value) || (0, _orkestrel_contract.isBoolean)(value)) row += ` ${key}=${String(value)}`;
|
|
1729
|
+
}
|
|
1730
|
+
if (node.properties["checked"] === true || node.properties["checked"] === "true") row += " [checked]";
|
|
1731
|
+
if (node.properties["disabled"] === true) row += " [disabled]";
|
|
1732
|
+
if (node.tool !== void 0) row += ` [tool=${node.tool}]`;
|
|
1733
|
+
return row;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Returns the referenced outline nodes whose role and accessible name share the most words with a
|
|
1737
|
+
* search, in document order.
|
|
1738
|
+
*
|
|
1739
|
+
* @remarks
|
|
1740
|
+
* The search and each node's role and name are lowercased and split into whole words by
|
|
1741
|
+
* `BROWSER_SEARCH_PATTERN`, so a word shorter than 3 letters or digits never counts, and words
|
|
1742
|
+
* compare whole, with no substring or prefix match. A node scores the number of distinct search
|
|
1743
|
+
* words equal to one of its words, and every node with the highest score above 0 matches. Only a
|
|
1744
|
+
* node an outline renders as a referenced row takes part: one that is not ignored, carries a
|
|
1745
|
+
* reference, and whose role is not omitted, a heading, or text.
|
|
1746
|
+
*
|
|
1747
|
+
* @param nodes - Ordered accessibility rows
|
|
1748
|
+
* @param search - The words to match
|
|
1749
|
+
* @returns The best-matching nodes in document order; empty when no node shares a word
|
|
1750
|
+
*
|
|
1751
|
+
* @example
|
|
1752
|
+
* ```ts
|
|
1753
|
+
* import { scanBrowserOutline } from '@orkestrel/browser'
|
|
1754
|
+
*
|
|
1755
|
+
* const node = {
|
|
1756
|
+
* parent: undefined,
|
|
1757
|
+
* children: [],
|
|
1758
|
+
* backend: undefined,
|
|
1759
|
+
* frame: undefined,
|
|
1760
|
+
* ignored: false,
|
|
1761
|
+
* description: undefined,
|
|
1762
|
+
* value: undefined,
|
|
1763
|
+
* properties: {},
|
|
1764
|
+
* session: 'main',
|
|
1765
|
+
* }
|
|
1766
|
+
* const nodes = [
|
|
1767
|
+
* { ...node, id: '1', role: 'button', name: 'Close', reference: 'e1' },
|
|
1768
|
+
* { ...node, id: '2', role: 'button', name: 'Archive', reference: 'e2' },
|
|
1769
|
+
* ]
|
|
1770
|
+
* scanBrowserOutline(nodes, 'archive dialog button').map((match) => match.reference) // ['e2']
|
|
1771
|
+
* ```
|
|
1772
|
+
*/
|
|
1773
|
+
function scanBrowserOutline(nodes, search) {
|
|
1774
|
+
const words = collectBrowserWords(search);
|
|
1775
|
+
if (words.size === 0) return [];
|
|
1776
|
+
let best = 0;
|
|
1777
|
+
const scored = [];
|
|
1778
|
+
for (const node of nodes) {
|
|
1779
|
+
const role = node.role ?? "";
|
|
1780
|
+
if (node.ignored || node.reference === void 0 || BROWSER_OUTLINE_OMITTED_ROLES.has(role) || role === "heading" || role === "StaticText") continue;
|
|
1781
|
+
const own = collectBrowserWords(`${role} ${node.name ?? ""}`);
|
|
1782
|
+
let score = 0;
|
|
1783
|
+
for (const word of words) if (own.has(word)) score += 1;
|
|
1784
|
+
if (score === 0) continue;
|
|
1785
|
+
best = Math.max(best, score);
|
|
1786
|
+
scored.push({
|
|
1787
|
+
node,
|
|
1788
|
+
score
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
return scored.filter((entry) => entry.score === best).map((entry) => entry.node);
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Collects distinct lowercase words of at least 3 letters or digits.
|
|
1795
|
+
*
|
|
1796
|
+
* @param text - Text to scan
|
|
1797
|
+
* @returns The searchable whole words
|
|
1798
|
+
*
|
|
1799
|
+
* @example
|
|
1800
|
+
* ```ts
|
|
1801
|
+
* import { collectBrowserWords } from '@orkestrel/browser'
|
|
1802
|
+
* [...collectBrowserWords('The cart, the bag')] // ['the', 'cart', 'bag']
|
|
1803
|
+
* ```
|
|
1804
|
+
*/
|
|
1805
|
+
function collectBrowserWords(text) {
|
|
1806
|
+
return new Set(Array.from(text.toLowerCase().matchAll(BROWSER_SEARCH_PATTERN), (match) => match[0]));
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Matches the lines sharing the most distinct search words, in document order.
|
|
1810
|
+
*
|
|
1811
|
+
* @param text - The complete projection
|
|
1812
|
+
* @param search - Words to match, using the outline's word rule
|
|
1813
|
+
* @returns The best-scoring lines and their original UTF-16 offsets; empty without a shared word
|
|
1814
|
+
*
|
|
1815
|
+
* @example
|
|
1816
|
+
* ```ts
|
|
1817
|
+
* import { scanBrowserText } from '@orkestrel/browser'
|
|
1818
|
+
* scanBrowserText('Cart\nBlue kettle', 'kettle') // [{ offset: 5, text: 'Blue kettle' }]
|
|
1819
|
+
* ```
|
|
1820
|
+
*/
|
|
1821
|
+
function scanBrowserText(text, search) {
|
|
1822
|
+
const words = collectBrowserWords(search);
|
|
1823
|
+
if (words.size === 0) return [];
|
|
1824
|
+
let best = 0;
|
|
1825
|
+
const scored = [];
|
|
1826
|
+
for (const line of text.matchAll(/[^\r\n]+/g)) {
|
|
1827
|
+
const own = collectBrowserWords(line[0]);
|
|
1828
|
+
let score = 0;
|
|
1829
|
+
for (const word of words) if (own.has(word)) score += 1;
|
|
1830
|
+
if (score === 0) continue;
|
|
1831
|
+
best = Math.max(best, score);
|
|
1832
|
+
scored.push({
|
|
1833
|
+
match: {
|
|
1834
|
+
offset: line.index,
|
|
1835
|
+
text: line[0]
|
|
1836
|
+
},
|
|
1837
|
+
score
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
return scored.filter((entry) => entry.score === best).map((entry) => entry.match);
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Renders matching rows in at most half the available reply room.
|
|
1844
|
+
*
|
|
1845
|
+
* @remarks
|
|
1846
|
+
* An oversized first row is cut with an ellipsis without splitting a surrogate pair, reserving
|
|
1847
|
+
* room for the first later row that can fit beside it only when the cut keeps the leading token
|
|
1848
|
+
* through its first space. Otherwise it cuts without reserving. Later rows that do not fit are
|
|
1849
|
+
* skipped. An empty collection or a cut with no character before the ellipsis produces no block.
|
|
1850
|
+
*
|
|
1851
|
+
* @param heading - The match count and search, ending with a colon
|
|
1852
|
+
* @param rows - Matching rows in document order
|
|
1853
|
+
* @param room - Characters available in the reply
|
|
1854
|
+
* @returns The bounded block with a trailing blank line, or an empty string
|
|
1855
|
+
*
|
|
1856
|
+
* @example
|
|
1857
|
+
* ```ts
|
|
1858
|
+
* import { renderBrowserMatches } from '@orkestrel/browser'
|
|
1859
|
+
* renderBrowserMatches('Matches:', ['[5] Cart'], 100) // 'Matches:\n[5] Cart\n\n'
|
|
1860
|
+
* ```
|
|
1861
|
+
*/
|
|
1862
|
+
function renderBrowserMatches(heading, rows, room) {
|
|
1863
|
+
const half = Math.floor(room / 2);
|
|
1864
|
+
let text = `${heading}\n`;
|
|
1865
|
+
let count = 0;
|
|
1866
|
+
for (const row of rows) {
|
|
1867
|
+
const space = half - text.length - 2;
|
|
1868
|
+
if (space < 1) break;
|
|
1869
|
+
if (row.length > space) {
|
|
1870
|
+
if (count > 0) continue;
|
|
1871
|
+
let end = space - 1;
|
|
1872
|
+
const later = rows.slice(1).find((candidate) => candidate.length + 2 <= space);
|
|
1873
|
+
if (later !== void 0 && end - later.length - 1 >= row.indexOf(" ") + 1) end -= later.length + 1;
|
|
1874
|
+
const last = row.charCodeAt(end - 1);
|
|
1875
|
+
if (last >= 55296 && last <= 56319) end -= 1;
|
|
1876
|
+
if (end < 1) break;
|
|
1877
|
+
text += `${row.slice(0, end)}…\n`;
|
|
1878
|
+
} else text += `${row}\n`;
|
|
1879
|
+
count += 1;
|
|
1880
|
+
}
|
|
1881
|
+
return count === 0 ? "" : `${text}\n`;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1630
1884
|
* Renders document-order text and referenced elements with a bounded element count.
|
|
1885
|
+
*
|
|
1886
|
+
* @remarks
|
|
1887
|
+
* Each referenced row renders through `renderBrowserOutlineRow`, and the closing
|
|
1888
|
+
* `(COUNT of TOTAL elements)` line counts the rows included and available. `matches` holds the
|
|
1889
|
+
* rows `scanBrowserOutline` returns for `search`, and `focus` the last referenced row whose node
|
|
1890
|
+
* carries a `focused` property of `true`; both reach past `limit`.
|
|
1891
|
+
*
|
|
1631
1892
|
* @param url - Document address
|
|
1632
1893
|
* @param title - Document title
|
|
1633
1894
|
* @param nodes - Ordered accessibility rows
|
|
1634
1895
|
* @param limit - Maximum referenced rows to include
|
|
1635
|
-
* @
|
|
1896
|
+
* @param search - The words whose best-matching rows `matches` lists. Default: none, which lists
|
|
1897
|
+
* none
|
|
1898
|
+
* @returns Outline text, element counts, matching rows, and the focused row
|
|
1899
|
+
*
|
|
1900
|
+
* @example
|
|
1901
|
+
* ```ts
|
|
1902
|
+
* import { renderBrowserOutline } from '@orkestrel/browser'
|
|
1903
|
+
*
|
|
1904
|
+
* const node = {
|
|
1905
|
+
* parent: undefined,
|
|
1906
|
+
* children: [],
|
|
1907
|
+
* backend: undefined,
|
|
1908
|
+
* frame: undefined,
|
|
1909
|
+
* ignored: false,
|
|
1910
|
+
* description: undefined,
|
|
1911
|
+
* value: undefined,
|
|
1912
|
+
* session: 'main',
|
|
1913
|
+
* }
|
|
1914
|
+
* const outline = renderBrowserOutline(
|
|
1915
|
+
* 'https://shop.example/cart',
|
|
1916
|
+
* 'Cart',
|
|
1917
|
+
* [
|
|
1918
|
+
* { ...node, id: '1', role: 'button', name: 'Close', properties: {}, reference: 'e1' },
|
|
1919
|
+
* { ...node, id: '2', role: 'button', name: 'Archive', properties: { focused: true }, reference: 'e2' },
|
|
1920
|
+
* ],
|
|
1921
|
+
* 1,
|
|
1922
|
+
* 'archive button',
|
|
1923
|
+
* )
|
|
1924
|
+
* outline.text // 'page "Cart" https://shop.example/cart\ne1 button "Close"\n(1 of 2 elements)'
|
|
1925
|
+
* outline.matches // ['e2 button "Archive"']
|
|
1926
|
+
* outline.focus // 'e2 button "Archive"'
|
|
1927
|
+
* ```
|
|
1636
1928
|
*/
|
|
1637
|
-
function renderBrowserOutline(url, title, nodes, limit) {
|
|
1929
|
+
function renderBrowserOutline(url, title, nodes, limit, search) {
|
|
1638
1930
|
const rows = [`page ${JSON.stringify(title)} ${url}`];
|
|
1931
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1932
|
+
for (const node of nodes) {
|
|
1933
|
+
let parents = sessions.get(node.session);
|
|
1934
|
+
if (parents === void 0) {
|
|
1935
|
+
parents = /* @__PURE__ */ new Map();
|
|
1936
|
+
sessions.set(node.session, parents);
|
|
1937
|
+
}
|
|
1938
|
+
if (!parents.has(node.id)) parents.set(node.id, node);
|
|
1939
|
+
}
|
|
1639
1940
|
let count = 0;
|
|
1640
1941
|
let total = 0;
|
|
1942
|
+
let focus;
|
|
1641
1943
|
for (const node of nodes) {
|
|
1642
1944
|
if (node.ignored || BROWSER_OUTLINE_OMITTED_ROLES.has(node.role ?? "")) continue;
|
|
1643
1945
|
const name = normalizeBrowserName(node.name ?? "");
|
|
@@ -1646,20 +1948,16 @@ function renderBrowserOutline(url, title, nodes, limit) {
|
|
|
1646
1948
|
continue;
|
|
1647
1949
|
}
|
|
1648
1950
|
if (node.role === "StaticText") {
|
|
1649
|
-
const parent =
|
|
1951
|
+
const parent = node.parent === void 0 ? void 0 : sessions.get(node.session)?.get(node.parent);
|
|
1650
1952
|
if (name !== "" && name !== normalizeBrowserName(parent?.name ?? "")) rows.push(name);
|
|
1651
1953
|
continue;
|
|
1652
1954
|
}
|
|
1653
1955
|
if (node.reference === void 0) continue;
|
|
1654
1956
|
total += 1;
|
|
1957
|
+
if (node.properties["focused"] === true) focus = node;
|
|
1655
1958
|
if (count >= limit) continue;
|
|
1656
1959
|
count += 1;
|
|
1657
|
-
|
|
1658
|
-
if (node.value !== void 0 && node.value !== "") row += ` value=${JSON.stringify(String(node.value))}`;
|
|
1659
|
-
if (node.properties["checked"] === true || node.properties["checked"] === "true") row += " [checked]";
|
|
1660
|
-
if (node.properties["disabled"] === true) row += " [disabled]";
|
|
1661
|
-
if (node.tool !== void 0) row += ` [tool=${node.tool}]`;
|
|
1662
|
-
rows.push(row);
|
|
1960
|
+
rows.push(renderBrowserOutlineRow(node));
|
|
1663
1961
|
}
|
|
1664
1962
|
rows.push(`(${count} of ${total} elements)`);
|
|
1665
1963
|
return {
|
|
@@ -1667,7 +1965,9 @@ function renderBrowserOutline(url, title, nodes, limit) {
|
|
|
1667
1965
|
title,
|
|
1668
1966
|
text: rows.join("\n"),
|
|
1669
1967
|
count,
|
|
1670
|
-
total
|
|
1968
|
+
total,
|
|
1969
|
+
matches: search === void 0 ? [] : scanBrowserOutline(nodes, search).map(renderBrowserOutlineRow),
|
|
1970
|
+
focus: focus === void 0 ? void 0 : renderBrowserOutlineRow(focus)
|
|
1671
1971
|
};
|
|
1672
1972
|
}
|
|
1673
1973
|
/**
|
|
@@ -1748,27 +2048,27 @@ function renderBrowserToolOutput(value) {
|
|
|
1748
2048
|
return (rendered.success ? rendered.value ?? String(value) : "[Unserializable tool output]").slice(0, BROWSER_REGISTRY_OUTPUT_LIMIT);
|
|
1749
2049
|
}
|
|
1750
2050
|
/**
|
|
1751
|
-
* Derives the advertised input schema from an authored one, adding a required `
|
|
2051
|
+
* Derives the advertised input schema from an authored one, adding a required `purpose` parameter when the schema requires nothing.
|
|
1752
2052
|
* @param schema - Authored input schema, or undefined for a parameterless tool
|
|
1753
|
-
* @returns An advertised schema, or undefined when the authored `
|
|
1754
|
-
* @remarks The caller strips only a synthetic `
|
|
2053
|
+
* @returns An advertised schema, or undefined when the authored `purpose` parameter is optional
|
|
2054
|
+
* @remarks The caller strips only a synthetic `purpose` parameter before invoking the page tool.
|
|
1755
2055
|
*/
|
|
1756
2056
|
function deriveBrowserToolSchema(schema) {
|
|
1757
2057
|
const properties = (0, _orkestrel_contract.isRecord)(schema?.["properties"]) ? schema["properties"] : {};
|
|
1758
2058
|
const required = (0, _orkestrel_contract.isArray)(schema?.["required"]) ? schema["required"].filter(_orkestrel_contract.isString) : [];
|
|
1759
|
-
if (Object.hasOwn(properties, "
|
|
2059
|
+
if (Object.hasOwn(properties, "purpose") && !required.includes("purpose")) return void 0;
|
|
1760
2060
|
if (required.length > 0) return schema;
|
|
1761
2061
|
return {
|
|
1762
2062
|
...schema,
|
|
1763
2063
|
type: "object",
|
|
1764
2064
|
properties: {
|
|
1765
2065
|
...properties,
|
|
1766
|
-
|
|
2066
|
+
purpose: {
|
|
1767
2067
|
type: "string",
|
|
1768
2068
|
description: "Describe the purpose of this action."
|
|
1769
2069
|
}
|
|
1770
2070
|
},
|
|
1771
|
-
required: ["
|
|
2071
|
+
required: ["purpose"]
|
|
1772
2072
|
};
|
|
1773
2073
|
}
|
|
1774
2074
|
/**
|
|
@@ -1780,8 +2080,8 @@ function deriveBrowserToolSchema(schema) {
|
|
|
1780
2080
|
* units, one fewer when the last would split a surrogate pair (so a limit of 1 before a pair keeps
|
|
1781
2081
|
* nothing), followed by `\n[characters 0–END of TOTAL; FOOTER]`. The footer has no default, so
|
|
1782
2082
|
* every caller states what the cut result's reader does next: the toolset passes
|
|
1783
|
-
* `BROWSER_TOOL_VIEW_FOOTER` for
|
|
1784
|
-
* any other.
|
|
2083
|
+
* `BROWSER_TOOL_VIEW_FOOTER` for an action or `dialog` receipt that carries a view and
|
|
2084
|
+
* `BROWSER_TOOL_CUT_FOOTER` for any other result, including `look` and `read` results.
|
|
1785
2085
|
*
|
|
1786
2086
|
* @param text - The page-authored or composed string
|
|
1787
2087
|
* @param limit - The most characters kept before the footer, a positive integer
|
|
@@ -1818,8 +2118,8 @@ function boundBrowserText(text, limit, footer) {
|
|
|
1818
2118
|
* ```ts
|
|
1819
2119
|
* import { BROWSER_TOOL_COPY, validateBrowserToolArguments } from '@orkestrel/browser'
|
|
1820
2120
|
*
|
|
1821
|
-
* validateBrowserToolArguments(BROWSER_TOOL_COPY.look, {
|
|
1822
|
-
* // throws 'The look tool takes no ref parameter; call look with
|
|
2121
|
+
* validateBrowserToolArguments(BROWSER_TOOL_COPY.look, { search: 'cart', ref: 'e1' })
|
|
2122
|
+
* // throws 'The look tool takes no ref parameter; call look with search and offset.'
|
|
1823
2123
|
* ```
|
|
1824
2124
|
*/
|
|
1825
2125
|
function validateBrowserToolArguments(definition, args) {
|
|
@@ -3599,7 +3899,7 @@ function renderBrowserJourney(journey) {
|
|
|
3599
3899
|
return `${step.id} type ${secret ? "(secret)" : JSON.stringify(text)}${suffix} into ${name}${step.arguments["submit"] === true ? ", submit" : ""}`;
|
|
3600
3900
|
}
|
|
3601
3901
|
case "press": return `${step.id} press ${text}${suffix}`;
|
|
3602
|
-
case "wait": return `${step.id} wait ${JSON.stringify(text)}${suffix}`;
|
|
3902
|
+
case "wait": return `${step.id} wait ${JSON.stringify(text)}${suffix}${step.arguments["absent"] === true ? ", absent" : ""}`;
|
|
3603
3903
|
case "dialog": return `${step.id} dialog ${step.arguments["accept"] === true ? "accept" : "dismiss"}${binding === void 0 ? "" : ` ${JSON.stringify(text)}${suffix}`}`;
|
|
3604
3904
|
case "switch": return `${step.id} switch ${JSON.stringify(step.tab?.title)} ${step.tab?.url}`;
|
|
3605
3905
|
case "unresolved": return `${step.id} unresolved: ${step.gap}`;
|
|
@@ -3907,46 +4207,55 @@ function buildBrowserJourney(steps, options) {
|
|
|
3907
4207
|
//#endregion
|
|
3908
4208
|
//#region src/core/compilers.ts
|
|
3909
4209
|
/**
|
|
3910
|
-
* Compiles a
|
|
4210
|
+
* Compiles a wait woken by mutations and finished transitions and animations, with one deadline and explicit disconnect ownership.
|
|
4211
|
+
* @remarks
|
|
4212
|
+
* `transitionend` and `animationend` inside a shadow root are not composed in Chromium
|
|
4213
|
+
* 154.0.4258.53, so this document listener does not see them. Mutations inside that root are
|
|
4214
|
+
* not observed either.
|
|
3911
4215
|
* @param deadline - Maximum time in milliseconds
|
|
3912
4216
|
* @param key - Isolated-world property owning this observer
|
|
3913
|
-
* @param predicate - Optional expression checked immediately and after
|
|
4217
|
+
* @param predicate - Optional expression checked immediately and on the task after a mutation batch or a `BROWSER_WAIT_EVENTS` event
|
|
3914
4218
|
* @returns Promise expression resolving true on a match or change, false at the deadline
|
|
3915
4219
|
*/
|
|
3916
4220
|
function compileQueryWaitExpression(deadline, key, predicate) {
|
|
3917
4221
|
return `new Promise((resolve) => {
|
|
3918
|
-
let
|
|
4222
|
+
let task
|
|
3919
4223
|
let timer
|
|
3920
4224
|
let observer
|
|
4225
|
+
const events = new AbortController()
|
|
3921
4226
|
const finish = (value) => {
|
|
3922
4227
|
observer?.disconnect()
|
|
3923
|
-
|
|
4228
|
+
events.abort()
|
|
4229
|
+
if (task !== undefined) clearTimeout(task)
|
|
3924
4230
|
clearTimeout(timer)
|
|
3925
4231
|
delete globalThis[${JSON.stringify(key)}]
|
|
3926
4232
|
resolve(value)
|
|
3927
4233
|
}
|
|
3928
4234
|
const check = () => {
|
|
3929
|
-
|
|
4235
|
+
task = undefined
|
|
3930
4236
|
if (${predicate ?? "true"}) finish(true)
|
|
3931
4237
|
}
|
|
3932
4238
|
globalThis[${JSON.stringify(key)}] = () => finish(false)
|
|
3933
|
-
|
|
3934
|
-
if (
|
|
3935
|
-
}
|
|
4239
|
+
const schedule = () => {
|
|
4240
|
+
if (task === undefined) task = setTimeout(check, 0)
|
|
4241
|
+
}
|
|
4242
|
+
observer = new MutationObserver(schedule)
|
|
3936
4243
|
observer.observe(document, { childList: true, attributes: true, characterData: true, subtree: true })
|
|
4244
|
+
for (const name of ${JSON.stringify(BROWSER_WAIT_EVENTS)}) document.addEventListener(name, schedule, { capture: true, signal: events.signal })
|
|
3937
4245
|
timer = setTimeout(() => finish(false), ${JSON.stringify(deadline)})
|
|
3938
4246
|
${predicate === void 0 ? "" : "check()"}
|
|
3939
4247
|
})`;
|
|
3940
4248
|
}
|
|
3941
4249
|
/**
|
|
3942
|
-
* Compiles a visible
|
|
4250
|
+
* Compiles a wait for the presence or absence of visible text, coalesced by tasks.
|
|
3943
4251
|
* @param text - Text to find in the main document body
|
|
3944
4252
|
* @param deadline - Maximum time in milliseconds
|
|
3945
4253
|
* @param key - Isolated-world property owning this observer
|
|
4254
|
+
* @param absent - If `true`, resolves when the body's `innerText` lacks `text`; if `false`, when it contains it. Default: `false`
|
|
3946
4255
|
* @returns Mutation-driven promise expression with one deadline
|
|
3947
4256
|
*/
|
|
3948
|
-
function compileTextWaitExpression(text, deadline, key) {
|
|
3949
|
-
return compileQueryWaitExpression(deadline, key,
|
|
4257
|
+
function compileTextWaitExpression(text, deadline, key, absent = false) {
|
|
4258
|
+
return compileQueryWaitExpression(deadline, key, `${absent ? "!" : ""}(document.body?.innerText ?? '').includes(${JSON.stringify(text)})`);
|
|
3950
4259
|
}
|
|
3951
4260
|
/**
|
|
3952
4261
|
* Compiles text selection or select-option assignment against the resolved element.
|
|
@@ -4258,20 +4567,337 @@ ${expression}
|
|
|
4258
4567
|
); const s = JSON.stringify(r); if (typeof s === 'string' && s.length > ${limit}) throw new Error(${JSON.stringify(BROWSER_RESULT_LIMIT_SENTINEL_PREFIX)} + s.length); return r })()`;
|
|
4259
4568
|
}
|
|
4260
4569
|
/**
|
|
4261
|
-
* Compiles the in-page function that reads the document's URL, title, and
|
|
4570
|
+
* Compiles the in-page function that reads the document's URL, title, and rendered HTML.
|
|
4262
4571
|
*
|
|
4263
4572
|
* @remarks
|
|
4264
|
-
* The
|
|
4265
|
-
*
|
|
4266
|
-
*
|
|
4267
|
-
*
|
|
4573
|
+
* The optional root defaults to `document.documentElement`. {@link BrowserReadingInput}
|
|
4574
|
+
* defines its inert capture, lowering, redaction, and limits. A missing root yields empty HTML.
|
|
4575
|
+
* Call it inside {@link compileGuardedEvaluateExpression} to bound the final serialized result
|
|
4576
|
+
* under {@link BROWSER_RESULT_LIMIT}.
|
|
4268
4577
|
*
|
|
4269
4578
|
* @returns A function declaration source
|
|
4270
4579
|
*/
|
|
4271
4580
|
function compileReadFunction() {
|
|
4272
|
-
return `function() {
|
|
4273
|
-
const
|
|
4274
|
-
return { url:
|
|
4581
|
+
return `function(node = document.documentElement) {
|
|
4582
|
+
const owner = node?.ownerDocument ?? document
|
|
4583
|
+
if (!node) return { url: owner.URL, title: owner.title, html: '' }
|
|
4584
|
+
const capture = { url: owner.URL, title: owner.title, html: '' }
|
|
4585
|
+
const view = owner.defaultView
|
|
4586
|
+
const rendered = view !== null && node.isConnected
|
|
4587
|
+
const inert = owner.implementation.createHTMLDocument('')
|
|
4588
|
+
let root = inert.importNode(node, true)
|
|
4589
|
+
const pending = [{ live: node, twin: root, visited: false }]
|
|
4590
|
+
if (rendered) {
|
|
4591
|
+
let child = node
|
|
4592
|
+
for (
|
|
4593
|
+
let parent = node;
|
|
4594
|
+
parent?.ownerDocument === owner;
|
|
4595
|
+
parent = parent.assignedSlot ?? parent.parentElement ?? (parent.parentNode instanceof view.ShadowRoot ? parent.parentNode.host : null)
|
|
4596
|
+
) {
|
|
4597
|
+
const style = view.getComputedStyle(parent)
|
|
4598
|
+
const outside =
|
|
4599
|
+
parent !== node &&
|
|
4600
|
+
(style.contentVisibility === 'hidden' ||
|
|
4601
|
+
['canvas', 'video', 'audio'].includes(parent.localName) ||
|
|
4602
|
+
parent instanceof view.HTMLInputElement ||
|
|
4603
|
+
parent instanceof view.HTMLTextAreaElement ||
|
|
4604
|
+
parent instanceof view.HTMLOptionElement ||
|
|
4605
|
+
(parent.namespaceURI === 'http://www.w3.org/2000/svg' &&
|
|
4606
|
+
parent.localName === 'switch' &&
|
|
4607
|
+
child !== (Array.from(parent.children).find((candidate) => candidate.getClientRects().length > 0) ?? parent.firstElementChild)) ||
|
|
4608
|
+
((parent instanceof view.HTMLSelectElement || parent instanceof view.HTMLOptGroupElement) &&
|
|
4609
|
+
!(node instanceof view.HTMLOptionElement || node instanceof view.HTMLOptGroupElement)) ||
|
|
4610
|
+
[
|
|
4611
|
+
'script',
|
|
4612
|
+
'style',
|
|
4613
|
+
'template',
|
|
4614
|
+
'frame',
|
|
4615
|
+
'frameset',
|
|
4616
|
+
'iframe',
|
|
4617
|
+
'object',
|
|
4618
|
+
'embed',
|
|
4619
|
+
'applet',
|
|
4620
|
+
'noscript',
|
|
4621
|
+
'meta',
|
|
4622
|
+
'link',
|
|
4623
|
+
'base',
|
|
4624
|
+
'math',
|
|
4625
|
+
].includes(parent.localName) ||
|
|
4626
|
+
(parent.localName === 'details' &&
|
|
4627
|
+
!parent.hasAttribute('open') &&
|
|
4628
|
+
child !== parent.querySelector(':scope > summary')))
|
|
4629
|
+
const unassigned =
|
|
4630
|
+
child.parentElement?.shadowRoot !== null &&
|
|
4631
|
+
child.parentElement?.shadowRoot !== undefined &&
|
|
4632
|
+
child.assignedSlot === null
|
|
4633
|
+
if (style.display === 'none' || outside || unassigned) {
|
|
4634
|
+
root = undefined
|
|
4635
|
+
pending.length = 0
|
|
4636
|
+
break
|
|
4637
|
+
}
|
|
4638
|
+
child = parent
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
while (pending.length > 0) {
|
|
4642
|
+
const entry = pending.pop()
|
|
4643
|
+
if (entry === undefined) continue
|
|
4644
|
+
const { live, twin, visited } = entry
|
|
4645
|
+
const tag = live.localName
|
|
4646
|
+
const input = tag === 'input' ? (live.getAttribute('type')?.toLowerCase() ?? 'text') : ''
|
|
4647
|
+
if (
|
|
4648
|
+
[
|
|
4649
|
+
'script',
|
|
4650
|
+
'style',
|
|
4651
|
+
'template',
|
|
4652
|
+
'frame',
|
|
4653
|
+
'frameset',
|
|
4654
|
+
'iframe',
|
|
4655
|
+
'object',
|
|
4656
|
+
'embed',
|
|
4657
|
+
'applet',
|
|
4658
|
+
'noscript',
|
|
4659
|
+
'meta',
|
|
4660
|
+
'link',
|
|
4661
|
+
'base',
|
|
4662
|
+
'math',
|
|
4663
|
+
].includes(tag) ||
|
|
4664
|
+
(tag === 'input' && ['password', 'hidden'].includes(input))
|
|
4665
|
+
) {
|
|
4666
|
+
twin.remove()
|
|
4667
|
+
if (twin === root) root = undefined
|
|
4668
|
+
continue
|
|
4669
|
+
}
|
|
4670
|
+
const style = entry.style ?? (rendered ? view.getComputedStyle(live) : undefined)
|
|
4671
|
+
const invisible = style?.visibility === 'hidden' || style?.visibility === 'collapse'
|
|
4672
|
+
if (style?.display === 'none' || (invisible && !live.hasChildNodes())) {
|
|
4673
|
+
twin.remove()
|
|
4674
|
+
if (twin === root) root = undefined
|
|
4675
|
+
continue
|
|
4676
|
+
}
|
|
4677
|
+
if (!visited) {
|
|
4678
|
+
pending.push({ live, twin, style, visited: true })
|
|
4679
|
+
if (rendered) {
|
|
4680
|
+
twin.removeAttribute('hidden')
|
|
4681
|
+
twin.removeAttribute('aria-hidden')
|
|
4682
|
+
}
|
|
4683
|
+
if (
|
|
4684
|
+
rendered &&
|
|
4685
|
+
(live instanceof view.HTMLAnchorElement || live instanceof view.HTMLAreaElement) &&
|
|
4686
|
+
live.hasAttribute('href')
|
|
4687
|
+
)
|
|
4688
|
+
twin.setAttribute('href', live.href)
|
|
4689
|
+
if (style?.contentVisibility === 'hidden' || ['canvas', 'video', 'audio'].includes(tag)) {
|
|
4690
|
+
twin.replaceChildren()
|
|
4691
|
+
continue
|
|
4692
|
+
}
|
|
4693
|
+
if (
|
|
4694
|
+
rendered &&
|
|
4695
|
+
(live instanceof view.HTMLInputElement ||
|
|
4696
|
+
live instanceof view.HTMLTextAreaElement ||
|
|
4697
|
+
live instanceof view.HTMLSelectElement ||
|
|
4698
|
+
live instanceof view.HTMLOptionElement ||
|
|
4699
|
+
live instanceof view.HTMLOptGroupElement)
|
|
4700
|
+
)
|
|
4701
|
+
continue
|
|
4702
|
+
const summary =
|
|
4703
|
+
tag === 'details' && !live.hasAttribute('open')
|
|
4704
|
+
? live.querySelector(':scope > summary')
|
|
4705
|
+
: undefined
|
|
4706
|
+
|
|
4707
|
+
// Native layout selects the switch branch after its conditional processing tests.
|
|
4708
|
+
const conditional = tag === 'switch' && live.namespaceURI === 'http://www.w3.org/2000/svg'
|
|
4709
|
+
const branch = rendered && conditional
|
|
4710
|
+
? (Array.from(live.children).find((candidate) => candidate.getClientRects().length > 0) ?? live.firstElementChild)
|
|
4711
|
+
: undefined
|
|
4712
|
+
// Save sibling pointers before pruning invalidates the copied child collections.
|
|
4713
|
+
let element = live.lastElementChild
|
|
4714
|
+
let counterpart = twin.lastElementChild
|
|
4715
|
+
let child = live.lastChild
|
|
4716
|
+
let mirror = twin.lastChild
|
|
4717
|
+
while (child !== null && mirror !== null) {
|
|
4718
|
+
const previous = child.previousSibling
|
|
4719
|
+
const previousMirror = mirror.previousSibling
|
|
4720
|
+
const previousElement = element?.previousElementSibling ?? null
|
|
4721
|
+
const previousCounterpart = counterpart?.previousElementSibling ?? null
|
|
4722
|
+
let omitted = rendered && ((summary !== undefined && child !== summary) || (conditional && child !== branch))
|
|
4723
|
+
if (rendered && live.shadowRoot !== null) {
|
|
4724
|
+
const slot =
|
|
4725
|
+
child instanceof view.Element || child instanceof view.Text ? child.assignedSlot : null
|
|
4726
|
+
if (slot === null) omitted = true
|
|
4727
|
+
else {
|
|
4728
|
+
for (
|
|
4729
|
+
let parent = slot;
|
|
4730
|
+
parent !== null && parent !== live;
|
|
4731
|
+
parent = parent.assignedSlot ?? parent.parentElement ?? (parent.parentNode instanceof view.ShadowRoot ? parent.parentNode.host : null)
|
|
4732
|
+
) {
|
|
4733
|
+
if (view.getComputedStyle(parent).display === 'none') omitted = true
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
if (omitted || (child.nodeType === 3 && invisible)) mirror.parentNode?.removeChild(mirror)
|
|
4738
|
+
else if (child.nodeType === 1) {
|
|
4739
|
+
// Element sibling pointers retain their type across windowless owner realms.
|
|
4740
|
+
if (element !== null && counterpart !== null)
|
|
4741
|
+
pending.push({ live: element, twin: counterpart, visited: false })
|
|
4742
|
+
}
|
|
4743
|
+
if (child.nodeType === 1) {
|
|
4744
|
+
element = previousElement
|
|
4745
|
+
counterpart = previousCounterpart
|
|
4746
|
+
}
|
|
4747
|
+
child = previous
|
|
4748
|
+
mirror = previousMirror
|
|
4749
|
+
}
|
|
4750
|
+
continue
|
|
4751
|
+
}
|
|
4752
|
+
if (!rendered || style?.contentVisibility === 'hidden') continue
|
|
4753
|
+
if (
|
|
4754
|
+
['div', 'summary', 'details'].includes(tag) &&
|
|
4755
|
+
!style?.display.startsWith('inline') &&
|
|
4756
|
+
style?.display !== 'contents' &&
|
|
4757
|
+
twin.hasChildNodes()
|
|
4758
|
+
) {
|
|
4759
|
+
twin.prepend(inert.createElement('br'))
|
|
4760
|
+
twin.append(inert.createElement('br'))
|
|
4761
|
+
}
|
|
4762
|
+
let fragments
|
|
4763
|
+
let lines
|
|
4764
|
+
let container = false
|
|
4765
|
+
if (live instanceof view.HTMLInputElement) {
|
|
4766
|
+
lines = []
|
|
4767
|
+
if (['text', 'email', 'search', 'tel', 'url', 'number'].includes(live.type)) {
|
|
4768
|
+
if (live.type !== 'number' || !live.validity.badInput) {
|
|
4769
|
+
const placeholder = view.getComputedStyle(live, '::placeholder')
|
|
4770
|
+
lines = [
|
|
4771
|
+
live.value ||
|
|
4772
|
+
(live.matches(':placeholder-shown') &&
|
|
4773
|
+
placeholder.visibility === 'visible' &&
|
|
4774
|
+
placeholder.opacity !== '0' &&
|
|
4775
|
+
placeholder.color !== 'rgba(0, 0, 0, 0)'
|
|
4776
|
+
? live.placeholder
|
|
4777
|
+
: ''),
|
|
4778
|
+
]
|
|
4779
|
+
}
|
|
4780
|
+
} else if (['submit', 'reset', 'button'].includes(live.type)) lines = [live.value]
|
|
4781
|
+
else if (live.type === 'file' && live.files?.length === 1)
|
|
4782
|
+
lines = Array.from(live.files ?? [], (file) => file.name)
|
|
4783
|
+
else if (live.type === 'image' && live.getAttribute('aria-hidden') !== 'true')
|
|
4784
|
+
lines = [live.getAttribute('aria-label')?.trim() || live.alt]
|
|
4785
|
+
} else if (live instanceof view.HTMLTextAreaElement) {
|
|
4786
|
+
const placeholder = view.getComputedStyle(live, '::placeholder')
|
|
4787
|
+
const value =
|
|
4788
|
+
live.value ||
|
|
4789
|
+
(live.matches(':placeholder-shown') &&
|
|
4790
|
+
placeholder.visibility === 'visible' &&
|
|
4791
|
+
placeholder.opacity !== '0' &&
|
|
4792
|
+
placeholder.color !== 'rgba(0, 0, 0, 0)'
|
|
4793
|
+
? live.placeholder
|
|
4794
|
+
: '')
|
|
4795
|
+
lines = value.split(/\\r?\\n/)
|
|
4796
|
+
} else if (
|
|
4797
|
+
live instanceof view.HTMLSelectElement ||
|
|
4798
|
+
live instanceof view.HTMLOptionElement ||
|
|
4799
|
+
live instanceof view.HTMLOptGroupElement
|
|
4800
|
+
) {
|
|
4801
|
+
lines = []
|
|
4802
|
+
const select = live instanceof view.HTMLSelectElement ? live : live.closest('select')
|
|
4803
|
+
if (select !== null) {
|
|
4804
|
+
if (!select.multiple && select.size <= 1) {
|
|
4805
|
+
const option = select.selectedOptions[0]
|
|
4806
|
+
if (option !== undefined && (live === select || live === option || live.contains(option)))
|
|
4807
|
+
lines.push(option.label)
|
|
4808
|
+
} else {
|
|
4809
|
+
const box = select.getBoundingClientRect()
|
|
4810
|
+
const top = box.top + select.clientTop
|
|
4811
|
+
const bottom = top + select.clientHeight
|
|
4812
|
+
const rows =
|
|
4813
|
+
live === select
|
|
4814
|
+
? select.querySelectorAll('option,optgroup')
|
|
4815
|
+
: [live, ...live.querySelectorAll('option')]
|
|
4816
|
+
for (const row of rows) {
|
|
4817
|
+
const rowStyle = view.getComputedStyle(row)
|
|
4818
|
+
const groupStyle =
|
|
4819
|
+
row.parentElement instanceof view.HTMLOptGroupElement
|
|
4820
|
+
? view.getComputedStyle(row.parentElement)
|
|
4821
|
+
: undefined
|
|
4822
|
+
if (
|
|
4823
|
+
rowStyle.display === 'none' ||
|
|
4824
|
+
rowStyle.visibility !== 'visible' ||
|
|
4825
|
+
groupStyle?.display === 'none'
|
|
4826
|
+
)
|
|
4827
|
+
continue
|
|
4828
|
+
const rect = row.getBoundingClientRect()
|
|
4829
|
+
// The group box includes its options; its caption ends where the first row starts.
|
|
4830
|
+
const end =
|
|
4831
|
+
row instanceof view.HTMLOptGroupElement
|
|
4832
|
+
? (Array.from(row.querySelectorAll('option'))
|
|
4833
|
+
.find((option) => option.getClientRects().length > 0)
|
|
4834
|
+
?.getBoundingClientRect().top ?? rect.bottom)
|
|
4835
|
+
: rect.bottom
|
|
4836
|
+
if (
|
|
4837
|
+
end > top &&
|
|
4838
|
+
rect.top < bottom &&
|
|
4839
|
+
rect.right > box.left + select.clientLeft &&
|
|
4840
|
+
rect.left < box.left + select.clientLeft + select.clientWidth &&
|
|
4841
|
+
(row instanceof view.HTMLOptionElement || row instanceof view.HTMLOptGroupElement)
|
|
4842
|
+
)
|
|
4843
|
+
lines.push(row.label)
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
} else if (tag === 'svg') {
|
|
4848
|
+
if (twin.parentElement?.closest('svg, foreignObject')?.localName === 'svg') continue
|
|
4849
|
+
lines = []
|
|
4850
|
+
fragments = []
|
|
4851
|
+
for (const content of twin.querySelectorAll('text, foreignObject')) {
|
|
4852
|
+
if (content.parentElement?.closest('svg, foreignObject')?.localName === 'foreignObject') continue
|
|
4853
|
+
if (content.localName === 'text' && content.parentElement?.closest('text')?.closest('svg') === content.closest('svg')) continue
|
|
4854
|
+
if (content.localName === 'foreignObject') fragments.push(...content.childNodes)
|
|
4855
|
+
else if (content.textContent) fragments.push(inert.createTextNode(content.textContent))
|
|
4856
|
+
}
|
|
4857
|
+
if (fragments.length === 0) fragments = undefined
|
|
4858
|
+
if (!invisible && fragments === undefined && live.getAttribute('aria-hidden') !== 'true')
|
|
4859
|
+
lines.push(
|
|
4860
|
+
live.getAttribute('aria-label')?.trim() ||
|
|
4861
|
+
live.querySelector(':scope > title')?.textContent ||
|
|
4862
|
+
'',
|
|
4863
|
+
)
|
|
4864
|
+
} else if (['form', 'dialog', 'button'].includes(tag)) {
|
|
4865
|
+
container = true
|
|
4866
|
+
if (live instanceof view.HTMLButtonElement && !invisible && live.innerText.trim() === '') {
|
|
4867
|
+
const name = live.getAttribute('aria-label')?.trim()
|
|
4868
|
+
if (name) {
|
|
4869
|
+
lines = [name]
|
|
4870
|
+
container = false
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
if (lines === undefined && !container) continue
|
|
4875
|
+
if (invisible && !container && tag !== 'svg') lines = []
|
|
4876
|
+
const block = !style?.display.startsWith('inline') && style?.display !== 'contents'
|
|
4877
|
+
const carrier = inert.createElement(block ? 'div' : 'span')
|
|
4878
|
+
if (container) carrier.append(...twin.childNodes)
|
|
4879
|
+
else if (fragments !== undefined) {
|
|
4880
|
+
for (const fragment of fragments) {
|
|
4881
|
+
carrier.append(fragment, inert.createElement('br'))
|
|
4882
|
+
}
|
|
4883
|
+
} else
|
|
4884
|
+
for (const [index, line] of (lines ?? []).entries()) {
|
|
4885
|
+
if (index > 0) carrier.append(inert.createElement('br'))
|
|
4886
|
+
carrier.append(inert.createTextNode(line))
|
|
4887
|
+
}
|
|
4888
|
+
if (carrier.hasChildNodes()) {
|
|
4889
|
+
carrier.prepend(inert.createElement('br'))
|
|
4890
|
+
carrier.append(inert.createElement('br'))
|
|
4891
|
+
}
|
|
4892
|
+
twin.replaceWith(carrier)
|
|
4893
|
+
if (twin === root) root = carrier
|
|
4894
|
+
}
|
|
4895
|
+
// Guard a copy that has fallen out of step with the live tree.
|
|
4896
|
+
for (const input of root?.querySelectorAll('input[type="password" i],input[type="hidden" i]') ??
|
|
4897
|
+
[])
|
|
4898
|
+
input.remove()
|
|
4899
|
+
capture.html = root?.outerHTML ?? ''
|
|
4900
|
+
return capture
|
|
4275
4901
|
}`;
|
|
4276
4902
|
}
|
|
4277
4903
|
/**
|
|
@@ -4425,7 +5051,7 @@ function compileActionabilityFunction(options) {
|
|
|
4425
5051
|
if (${JSON.stringify(options.enabled === true)} && this.matches(':disabled')) throw new Error('Element is disabled')
|
|
4426
5052
|
if (${JSON.stringify(options.editable === true)} && (this.matches('[readonly]') || (!this.isContentEditable && !('value' in this)))) throw new Error('Element is not editable')
|
|
4427
5053
|
let previous
|
|
4428
|
-
for (let index = 0; index < 2; index += 1) {
|
|
5054
|
+
for (let index = 0; index < ${options.stable === true ? 2 : 0}; index += 1) {
|
|
4429
5055
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
4430
5056
|
const rect = this.getBoundingClientRect()
|
|
4431
5057
|
const current = [rect.x, rect.y, rect.width, rect.height]
|
|
@@ -5558,7 +6184,7 @@ var BrowserCodegen = class {
|
|
|
5558
6184
|
* title: 'Example',
|
|
5559
6185
|
* html: '<nav>Menu</nav><main><p>Body</p></main>',
|
|
5560
6186
|
* })
|
|
5561
|
-
* reading.markdown() // { text: 'Body', offset: 0, total: 4 }
|
|
6187
|
+
* reading.markdown({ distill: true }) // { text: 'Body', offset: 0, total: 4 }
|
|
5562
6188
|
* ```
|
|
5563
6189
|
*/
|
|
5564
6190
|
var BrowserReading = class {
|
|
@@ -5590,7 +6216,7 @@ var BrowserReading = class {
|
|
|
5590
6216
|
return this.#navigation !== void 0 && this.#navigation() !== this.#epoch;
|
|
5591
6217
|
}
|
|
5592
6218
|
markdown(options) {
|
|
5593
|
-
const distill = options?.distill ??
|
|
6219
|
+
const distill = options?.distill ?? false;
|
|
5594
6220
|
let text = this.#markdowns.get(distill);
|
|
5595
6221
|
if (text === void 0) {
|
|
5596
6222
|
text = (0, _orkestrel_markdown.renderMarkdown)((0, _orkestrel_markdown.htmlToMarkdown)(this.#source(distill).document));
|
|
@@ -5599,7 +6225,7 @@ var BrowserReading = class {
|
|
|
5599
6225
|
return extractBrowserSlice(text, options?.offset, options?.limit);
|
|
5600
6226
|
}
|
|
5601
6227
|
text(options) {
|
|
5602
|
-
const distill = options?.distill ??
|
|
6228
|
+
const distill = options?.distill ?? false;
|
|
5603
6229
|
let text = this.#texts.get(distill);
|
|
5604
6230
|
if (text === void 0) {
|
|
5605
6231
|
text = (0, _orkestrel_html.renderText)(this.#source(distill).document);
|
|
@@ -5608,7 +6234,10 @@ var BrowserReading = class {
|
|
|
5608
6234
|
return extractBrowserSlice(text, options?.offset, options?.limit);
|
|
5609
6235
|
}
|
|
5610
6236
|
#source(distill) {
|
|
5611
|
-
if (!distill) return this.#html
|
|
6237
|
+
if (!distill) return this.#html.map((node) => node.category === "element" ? {
|
|
6238
|
+
...node,
|
|
6239
|
+
attributes: (0, _orkestrel_html.resolveAttributes)(node, this.#url)
|
|
6240
|
+
} : node);
|
|
5612
6241
|
this.#distilled ??= this.#html.distill({ base: this.#url });
|
|
5613
6242
|
return this.#distilled;
|
|
5614
6243
|
}
|
|
@@ -5679,8 +6308,7 @@ var BrowserPageElement = class {
|
|
|
5679
6308
|
async read(options) {
|
|
5680
6309
|
const epoch = this.#epoch();
|
|
5681
6310
|
const capture = await this.#call(`function() {
|
|
5682
|
-
const capture = (${compileReadFunction()})()
|
|
5683
|
-
capture.html = this.outerHTML
|
|
6311
|
+
const capture = (${compileReadFunction()})(this)
|
|
5684
6312
|
return ${compileGuardedEvaluateExpression("capture", BROWSER_RESULT_LIMIT)}
|
|
5685
6313
|
}`, options);
|
|
5686
6314
|
if (!(0, _orkestrel_contract.isRecord)(capture)) throw new BrowserElementError(this.reference, "UNKNOWN", "returned an invalid reading");
|
|
@@ -5881,6 +6509,7 @@ var BrowserPageElement = class {
|
|
|
5881
6509
|
}
|
|
5882
6510
|
async #pointer(options) {
|
|
5883
6511
|
this.#assert(options);
|
|
6512
|
+
await this.#input.page.send("Page.bringToFront", void 0, options);
|
|
5884
6513
|
await this.#input.client.send("DOM.scrollIntoViewIfNeeded", { backendNodeId: this.#input.backend }, {
|
|
5885
6514
|
session: this.#input.node.session,
|
|
5886
6515
|
...options
|
|
@@ -6001,7 +6630,7 @@ var BrowserElementManager = class {
|
|
|
6001
6630
|
...options
|
|
6002
6631
|
});
|
|
6003
6632
|
this.#assertCapture(this.#input.page.id, epoch);
|
|
6004
|
-
return renderBrowserOutline(this.#input.page.url, requireBrowserString(readEvaluationResult(result), "Document title"), this.#within(rows, options?.within), limit);
|
|
6633
|
+
return renderBrowserOutline(this.#input.page.url, requireBrowserString(readEvaluationResult(result), "Document title"), this.#within(rows, options?.within), limit, options?.search);
|
|
6005
6634
|
} catch (error) {
|
|
6006
6635
|
if (options?.signal?.aborted === true || this.#changes === changes) throw error;
|
|
6007
6636
|
this.#lifetime.signal.throwIfAborted();
|
|
@@ -6091,34 +6720,52 @@ var BrowserElementManager = class {
|
|
|
6091
6720
|
const signal = options?.signal === void 0 ? this.#lifetime.signal : AbortSignal.any([options.signal, this.#lifetime.signal]);
|
|
6092
6721
|
while (true) {
|
|
6093
6722
|
signal.throwIfAborted();
|
|
6094
|
-
const remaining = Math.max(0, end - performance.now());
|
|
6095
|
-
const call = {
|
|
6096
|
-
timeout: remaining,
|
|
6097
|
-
signal
|
|
6098
|
-
};
|
|
6099
|
-
await this.#input.ready(call);
|
|
6100
|
-
const frame = this.#scopeFrame(query.within);
|
|
6101
|
-
const session = await this.#input.resolve(frame);
|
|
6102
|
-
const context = await this.#input.world(frame, session, call);
|
|
6103
6723
|
const key = `__browserQueryWait${++this.#sequence}`;
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
returnByValue: true,
|
|
6108
|
-
awaitPromise: true
|
|
6109
|
-
}, {
|
|
6110
|
-
session,
|
|
6111
|
-
timeout: remaining + 1e3,
|
|
6112
|
-
signal
|
|
6113
|
-
}).then((result) => ({ result }), (error) => ({ error }));
|
|
6724
|
+
let session;
|
|
6725
|
+
let context;
|
|
6726
|
+
const changes = this.#changes;
|
|
6114
6727
|
try {
|
|
6728
|
+
await this.#input.ready({
|
|
6729
|
+
timeout: this.#remaining(end),
|
|
6730
|
+
signal
|
|
6731
|
+
});
|
|
6732
|
+
const frame = this.#scopeFrame(query.within);
|
|
6733
|
+
session = await this.#input.resolve(frame);
|
|
6734
|
+
context = await this.#input.world(frame, session, {
|
|
6735
|
+
timeout: this.#remaining(end),
|
|
6736
|
+
signal
|
|
6737
|
+
});
|
|
6738
|
+
const remaining = this.#remaining(end);
|
|
6739
|
+
const call = {
|
|
6740
|
+
timeout: remaining,
|
|
6741
|
+
signal
|
|
6742
|
+
};
|
|
6743
|
+
const pending = this.#input.client.send("Runtime.evaluate", {
|
|
6744
|
+
expression: compileQueryWaitExpression(remaining, key),
|
|
6745
|
+
contextId: context,
|
|
6746
|
+
returnByValue: true,
|
|
6747
|
+
awaitPromise: true
|
|
6748
|
+
}, {
|
|
6749
|
+
session,
|
|
6750
|
+
timeout: remaining + 1e3,
|
|
6751
|
+
signal
|
|
6752
|
+
}).then((result) => ({ result }), (error) => ({ error }));
|
|
6115
6753
|
const found = await this.find(query, call);
|
|
6116
6754
|
if (options?.absent === true ? found.length === 0 : found.length > 0) return found;
|
|
6117
6755
|
const outcome = await pending;
|
|
6118
6756
|
if ("error" in outcome) throw outcome.error;
|
|
6119
6757
|
if (readEvaluationResult(outcome.result) !== true || performance.now() >= end) throw new BrowserError("Element wait timed out", "BROWSER_WAIT_TIMEOUT");
|
|
6758
|
+
} catch (error) {
|
|
6759
|
+
signal.throwIfAborted();
|
|
6760
|
+
if (isCDPTimeoutError(error)) throw new BrowserError("Element wait timed out", "BROWSER_WAIT_TIMEOUT");
|
|
6761
|
+
if (this.#input.page.closed || this.#changes === changes && (!(0, _orkestrel_contract.isError)(error) || !BROWSER_CONTEXT_LOSS_PATTERN.test(error.message))) throw error;
|
|
6762
|
+
const remaining = this.#remaining(end);
|
|
6763
|
+
if (this.#changes === changes) await this.#input.recover({
|
|
6764
|
+
signal,
|
|
6765
|
+
timeout: remaining
|
|
6766
|
+
});
|
|
6120
6767
|
} finally {
|
|
6121
|
-
await this.#input.client.send("Runtime.evaluate", {
|
|
6768
|
+
if (context !== void 0 && session !== void 0) await this.#input.client.send("Runtime.evaluate", {
|
|
6122
6769
|
expression: `globalThis[${JSON.stringify(key)}]?.()`,
|
|
6123
6770
|
contextId: context,
|
|
6124
6771
|
returnByValue: true
|
|
@@ -6129,6 +6776,11 @@ var BrowserElementManager = class {
|
|
|
6129
6776
|
}
|
|
6130
6777
|
}
|
|
6131
6778
|
}
|
|
6779
|
+
#remaining(end) {
|
|
6780
|
+
const remaining = end - performance.now();
|
|
6781
|
+
if (remaining <= 0) throw new BrowserError("Element wait timed out", "BROWSER_WAIT_TIMEOUT");
|
|
6782
|
+
return remaining;
|
|
6783
|
+
}
|
|
6132
6784
|
element(reference) {
|
|
6133
6785
|
const canonical = parseBrowserReference(reference);
|
|
6134
6786
|
return [...this.#records.values()].find((entry) => entry.node.reference === canonical)?.element;
|
|
@@ -6636,7 +7288,7 @@ var BrowserRegistry = class {
|
|
|
6636
7288
|
pending.reject(error);
|
|
6637
7289
|
}
|
|
6638
7290
|
async #executeAdopted(tool, synthetic, input, context) {
|
|
6639
|
-
const parameters = synthetic ? Object.fromEntries(Object.entries(input).filter(([key]) => key !== "
|
|
7291
|
+
const parameters = synthetic ? Object.fromEntries(Object.entries(input).filter(([key]) => key !== "purpose")) : input;
|
|
6640
7292
|
const result = await this.execute(tool, parameters, { signal: context.signal });
|
|
6641
7293
|
if (result.status !== "Completed") throw new BrowserError(`Browser tool ${result.status}: ${result.error ?? "No error description"}`.slice(0, BROWSER_REGISTRY_OUTPUT_LIMIT));
|
|
6642
7294
|
return renderBrowserToolOutput(result.output);
|
|
@@ -8953,6 +9605,8 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
8953
9605
|
#epoch = 0;
|
|
8954
9606
|
#floor = 0;
|
|
8955
9607
|
#closed = false;
|
|
9608
|
+
#waitRelease = new AbortController();
|
|
9609
|
+
#textWaits = /* @__PURE__ */ new Map();
|
|
8956
9610
|
#codegen;
|
|
8957
9611
|
#registry;
|
|
8958
9612
|
#codegenStart = new BrowserTransition();
|
|
@@ -9003,6 +9657,7 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9003
9657
|
session: sessionId,
|
|
9004
9658
|
resolve: this.#resolveFrameSession.bind(this),
|
|
9005
9659
|
world: this.#world.bind(this),
|
|
9660
|
+
recover: this.#recoverReadiness.bind(this),
|
|
9006
9661
|
reference: this.#reference,
|
|
9007
9662
|
ready: this.#ready.bind(this)
|
|
9008
9663
|
});
|
|
@@ -9066,6 +9721,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9066
9721
|
}
|
|
9067
9722
|
async wait(text, options) {
|
|
9068
9723
|
this.assert();
|
|
9724
|
+
options = {
|
|
9725
|
+
...options,
|
|
9726
|
+
signal: options?.signal === void 0 ? this.#waitRelease.signal : AbortSignal.any([options.signal, this.#waitRelease.signal])
|
|
9727
|
+
};
|
|
9069
9728
|
const timeout = options?.timeout ?? 3e4;
|
|
9070
9729
|
validateBrowserTimeout(timeout);
|
|
9071
9730
|
const end = performance.now() + timeout;
|
|
@@ -9078,9 +9737,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9078
9737
|
});
|
|
9079
9738
|
const context = await this.#world(this.id, this.#sessionId, options);
|
|
9080
9739
|
const key = `__browserTextWait${++this.#waitSequence}`;
|
|
9740
|
+
this.#textWaits.set(key, context);
|
|
9081
9741
|
try {
|
|
9082
9742
|
if (readEvaluationResult(await this.send("Runtime.evaluate", {
|
|
9083
|
-
expression: compileTextWaitExpression(text, remaining, key),
|
|
9743
|
+
expression: compileTextWaitExpression(text, remaining, key, options?.absent === true),
|
|
9084
9744
|
contextId: context,
|
|
9085
9745
|
returnByValue: true,
|
|
9086
9746
|
awaitPromise: true
|
|
@@ -9094,23 +9754,20 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9094
9754
|
return;
|
|
9095
9755
|
} catch (error) {
|
|
9096
9756
|
if (options?.signal?.aborted === true) {
|
|
9097
|
-
await this
|
|
9098
|
-
expression: `globalThis[${JSON.stringify(key)}]?.()`,
|
|
9099
|
-
contextId: context,
|
|
9100
|
-
returnByValue: true
|
|
9101
|
-
}, { timeout: 1e3 }).catch(() => void 0);
|
|
9757
|
+
if (!this.#closed) await this.#releaseTextWait(key, context);
|
|
9102
9758
|
throw options.signal.reason;
|
|
9103
9759
|
}
|
|
9104
|
-
if (!(0, _orkestrel_contract.isError)(error) ||
|
|
9105
|
-
this.#dom = void 0;
|
|
9760
|
+
if (!(0, _orkestrel_contract.isError)(error) || !BROWSER_CONTEXT_LOSS_PATTERN.test(error.message)) throw error;
|
|
9106
9761
|
if (performance.now() >= end) throw new BrowserError("Browser text wait timed out", "BROWSER_WAIT_TIMEOUT", {
|
|
9107
9762
|
text,
|
|
9108
9763
|
timeout
|
|
9109
9764
|
});
|
|
9110
|
-
await this.#
|
|
9765
|
+
await this.#recoverReadiness({
|
|
9111
9766
|
...options,
|
|
9112
9767
|
timeout: Math.max(0, end - performance.now())
|
|
9113
9768
|
});
|
|
9769
|
+
} finally {
|
|
9770
|
+
this.#textWaits.delete(key);
|
|
9114
9771
|
}
|
|
9115
9772
|
}
|
|
9116
9773
|
}
|
|
@@ -9498,6 +10155,8 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9498
10155
|
return release;
|
|
9499
10156
|
}
|
|
9500
10157
|
async #releaseResources() {
|
|
10158
|
+
this.#waitRelease.abort(new BrowserError("Browser page is closed"));
|
|
10159
|
+
await Promise.all([...this.#textWaits].map(([key, context]) => this.#releaseTextWait(key, context)));
|
|
9501
10160
|
this.#unhold();
|
|
9502
10161
|
this.#announcement.reject(new BrowserError("Browser session ended"));
|
|
9503
10162
|
this.#client.unsubscribe("Page.lifecycleEvent", this.#lifecycleHandler, this.#sessionId);
|
|
@@ -9547,6 +10206,16 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9547
10206
|
}
|
|
9548
10207
|
this.#steps.destroy();
|
|
9549
10208
|
}
|
|
10209
|
+
async #releaseTextWait(key, context) {
|
|
10210
|
+
await this.#client.send("Runtime.evaluate", {
|
|
10211
|
+
expression: `globalThis[${JSON.stringify(key)}]?.()`,
|
|
10212
|
+
contextId: context,
|
|
10213
|
+
returnByValue: true
|
|
10214
|
+
}, {
|
|
10215
|
+
session: this.#sessionId,
|
|
10216
|
+
timeout: 1e3
|
|
10217
|
+
}).catch(() => void 0);
|
|
10218
|
+
}
|
|
9550
10219
|
#frame(frame) {
|
|
9551
10220
|
return new BrowserFrame(this.#client, (id) => this.#resolveFrameSession(id), frame.id, frame.url, frame.parent, frame.name, true, () => this.#epochOf(frame.id), (session, call) => this.#world(frame.id, session, call));
|
|
9552
10221
|
}
|
|
@@ -9590,6 +10259,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9590
10259
|
#rejectSeed(seed) {
|
|
9591
10260
|
if (this.#seed === seed) this.#seed = void 0;
|
|
9592
10261
|
}
|
|
10262
|
+
#recoverReadiness(options) {
|
|
10263
|
+
this.#dom = void 0;
|
|
10264
|
+
return this.#parkReadiness(options);
|
|
10265
|
+
}
|
|
9593
10266
|
async #seedReadiness(options) {
|
|
9594
10267
|
const document = this.#floor;
|
|
9595
10268
|
const context = await this.#world(this.id, this.#sessionId, options);
|
|
@@ -9637,24 +10310,44 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9637
10310
|
this.#dom = this.#loader ?? "initial";
|
|
9638
10311
|
for (const id of this.#readiness.keys()) this.#settleReadiness(id)?.resolve();
|
|
9639
10312
|
}
|
|
9640
|
-
#world(frame, session, options) {
|
|
10313
|
+
async #world(frame, session, options) {
|
|
10314
|
+
options?.signal?.throwIfAborted();
|
|
9641
10315
|
const cached = this.#worlds.get(frame);
|
|
9642
10316
|
if (cached !== void 0) return Promise.resolve(cached.context);
|
|
9643
10317
|
const existing = this.#creating.get(frame);
|
|
9644
|
-
if (existing !== void 0) return existing.promise;
|
|
10318
|
+
if (existing !== void 0) return await this.#awaitWorld(existing.promise, options);
|
|
9645
10319
|
const pending = this.#client.send("Page.createIsolatedWorld", {
|
|
9646
10320
|
frameId: frame,
|
|
9647
10321
|
worldName: BROWSER_FRAME_WORLD_NAME
|
|
9648
10322
|
}, {
|
|
9649
10323
|
session,
|
|
9650
|
-
|
|
10324
|
+
signal: this.#waitRelease.signal
|
|
9651
10325
|
}).then(this.#decodeWorld.bind(this, frame));
|
|
9652
10326
|
this.#creating.set(frame, {
|
|
9653
10327
|
session,
|
|
9654
10328
|
promise: pending
|
|
9655
10329
|
});
|
|
9656
10330
|
pending.then(this.#publishWorld.bind(this, frame, session, pending), this.#settleWorld.bind(this, frame, pending));
|
|
9657
|
-
return pending;
|
|
10331
|
+
return await this.#awaitWorld(pending, options);
|
|
10332
|
+
}
|
|
10333
|
+
async #awaitWorld(pending, options) {
|
|
10334
|
+
const signal = options?.signal;
|
|
10335
|
+
signal?.throwIfAborted();
|
|
10336
|
+
const timeout = options?.timeout ?? 3e4;
|
|
10337
|
+
validateBrowserTimeout(timeout);
|
|
10338
|
+
const abandoned = Promise.withResolvers();
|
|
10339
|
+
const abort = this.#abandonWorld.bind(this, abandoned, signal);
|
|
10340
|
+
const timer = setTimeout(() => abandoned.reject(new CDPTimeoutError("Isolated world wait timed out")), timeout);
|
|
10341
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
10342
|
+
try {
|
|
10343
|
+
return await Promise.race([pending, abandoned.promise]);
|
|
10344
|
+
} finally {
|
|
10345
|
+
clearTimeout(timer);
|
|
10346
|
+
signal?.removeEventListener("abort", abort);
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10349
|
+
#abandonWorld(pending, signal) {
|
|
10350
|
+
pending.reject(signal?.reason);
|
|
9658
10351
|
}
|
|
9659
10352
|
#decodeWorld(frame, world) {
|
|
9660
10353
|
return readBrowserWorld(world, frame);
|
|
@@ -11485,7 +12178,7 @@ function createBrowserReading(input) {
|
|
|
11485
12178
|
*
|
|
11486
12179
|
* const toolset = createBrowserToolset(page, { context })
|
|
11487
12180
|
* await toolset.start()
|
|
11488
|
-
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: {
|
|
12181
|
+
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'cart' } })
|
|
11489
12182
|
* ```
|
|
11490
12183
|
*
|
|
11491
12184
|
* @example Drive a page with a small model
|
|
@@ -11499,8 +12192,8 @@ function createBrowserReading(input) {
|
|
|
11499
12192
|
* const system =
|
|
11500
12193
|
* 'You control a web browser with tools and must call a tool before you answer. ' +
|
|
11501
12194
|
* 'The first message shows the page as look returns it; references such as e4 name its elements. ' +
|
|
11502
|
-
* 'To learn a fact, call read with
|
|
11503
|
-
*
|
|
12195
|
+
* 'To learn a fact, call read with search set to words from your question; when its result ends by naming an offset, call read again with that offset. ' +
|
|
12196
|
+
* "To use the site's search box, call type with its reference, the words, and submit true. " +
|
|
11504
12197
|
* 'To press a button or follow a link, call click with its reference from the latest result. Never invent a reference. ' +
|
|
11505
12198
|
* 'If text you expect has not appeared, call wait once. ' +
|
|
11506
12199
|
* 'When the task is done, answer in one short sentence.'
|
|
@@ -11510,11 +12203,11 @@ function createBrowserReading(input) {
|
|
|
11510
12203
|
* const page = await browser.create({ url: 'https://shop.example.test/' })
|
|
11511
12204
|
* const toolset = createBrowserToolset(page, { tools: createToolManager() })
|
|
11512
12205
|
* await toolset.start()
|
|
11513
|
-
* toolset.tools.tools().map((tool) => tool.name) // ['look', 'read', 'click', 'type', 'press', 'navigate', 'wait']
|
|
12206
|
+
* toolset.tools.tools().map((tool) => tool.name) // ['look', 'read', 'plain', 'click', 'type', 'press', 'navigate', 'wait']
|
|
11514
12207
|
* const seeded = await toolset.tools.execute({
|
|
11515
12208
|
* id: 'seed',
|
|
11516
12209
|
* name: 'look',
|
|
11517
|
-
* arguments: {
|
|
12210
|
+
* arguments: { search: '' },
|
|
11518
12211
|
* })
|
|
11519
12212
|
* const view = seeded.success ? String(seeded.value) : seeded.error
|
|
11520
12213
|
* const agent = createAgent(createOllama({ model: 'qwen3.5:2b-q4_K_M' }), {
|
|
@@ -11734,6 +12427,10 @@ var BrowserJourneyToolset = class {
|
|
|
11734
12427
|
const offset = args["offset"] ?? 0;
|
|
11735
12428
|
if (!(0, _orkestrel_contract.isInteger)(offset) || offset < 0) throw new BrowserError("The offset parameter must be a non-negative integer.", "BROWSER_JOURNEY_ARGUMENT", { key: "offset" });
|
|
11736
12429
|
const listings = [];
|
|
12430
|
+
const headings = [];
|
|
12431
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
12432
|
+
let headingOffset = 0;
|
|
12433
|
+
let listingOffset = 0;
|
|
11737
12434
|
const faults = /* @__PURE__ */ new Set();
|
|
11738
12435
|
let position = 0;
|
|
11739
12436
|
for (;;) {
|
|
@@ -11741,11 +12438,21 @@ var BrowserJourneyToolset = class {
|
|
|
11741
12438
|
signal,
|
|
11742
12439
|
offset: position
|
|
11743
12440
|
});
|
|
11744
|
-
|
|
12441
|
+
for (const entry of page.entries) {
|
|
12442
|
+
const listing = renderBrowserJourney(entry.journey);
|
|
12443
|
+
const heading = listing.split("\n", 1)[0] ?? "";
|
|
12444
|
+
headings.push(heading);
|
|
12445
|
+
offsets.set(headingOffset, listingOffset);
|
|
12446
|
+
headingOffset += heading.length + 1;
|
|
12447
|
+
listingOffset += listing.length + 2;
|
|
12448
|
+
listings.push(listing);
|
|
12449
|
+
}
|
|
11745
12450
|
for (const fault of page.faults) {
|
|
11746
12451
|
if (faults.has(fault.name)) continue;
|
|
11747
12452
|
faults.add(fault.name);
|
|
11748
|
-
|
|
12453
|
+
const listing = renderBrowserJourneyFault(fault);
|
|
12454
|
+
listings.push(listing);
|
|
12455
|
+
listingOffset += listing.length + 2;
|
|
11749
12456
|
}
|
|
11750
12457
|
const span = page.entries.length;
|
|
11751
12458
|
position += span;
|
|
@@ -11754,7 +12461,11 @@ var BrowserJourneyToolset = class {
|
|
|
11754
12461
|
if (listings.length === 0) return BROWSER_JOURNEY_EMPTY_LISTING;
|
|
11755
12462
|
const listing = listings.join("\n\n");
|
|
11756
12463
|
const start = offset < listing.length ? offset : 0;
|
|
11757
|
-
|
|
12464
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
12465
|
+
const matches = start === 0 ? scanBrowserText(headings.join("\n"), search) : [];
|
|
12466
|
+
const count = matches.length;
|
|
12467
|
+
const block = renderBrowserMatches(`${count} ${count === 1 ? "journey matches" : "journeys match"} ${JSON.stringify(search)}:`, matches.map((match) => `[${offsets.get(match.offset)}] ${match.text}`), this.#limit);
|
|
12468
|
+
let end = Math.min(listing.length, start + this.#limit - block.length);
|
|
11758
12469
|
const last = listing.charCodeAt(end - 1);
|
|
11759
12470
|
if (end < listing.length && last >= 55296 && last <= 56319) end -= 1;
|
|
11760
12471
|
if (end === start) throw new BrowserError(`The journeys limit of ${this.#limit} characters cannot hold the next character at offset ${start}; raise the journeys limit.`, "BROWSER_TOOLSET_LIMIT", {
|
|
@@ -11762,8 +12473,8 @@ var BrowserJourneyToolset = class {
|
|
|
11762
12473
|
offset: start
|
|
11763
12474
|
});
|
|
11764
12475
|
const text = listing.slice(start, end);
|
|
11765
|
-
if (start === 0 && end === listing.length) return text
|
|
11766
|
-
return `${text}\n\n[characters ${start}–${end} of ${listing.length}${end < listing.length ? `; call journeys with offset ${end} for more` : ""}]`;
|
|
12476
|
+
if (start === 0 && end === listing.length) return `${block}${text}`;
|
|
12477
|
+
return `${block}${text}\n\n[characters ${start}–${end} of ${listing.length}${end < listing.length ? `; call journeys with offset ${end} for more` : ""}]`;
|
|
11767
12478
|
}
|
|
11768
12479
|
async #edit(args, signal) {
|
|
11769
12480
|
if (this.#readonly) throw new BrowserError(BROWSER_JOURNEY_READONLY_REFUSAL, "BROWSER_JOURNEY_READONLY");
|
|
@@ -11929,7 +12640,7 @@ var BrowserJourneyToolset = class {
|
|
|
11929
12640
|
const performed = await this.#toolset.perform({
|
|
11930
12641
|
id: "journey",
|
|
11931
12642
|
name: "look",
|
|
11932
|
-
arguments: {
|
|
12643
|
+
arguments: { search: "" }
|
|
11933
12644
|
}, { signal });
|
|
11934
12645
|
return performed.result.success ? String(performed.result.value) : performed.result.error;
|
|
11935
12646
|
}
|
|
@@ -11957,7 +12668,7 @@ var BrowserJourneyToolset = class {
|
|
|
11957
12668
|
* view's own tools beside them.
|
|
11958
12669
|
*
|
|
11959
12670
|
* @remarks
|
|
11960
|
-
* Every toolset advertises `look`, `read`, `click`, `type`, and `wait`, which need only the
|
|
12671
|
+
* Every toolset advertises `look`, `read`, `plain`, `click`, `type`, and `wait`, which need only the
|
|
11961
12672
|
* `BrowserViewInterface` it is constructed over. With `options.page` it also advertises `press`
|
|
11962
12673
|
* and `navigate`, stages the `dialog` tool while a dialog is open on the current page, follows a
|
|
11963
12674
|
* popup the current page opens and returns to the opener when the popup closes, and adopts
|
|
@@ -11971,11 +12682,18 @@ var BrowserJourneyToolset = class {
|
|
|
11971
12682
|
* refuses every tool but `dialog` while a dialog is open, forwards `ToolContext.signal` into every
|
|
11972
12683
|
* protocol call, refuses a call to a toolset tool that carries a parameter the tool does not
|
|
11973
12684
|
* advertise, and cuts every returned string and every thrown message at `limit` characters plus
|
|
11974
|
-
* a footer; the footer of a cut
|
|
11975
|
-
*
|
|
11976
|
-
* the
|
|
11977
|
-
*
|
|
11978
|
-
* the
|
|
12685
|
+
* a footer; the footer of a cut action or `dialog` receipt that carries a view names `look` as the next
|
|
12686
|
+
* call. `look`, `read`, and `plain` read the page and return pages of at most `limit`
|
|
12687
|
+
* characters and name the offset the next page starts at. A page ends after the last line break
|
|
12688
|
+
* in its window that lies past the page's start, or at the window's end when no such break fits,
|
|
12689
|
+
* without splitting a surrogate pair. The last page ends at the end of the outline or reading. A `look` whose
|
|
12690
|
+
* `search` matches referenced rows lists those rows first, and its pages reach every referenced row.
|
|
12691
|
+
* A `press` receipt names the referenced element that has focus after the key. A page tool's
|
|
12692
|
+
* error message and JSON output reach the toolset already cut at `BROWSER_REGISTRY_OUTPUT_LIMIT`
|
|
12693
|
+
* (4 096) by the registry, so a `limit` over that shows at most 4 096 characters of either; a page
|
|
12694
|
+
* tool's text output reaches the boundary whole.
|
|
12695
|
+
* `wait` with `absent` set to `true` settles when the text is not on the page, and a miss
|
|
12696
|
+
* returns the `timeout` outcome as an appearance miss does.
|
|
11979
12697
|
*
|
|
11980
12698
|
* `click`, `type`, `press`, `navigate`, and `switch` are actions: one runs at a time, in call
|
|
11981
12699
|
* order, and holds the queue until its receipt. A queued action whose signal aborts leaves
|
|
@@ -12006,7 +12724,7 @@ var BrowserJourneyToolset = class {
|
|
|
12006
12724
|
* page placement's `type` with `submit` ends its action with `and submitted the form` when a read
|
|
12007
12725
|
* recorded a submission, or when the navigation the receipt settled names `formSubmissionGet` or
|
|
12008
12726
|
* `formSubmissionPost` as its reason, whatever the read answered, and with `and pressed Enter`
|
|
12009
|
-
* otherwise. A `read`
|
|
12727
|
+
* otherwise. A `read` or `plain`
|
|
12010
12728
|
* at an offset at or past its retained reading's end restarts that reading at 0. A receipt that
|
|
12011
12729
|
* waits for a requested navigation shares one `BROWSER_TOOL_TIMEOUT_MS` deadline between that
|
|
12012
12730
|
* wait and its view capture, of which `BROWSER_TOOL_CAPTURE_MS` is reserved for the capture. The
|
|
@@ -12022,9 +12740,9 @@ var BrowserJourneyToolset = class {
|
|
|
12022
12740
|
* A page tool is skipped, with `skip` emitted, when its name is reserved (`unresolved` included), when the manager holds
|
|
12023
12741
|
* its name under a tool the toolset did not add (checked again immediately before each addition),
|
|
12024
12742
|
* when its name falls outside `BROWSER_TOOL_NAME_PATTERN`, when its parameters or, for a source
|
|
12025
|
-
* that supplies `tools()`, its selected registration declare `
|
|
12743
|
+
* that supplies `tools()`, its selected registration declare `purpose` optional, or when that
|
|
12026
12744
|
* registration is marked `debugging`. The selected registration is the main frame's, then the
|
|
12027
|
-
* earlier one. A page tool whose parameters require nothing advertises a required `
|
|
12745
|
+
* earlier one. A page tool whose parameters require nothing advertises a required `purpose`, which
|
|
12028
12746
|
* is stripped before the tool runs. Every adopted tool is advertised `untrusted`. `destroy()`
|
|
12029
12747
|
* removes only the tools the manager still holds under the instances the toolset added, never
|
|
12030
12748
|
* closes a page, and ends only what `options.release` hands over, calling it one time last.
|
|
@@ -12035,7 +12753,7 @@ var BrowserJourneyToolset = class {
|
|
|
12035
12753
|
*
|
|
12036
12754
|
* const toolset = new BrowserToolset(page, { page })
|
|
12037
12755
|
* await toolset.start()
|
|
12038
|
-
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: {
|
|
12756
|
+
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'cart' } })
|
|
12039
12757
|
* await toolset.destroy()
|
|
12040
12758
|
* ```
|
|
12041
12759
|
*/
|
|
@@ -12099,20 +12817,23 @@ var BrowserToolset = class {
|
|
|
12099
12817
|
...options?.on === void 0 ? {} : { on: options.on },
|
|
12100
12818
|
...options?.error === void 0 ? {} : { error: options.error }
|
|
12101
12819
|
});
|
|
12102
|
-
const look = this.#create("look", this.#look.bind(this),
|
|
12103
|
-
const read = this.#create("read", this.#read.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12820
|
+
const look = this.#create("look", this.#look.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12821
|
+
const read = this.#create("read", this.#read.bind(this, "read", (reading) => reading.markdown()), BROWSER_TOOL_CUT_FOOTER);
|
|
12822
|
+
const plain = this.#create("plain", this.#read.bind(this, "plain", (reading) => reading.text()), BROWSER_TOOL_CUT_FOOTER);
|
|
12104
12823
|
const click = this.#create("click", this.#click.bind(this), BROWSER_TOOL_VIEW_FOOTER);
|
|
12105
12824
|
const type = this.#create("type", this.#type.bind(this), BROWSER_TOOL_VIEW_FOOTER);
|
|
12106
12825
|
const wait = this.#create("wait", this.#wait.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12107
12826
|
this.#native = Object.freeze(options?.page === void 0 ? [
|
|
12108
12827
|
look,
|
|
12109
12828
|
read,
|
|
12829
|
+
plain,
|
|
12110
12830
|
click,
|
|
12111
12831
|
type,
|
|
12112
12832
|
wait
|
|
12113
12833
|
] : [
|
|
12114
12834
|
look,
|
|
12115
12835
|
read,
|
|
12836
|
+
plain,
|
|
12116
12837
|
click,
|
|
12117
12838
|
type,
|
|
12118
12839
|
this.#create("press", this.#press.bind(this), BROWSER_TOOL_VIEW_FOOTER),
|
|
@@ -12415,44 +13136,74 @@ var BrowserToolset = class {
|
|
|
12415
13136
|
if ((0, _orkestrel_contract.isString)(secret) && secret.length > 0) message = message.replaceAll(JSON.stringify(secret), "[redacted]").replaceAll(secret, "[redacted]");
|
|
12416
13137
|
return clause === void 0 ? message : boundBrowserText(message, this.#limit, clause);
|
|
12417
13138
|
}
|
|
12418
|
-
async #look(
|
|
12419
|
-
|
|
13139
|
+
async #look(args, context) {
|
|
13140
|
+
const offset = this.#readOffset(args);
|
|
13141
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13142
|
+
const outline = await this.#race(this.#cursor.elements.outline({
|
|
13143
|
+
signal: context.signal,
|
|
13144
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
13145
|
+
search
|
|
13146
|
+
}), "");
|
|
13147
|
+
const total = outline.text.length;
|
|
13148
|
+
const start = offset < total ? offset : 0;
|
|
13149
|
+
const note = this.#drainNote();
|
|
13150
|
+
const room = this.#limit - note.length;
|
|
13151
|
+
const count = outline.matches.length;
|
|
13152
|
+
const block = start === 0 ? renderBrowserMatches(`${count} ${count === 1 ? "element matches" : "elements match"} ${JSON.stringify(search)}:`, outline.matches, room) : "";
|
|
13153
|
+
const space = room - block.length;
|
|
13154
|
+
const slice = space < 1 ? {
|
|
13155
|
+
text: "",
|
|
13156
|
+
offset: start,
|
|
13157
|
+
total
|
|
13158
|
+
} : extractBrowserSlice(outline.text, start, space);
|
|
13159
|
+
return this.#pageSlice("look", note, slice, block);
|
|
12420
13160
|
}
|
|
12421
|
-
async #read(args, context) {
|
|
12422
|
-
const offset = args
|
|
12423
|
-
if (!(0, _orkestrel_contract.isInteger)(offset) || offset < 0) throw new BrowserError("The offset parameter must be a non-negative integer.", "BROWSER_TOOLSET_ARGUMENT", { key: "offset" });
|
|
13161
|
+
async #read(name, project, args, context) {
|
|
13162
|
+
const offset = this.#readOffset(args);
|
|
12424
13163
|
const view = this.#cursor;
|
|
12425
13164
|
const retained = this.#reading;
|
|
12426
13165
|
let reading = retained?.reading;
|
|
12427
|
-
if (offset === 0 || retained === void 0 || reading === void 0 || retained.view !== view || reading.stale) {
|
|
13166
|
+
if (offset === 0 || retained === void 0 || reading === void 0 || retained.name !== name || retained.view !== view || reading.stale) {
|
|
12428
13167
|
reading = await this.#race(view.read({ signal: context.signal }), "", context.signal);
|
|
12429
13168
|
this.#reading = {
|
|
12430
13169
|
view,
|
|
12431
|
-
reading
|
|
13170
|
+
reading,
|
|
13171
|
+
name
|
|
12432
13172
|
};
|
|
12433
13173
|
}
|
|
12434
|
-
const
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
}).total ? offset : 0;
|
|
12438
|
-
const note = boundBrowserText(this.#drain(), Math.max(1, this.#limit - 1), BROWSER_TOOL_CUT_FOOTER);
|
|
13174
|
+
const whole = project(reading);
|
|
13175
|
+
const start = reading === retained?.reading && offset < whole.total ? offset : 0;
|
|
13176
|
+
const note = this.#drainNote();
|
|
12439
13177
|
const room = this.#limit - note.length;
|
|
12440
|
-
const
|
|
13178
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13179
|
+
const matches = start === 0 ? scanBrowserText(whole.text, search) : [];
|
|
13180
|
+
const count = matches.length;
|
|
13181
|
+
const block = renderBrowserMatches(`${count} ${count === 1 ? "line matches" : "lines match"} ${JSON.stringify(search)}:`, matches.map((match) => `[${match.offset}] ${match.text}`), room);
|
|
13182
|
+
const space = room - block.length;
|
|
13183
|
+
const slice = space < 1 ? {
|
|
12441
13184
|
text: "",
|
|
12442
13185
|
offset: start,
|
|
12443
|
-
total:
|
|
12444
|
-
} :
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
13186
|
+
total: whole.total
|
|
13187
|
+
} : extractBrowserSlice(whole.text, start, space);
|
|
13188
|
+
return this.#pageSlice(name, note, slice, block);
|
|
13189
|
+
}
|
|
13190
|
+
#readOffset(args) {
|
|
13191
|
+
const offset = args["offset"] ?? 0;
|
|
13192
|
+
if (!(0, _orkestrel_contract.isInteger)(offset) || offset < 0) throw new BrowserError("The offset parameter must be a non-negative integer.", "BROWSER_TOOLSET_ARGUMENT", { key: "offset" });
|
|
13193
|
+
return offset;
|
|
13194
|
+
}
|
|
13195
|
+
#drainNote() {
|
|
13196
|
+
return boundBrowserText(this.#drain(), Math.max(1, this.#limit - 1), BROWSER_TOOL_CUT_FOOTER);
|
|
13197
|
+
}
|
|
13198
|
+
#pageSlice(name, note, slice, block = "") {
|
|
13199
|
+
if (note === "" && slice.text === "" && slice.offset < slice.total) throw new BrowserError(`The ${name} limit of ${this.#limit} characters cannot hold the next character at offset ${slice.offset}; raise the toolset limit.`, "BROWSER_TOOLSET_LIMIT", {
|
|
12449
13200
|
limit: this.#limit,
|
|
12450
|
-
offset:
|
|
13201
|
+
offset: slice.offset
|
|
12451
13202
|
});
|
|
12452
13203
|
const end = slice.offset + slice.text.length;
|
|
12453
13204
|
const more = end < slice.total;
|
|
12454
|
-
if (
|
|
12455
|
-
return [`${note}${slice.text}`, `\n\n[characters ${
|
|
13205
|
+
if (slice.offset === 0 && !more) return [`${note}${block}${slice.text}`, ""];
|
|
13206
|
+
return [`${note}${block}${slice.text}`, `\n\n[characters ${slice.offset}–${end} of ${slice.total}${more ? `; call ${name} with offset ${end} for more` : ""}]`];
|
|
12456
13207
|
}
|
|
12457
13208
|
async #click(args, context) {
|
|
12458
13209
|
const turn = await this.#acquire(context.signal);
|
|
@@ -12555,7 +13306,7 @@ var BrowserToolset = class {
|
|
|
12555
13306
|
return [await this.#settle(page.keyboard.press(key), action, context.signal, true, record, popups, observation, key === "Enter" ? {
|
|
12556
13307
|
explicit: false,
|
|
12557
13308
|
action
|
|
12558
|
-
} : void 0), ""];
|
|
13309
|
+
} : void 0, true), ""];
|
|
12559
13310
|
} finally {
|
|
12560
13311
|
record?.destroy();
|
|
12561
13312
|
popups?.destroy();
|
|
@@ -12594,7 +13345,7 @@ var BrowserToolset = class {
|
|
|
12594
13345
|
const action = `Navigated to ${result.url}`;
|
|
12595
13346
|
return [renderBrowserReceipt({
|
|
12596
13347
|
action,
|
|
12597
|
-
view: await this.#capture(action, context.signal)
|
|
13348
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12598
13349
|
}), ""];
|
|
12599
13350
|
} finally {
|
|
12600
13351
|
if (this.#navigating === page) this.#navigating = void 0;
|
|
@@ -12605,22 +13356,25 @@ var BrowserToolset = class {
|
|
|
12605
13356
|
async #wait(args, context) {
|
|
12606
13357
|
const text = readBrowserToolString(args, "text");
|
|
12607
13358
|
const seconds = args["timeout"];
|
|
13359
|
+
const absent = args["absent"];
|
|
13360
|
+
if (absent !== void 0 && !(0, _orkestrel_contract.isBoolean)(absent)) throw new BrowserError("The absent parameter must be a boolean.", "BROWSER_TOOLSET_ARGUMENT", { key: "absent" });
|
|
12608
13361
|
if (seconds !== void 0 && (!(0, _orkestrel_contract.isFiniteNumber)(seconds) || seconds <= 0)) throw new BrowserError("The timeout parameter must be a positive number of seconds.", "BROWSER_TOOLSET_ARGUMENT", { key: "timeout" });
|
|
12609
13362
|
const timeout = Math.min(seconds === void 0 ? BROWSER_TOOL_TIMEOUT_MS : Math.ceil(seconds * 1e3), BROWSER_TOOL_TIMEOUT_LIMIT_MS);
|
|
12610
13363
|
const quoted = JSON.stringify(text);
|
|
12611
13364
|
try {
|
|
12612
13365
|
await this.#race(this.#cursor.wait(text, {
|
|
12613
13366
|
timeout,
|
|
12614
|
-
signal: context.signal
|
|
12615
|
-
|
|
12616
|
-
|
|
13367
|
+
signal: context.signal,
|
|
13368
|
+
absent: absent === true
|
|
13369
|
+
}), `Waited for ${quoted}${absent === true ? " to leave" : ""}`, context.signal);
|
|
13370
|
+
return [`${quoted} is ${absent === true ? "not " : ""}on the page.`, ""];
|
|
12617
13371
|
} catch (error) {
|
|
12618
13372
|
if (!context.signal.aborted && isBrowserError(error) && error.code === "BROWSER_WAIT_TIMEOUT") {
|
|
12619
13373
|
this.#actions.set(context.signal, {
|
|
12620
13374
|
...this.#actions.get(context.signal),
|
|
12621
13375
|
outcome: "timeout"
|
|
12622
13376
|
});
|
|
12623
|
-
return [`${quoted} did not appear within ${timeout / 1e3} s.`, ""];
|
|
13377
|
+
return [absent === true ? `${quoted} is still on the page after ${timeout / 1e3} s.` : `${quoted} did not appear within ${timeout / 1e3} s.`, ""];
|
|
12624
13378
|
}
|
|
12625
13379
|
throw error;
|
|
12626
13380
|
}
|
|
@@ -12643,14 +13397,19 @@ var BrowserToolset = class {
|
|
|
12643
13397
|
const action = `${accept ? "Accepted" : "Dismissed"} the ${dialog.category} dialog ${JSON.stringify(dialog.message)}`;
|
|
12644
13398
|
return [renderBrowserReceipt({
|
|
12645
13399
|
action,
|
|
12646
|
-
view: await this.#capture(action, context.signal)
|
|
13400
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12647
13401
|
}), ""];
|
|
12648
13402
|
} finally {
|
|
12649
13403
|
turn.resolve();
|
|
12650
13404
|
}
|
|
12651
13405
|
}
|
|
12652
|
-
async #tabs(
|
|
12653
|
-
|
|
13406
|
+
async #tabs(args, context) {
|
|
13407
|
+
const text = (await this.#list(context.signal, true)).map((tab) => `${tab.id} ${JSON.stringify(tab.title)} ${tab.url}${tab.current ? " (current)" : ""}`).join("\n");
|
|
13408
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13409
|
+
const matches = scanBrowserText(text, search);
|
|
13410
|
+
const count = matches.length;
|
|
13411
|
+
const note = this.#drainNote();
|
|
13412
|
+
return [`${note}${renderBrowserMatches(`${count} ${count === 1 ? "tab matches" : "tabs match"} ${JSON.stringify(search)}:`, matches.map((match) => match.text), this.#limit - note.length)}${text}`, ""];
|
|
12654
13413
|
}
|
|
12655
13414
|
async #switch(args, context) {
|
|
12656
13415
|
const tab = readBrowserToolString(args, "tab");
|
|
@@ -12673,7 +13432,7 @@ var BrowserToolset = class {
|
|
|
12673
13432
|
await this.#command(front, action, front);
|
|
12674
13433
|
return [renderBrowserReceipt({
|
|
12675
13434
|
action,
|
|
12676
|
-
view: await this.#capture(action, context.signal)
|
|
13435
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12677
13436
|
}), ""];
|
|
12678
13437
|
} finally {
|
|
12679
13438
|
turn.resolve();
|
|
@@ -12682,7 +13441,7 @@ var BrowserToolset = class {
|
|
|
12682
13441
|
async #invoke(tool, synthetic, args, context) {
|
|
12683
13442
|
const turn = await this.#acquire(context.signal);
|
|
12684
13443
|
try {
|
|
12685
|
-
const input = synthetic ? Object.fromEntries(Object.entries(args).filter(([key]) => key !== "
|
|
13444
|
+
const input = synthetic ? Object.fromEntries(Object.entries(args).filter(([key]) => key !== "purpose")) : args;
|
|
12686
13445
|
const command = Promise.resolve(tool.execute(input, context));
|
|
12687
13446
|
return [renderBrowserToolOutput(await this.#command(command, `Called ${tool.name}`, command, context.signal)), ""];
|
|
12688
13447
|
} finally {
|
|
@@ -12762,7 +13521,7 @@ var BrowserToolset = class {
|
|
|
12762
13521
|
}
|
|
12763
13522
|
return turn;
|
|
12764
13523
|
}
|
|
12765
|
-
async #settle(command, action, signal, trusted, record, popups, observation, enter) {
|
|
13524
|
+
async #settle(command, action, signal, trusted, record, popups, observation, enter, focus = false) {
|
|
12766
13525
|
let deadline;
|
|
12767
13526
|
try {
|
|
12768
13527
|
const started = record?.wait({ signal }).then(() => true, () => new Promise(() => void 0));
|
|
@@ -12798,11 +13557,13 @@ var BrowserToolset = class {
|
|
|
12798
13557
|
const unmoved = settled === void 0 && submissions.destinations.length === 0;
|
|
12799
13558
|
const status = settled?.stage === "committed" ? `the page is still loading ${settled.url}` : settled?.stage === "requested" ? `it requested ${settled.url} and the page did not change` : unmoved && submissions.prevented ? BROWSER_TOOL_HANDLED_STATUS : unmoved && submissions.submitted === false && (enter?.explicit === true || enter !== void 0 && submissions.implicit) ? "no form received the submission" : void 0;
|
|
12800
13559
|
const line = enter !== void 0 && (submissions.submitted === true || settled?.reason === "formSubmissionGet" || settled?.reason === "formSubmissionPost") ? enter.action : action;
|
|
13560
|
+
const captured = await this.#capture(line, signal, deadline);
|
|
13561
|
+
const parts = [status, focus && captured.focus !== void 0 ? `focus is on ${captured.focus}` : void 0].filter((part) => part !== void 0);
|
|
12801
13562
|
return renderBrowserReceipt({
|
|
12802
13563
|
action: line,
|
|
12803
13564
|
trusted,
|
|
12804
|
-
...
|
|
12805
|
-
view:
|
|
13565
|
+
...parts.length === 0 ? {} : { status: parts.join("; ") },
|
|
13566
|
+
view: captured.view
|
|
12806
13567
|
});
|
|
12807
13568
|
} finally {
|
|
12808
13569
|
record?.destroy();
|
|
@@ -12969,7 +13730,10 @@ var BrowserToolset = class {
|
|
|
12969
13730
|
}
|
|
12970
13731
|
}
|
|
12971
13732
|
async #capture(action, signal, deadline = performance.now() + BROWSER_TOOL_TIMEOUT_MS) {
|
|
12972
|
-
if (deadline - performance.now() < 1) return
|
|
13733
|
+
if (deadline - performance.now() < 1) return {
|
|
13734
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13735
|
+
focus: void 0
|
|
13736
|
+
};
|
|
12973
13737
|
const receipt = new AbortController();
|
|
12974
13738
|
const options = {
|
|
12975
13739
|
signal: AbortSignal.any([signal, receipt.signal]),
|
|
@@ -12977,16 +13741,34 @@ var BrowserToolset = class {
|
|
|
12977
13741
|
};
|
|
12978
13742
|
try {
|
|
12979
13743
|
const outline = await this.#bounded(this.#cursor.elements.outline(options), deadline, action);
|
|
12980
|
-
return outline === void 0 ?
|
|
13744
|
+
return outline === void 0 ? {
|
|
13745
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13746
|
+
focus: void 0
|
|
13747
|
+
} : {
|
|
13748
|
+
view: outline.text,
|
|
13749
|
+
focus: outline.focus
|
|
13750
|
+
};
|
|
12981
13751
|
} catch (error) {
|
|
12982
13752
|
if (signal.aborted || isBrowserError(error) && error.code === "BROWSER_TOOLSET_RECEIPT") throw error;
|
|
12983
|
-
if (!isBrowserElementError(error) || error.context?.["reason"] !== "GONE") return
|
|
13753
|
+
if (!isBrowserElementError(error) || error.context?.["reason"] !== "GONE") return {
|
|
13754
|
+
view: `(The view could not be read: ${(0, _orkestrel_contract.isError)(error) ? error.message : String(error)}; call look.)`,
|
|
13755
|
+
focus: void 0
|
|
13756
|
+
};
|
|
12984
13757
|
try {
|
|
12985
13758
|
const outline = await this.#bounded(this.#cursor.elements.outline(options), deadline, action);
|
|
12986
|
-
return outline === void 0 ?
|
|
13759
|
+
return outline === void 0 ? {
|
|
13760
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13761
|
+
focus: void 0
|
|
13762
|
+
} : {
|
|
13763
|
+
view: outline.text,
|
|
13764
|
+
focus: outline.focus
|
|
13765
|
+
};
|
|
12987
13766
|
} catch (retry) {
|
|
12988
13767
|
if (signal.aborted || isBrowserError(retry) && retry.code === "BROWSER_TOOLSET_RECEIPT") throw retry;
|
|
12989
|
-
return
|
|
13768
|
+
return {
|
|
13769
|
+
view: BROWSER_TOOL_CHANGED_NOTE,
|
|
13770
|
+
focus: void 0
|
|
13771
|
+
};
|
|
12990
13772
|
}
|
|
12991
13773
|
} finally {
|
|
12992
13774
|
receipt.abort(new BrowserError("the receipt settled", "BROWSER_TOOLSET_SETTLED"));
|
|
@@ -13226,6 +14008,7 @@ exports.BROWSER_ACTION_OUTCOMES = BROWSER_ACTION_OUTCOMES;
|
|
|
13226
14008
|
exports.BROWSER_ACTION_STAGES = BROWSER_ACTION_STAGES;
|
|
13227
14009
|
exports.BROWSER_CODEGEN_BINDING_NAME = BROWSER_CODEGEN_BINDING_NAME;
|
|
13228
14010
|
exports.BROWSER_CODEGEN_SOURCE = BROWSER_CODEGEN_SOURCE;
|
|
14011
|
+
exports.BROWSER_CONTEXT_LOSS_PATTERN = BROWSER_CONTEXT_LOSS_PATTERN;
|
|
13229
14012
|
exports.BROWSER_DEFAULT_TIMEOUT_MS = BROWSER_DEFAULT_TIMEOUT_MS;
|
|
13230
14013
|
exports.BROWSER_DEFAULT_VIEWPORT_HEIGHT = BROWSER_DEFAULT_VIEWPORT_HEIGHT;
|
|
13231
14014
|
exports.BROWSER_DEFAULT_VIEWPORT_WIDTH = BROWSER_DEFAULT_VIEWPORT_WIDTH;
|
|
@@ -13260,6 +14043,7 @@ exports.BROWSER_RESULT_LIMIT_SENTINEL_PREFIX = BROWSER_RESULT_LIMIT_SENTINEL_PRE
|
|
|
13260
14043
|
exports.BROWSER_RUN_ID_PATTERN = BROWSER_RUN_ID_PATTERN;
|
|
13261
14044
|
exports.BROWSER_SCHEMES = BROWSER_SCHEMES;
|
|
13262
14045
|
exports.BROWSER_SCREENSHOT_ATTRIBUTE = BROWSER_SCREENSHOT_ATTRIBUTE;
|
|
14046
|
+
exports.BROWSER_SEARCH_PATTERN = BROWSER_SEARCH_PATTERN;
|
|
13263
14047
|
exports.BROWSER_SNAPSHOT_NODE_LIMIT = BROWSER_SNAPSHOT_NODE_LIMIT;
|
|
13264
14048
|
exports.BROWSER_STABLE_FRAME_COUNT = BROWSER_STABLE_FRAME_COUNT;
|
|
13265
14049
|
exports.BROWSER_STOP_LOADING_TIMEOUT_MS = BROWSER_STOP_LOADING_TIMEOUT_MS;
|
|
@@ -13279,6 +14063,7 @@ exports.BROWSER_TOOL_TIMEOUT_LIMIT_MS = BROWSER_TOOL_TIMEOUT_LIMIT_MS;
|
|
|
13279
14063
|
exports.BROWSER_TOOL_TIMEOUT_MS = BROWSER_TOOL_TIMEOUT_MS;
|
|
13280
14064
|
exports.BROWSER_TOOL_VIEW_FOOTER = BROWSER_TOOL_VIEW_FOOTER;
|
|
13281
14065
|
exports.BROWSER_TYPED_ROLES = BROWSER_TYPED_ROLES;
|
|
14066
|
+
exports.BROWSER_WAIT_EVENTS = BROWSER_WAIT_EVENTS;
|
|
13282
14067
|
exports.BrowserAccessibility = BrowserAccessibility;
|
|
13283
14068
|
exports.BrowserClock = BrowserClock;
|
|
13284
14069
|
exports.BrowserCodegen = BrowserCodegen;
|
|
@@ -13331,6 +14116,7 @@ exports.buildBrowserJourney = buildBrowserJourney;
|
|
|
13331
14116
|
exports.bytesToText = bytesToText;
|
|
13332
14117
|
exports.collectBrowserJourneyBindings = collectBrowserJourneyBindings;
|
|
13333
14118
|
exports.collectBrowserJourneyTextBindings = collectBrowserJourneyTextBindings;
|
|
14119
|
+
exports.collectBrowserWords = collectBrowserWords;
|
|
13334
14120
|
exports.compileActionabilityFunction = compileActionabilityFunction;
|
|
13335
14121
|
exports.compileBrowserBindingCleanup = compileBrowserBindingCleanup;
|
|
13336
14122
|
exports.compileBrowserBindingResult = compileBrowserBindingResult;
|
|
@@ -13455,7 +14241,9 @@ exports.readRareStringData = readRareStringData;
|
|
|
13455
14241
|
exports.renderBrowserElement = renderBrowserElement;
|
|
13456
14242
|
exports.renderBrowserJourney = renderBrowserJourney;
|
|
13457
14243
|
exports.renderBrowserJourneyFault = renderBrowserJourneyFault;
|
|
14244
|
+
exports.renderBrowserMatches = renderBrowserMatches;
|
|
13458
14245
|
exports.renderBrowserOutline = renderBrowserOutline;
|
|
14246
|
+
exports.renderBrowserOutlineRow = renderBrowserOutlineRow;
|
|
13459
14247
|
exports.renderBrowserReceipt = renderBrowserReceipt;
|
|
13460
14248
|
exports.renderBrowserRun = renderBrowserRun;
|
|
13461
14249
|
exports.renderBrowserRunResult = renderBrowserRunResult;
|
|
@@ -13463,6 +14251,8 @@ exports.renderBrowserToolOutput = renderBrowserToolOutput;
|
|
|
13463
14251
|
exports.requireBrowserReference = requireBrowserReference;
|
|
13464
14252
|
exports.requireBrowserString = requireBrowserString;
|
|
13465
14253
|
exports.resolveBrowserJourneyBinding = resolveBrowserJourneyBinding;
|
|
14254
|
+
exports.scanBrowserOutline = scanBrowserOutline;
|
|
14255
|
+
exports.scanBrowserText = scanBrowserText;
|
|
13466
14256
|
exports.settleBrowserTeardown = settleBrowserTeardown;
|
|
13467
14257
|
exports.textToBytes = textToBytes;
|
|
13468
14258
|
exports.validateBrowserAccessibilityOptions = validateBrowserAccessibilityOptions;
|