@orkestrel/browser 0.0.21 → 0.0.23
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 +7 -4
- package/dist/bin/main.js +12 -3
- package/dist/bin/main.js.map +1 -1
- 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 +957 -174
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +356 -78
- package/dist/src/core/index.d.ts +356 -78
- package/dist/src/core/index.js +951 -176
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +540 -92
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +127 -18
- package/dist/src/server/index.d.ts +127 -18
- package/dist/src/server/index.js +529 -96
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -3
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.23"
|
|
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,14 +1681,252 @@ 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}`];
|
|
1639
1931
|
const sessions = /* @__PURE__ */ new Map();
|
|
1640
1932
|
for (const node of nodes) {
|
|
@@ -1647,6 +1939,7 @@ function renderBrowserOutline(url, title, nodes, limit) {
|
|
|
1647
1939
|
}
|
|
1648
1940
|
let count = 0;
|
|
1649
1941
|
let total = 0;
|
|
1942
|
+
let focus;
|
|
1650
1943
|
for (const node of nodes) {
|
|
1651
1944
|
if (node.ignored || BROWSER_OUTLINE_OMITTED_ROLES.has(node.role ?? "")) continue;
|
|
1652
1945
|
const name = normalizeBrowserName(node.name ?? "");
|
|
@@ -1661,14 +1954,10 @@ function renderBrowserOutline(url, title, nodes, limit) {
|
|
|
1661
1954
|
}
|
|
1662
1955
|
if (node.reference === void 0) continue;
|
|
1663
1956
|
total += 1;
|
|
1957
|
+
if (node.properties["focused"] === true) focus = node;
|
|
1664
1958
|
if (count >= limit) continue;
|
|
1665
1959
|
count += 1;
|
|
1666
|
-
|
|
1667
|
-
if (node.value !== void 0 && node.value !== "") row += ` value=${JSON.stringify(String(node.value))}`;
|
|
1668
|
-
if (node.properties["checked"] === true || node.properties["checked"] === "true") row += " [checked]";
|
|
1669
|
-
if (node.properties["disabled"] === true) row += " [disabled]";
|
|
1670
|
-
if (node.tool !== void 0) row += ` [tool=${node.tool}]`;
|
|
1671
|
-
rows.push(row);
|
|
1960
|
+
rows.push(renderBrowserOutlineRow(node));
|
|
1672
1961
|
}
|
|
1673
1962
|
rows.push(`(${count} of ${total} elements)`);
|
|
1674
1963
|
return {
|
|
@@ -1676,7 +1965,9 @@ function renderBrowserOutline(url, title, nodes, limit) {
|
|
|
1676
1965
|
title,
|
|
1677
1966
|
text: rows.join("\n"),
|
|
1678
1967
|
count,
|
|
1679
|
-
total
|
|
1968
|
+
total,
|
|
1969
|
+
matches: search === void 0 ? [] : scanBrowserOutline(nodes, search).map(renderBrowserOutlineRow),
|
|
1970
|
+
focus: focus === void 0 ? void 0 : renderBrowserOutlineRow(focus)
|
|
1680
1971
|
};
|
|
1681
1972
|
}
|
|
1682
1973
|
/**
|
|
@@ -1757,27 +2048,27 @@ function renderBrowserToolOutput(value) {
|
|
|
1757
2048
|
return (rendered.success ? rendered.value ?? String(value) : "[Unserializable tool output]").slice(0, BROWSER_REGISTRY_OUTPUT_LIMIT);
|
|
1758
2049
|
}
|
|
1759
2050
|
/**
|
|
1760
|
-
* 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.
|
|
1761
2052
|
* @param schema - Authored input schema, or undefined for a parameterless tool
|
|
1762
|
-
* @returns An advertised schema, or undefined when the authored `
|
|
1763
|
-
* @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.
|
|
1764
2055
|
*/
|
|
1765
2056
|
function deriveBrowserToolSchema(schema) {
|
|
1766
2057
|
const properties = (0, _orkestrel_contract.isRecord)(schema?.["properties"]) ? schema["properties"] : {};
|
|
1767
2058
|
const required = (0, _orkestrel_contract.isArray)(schema?.["required"]) ? schema["required"].filter(_orkestrel_contract.isString) : [];
|
|
1768
|
-
if (Object.hasOwn(properties, "
|
|
2059
|
+
if (Object.hasOwn(properties, "purpose") && !required.includes("purpose")) return void 0;
|
|
1769
2060
|
if (required.length > 0) return schema;
|
|
1770
2061
|
return {
|
|
1771
2062
|
...schema,
|
|
1772
2063
|
type: "object",
|
|
1773
2064
|
properties: {
|
|
1774
2065
|
...properties,
|
|
1775
|
-
|
|
2066
|
+
purpose: {
|
|
1776
2067
|
type: "string",
|
|
1777
2068
|
description: "Describe the purpose of this action."
|
|
1778
2069
|
}
|
|
1779
2070
|
},
|
|
1780
|
-
required: ["
|
|
2071
|
+
required: ["purpose"]
|
|
1781
2072
|
};
|
|
1782
2073
|
}
|
|
1783
2074
|
/**
|
|
@@ -1789,8 +2080,8 @@ function deriveBrowserToolSchema(schema) {
|
|
|
1789
2080
|
* units, one fewer when the last would split a surrogate pair (so a limit of 1 before a pair keeps
|
|
1790
2081
|
* nothing), followed by `\n[characters 0–END of TOTAL; FOOTER]`. The footer has no default, so
|
|
1791
2082
|
* every caller states what the cut result's reader does next: the toolset passes
|
|
1792
|
-
* `BROWSER_TOOL_VIEW_FOOTER` for
|
|
1793
|
-
* 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.
|
|
1794
2085
|
*
|
|
1795
2086
|
* @param text - The page-authored or composed string
|
|
1796
2087
|
* @param limit - The most characters kept before the footer, a positive integer
|
|
@@ -1827,8 +2118,8 @@ function boundBrowserText(text, limit, footer) {
|
|
|
1827
2118
|
* ```ts
|
|
1828
2119
|
* import { BROWSER_TOOL_COPY, validateBrowserToolArguments } from '@orkestrel/browser'
|
|
1829
2120
|
*
|
|
1830
|
-
* validateBrowserToolArguments(BROWSER_TOOL_COPY.look, {
|
|
1831
|
-
* // 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.'
|
|
1832
2123
|
* ```
|
|
1833
2124
|
*/
|
|
1834
2125
|
function validateBrowserToolArguments(definition, args) {
|
|
@@ -3608,7 +3899,7 @@ function renderBrowserJourney(journey) {
|
|
|
3608
3899
|
return `${step.id} type ${secret ? "(secret)" : JSON.stringify(text)}${suffix} into ${name}${step.arguments["submit"] === true ? ", submit" : ""}`;
|
|
3609
3900
|
}
|
|
3610
3901
|
case "press": return `${step.id} press ${text}${suffix}`;
|
|
3611
|
-
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" : ""}`;
|
|
3612
3903
|
case "dialog": return `${step.id} dialog ${step.arguments["accept"] === true ? "accept" : "dismiss"}${binding === void 0 ? "" : ` ${JSON.stringify(text)}${suffix}`}`;
|
|
3613
3904
|
case "switch": return `${step.id} switch ${JSON.stringify(step.tab?.title)} ${step.tab?.url}`;
|
|
3614
3905
|
case "unresolved": return `${step.id} unresolved: ${step.gap}`;
|
|
@@ -3916,46 +4207,55 @@ function buildBrowserJourney(steps, options) {
|
|
|
3916
4207
|
//#endregion
|
|
3917
4208
|
//#region src/core/compilers.ts
|
|
3918
4209
|
/**
|
|
3919
|
-
* 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.
|
|
3920
4215
|
* @param deadline - Maximum time in milliseconds
|
|
3921
4216
|
* @param key - Isolated-world property owning this observer
|
|
3922
|
-
* @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
|
|
3923
4218
|
* @returns Promise expression resolving true on a match or change, false at the deadline
|
|
3924
4219
|
*/
|
|
3925
4220
|
function compileQueryWaitExpression(deadline, key, predicate) {
|
|
3926
4221
|
return `new Promise((resolve) => {
|
|
3927
|
-
let
|
|
4222
|
+
let task
|
|
3928
4223
|
let timer
|
|
3929
4224
|
let observer
|
|
4225
|
+
const events = new AbortController()
|
|
3930
4226
|
const finish = (value) => {
|
|
3931
4227
|
observer?.disconnect()
|
|
3932
|
-
|
|
4228
|
+
events.abort()
|
|
4229
|
+
if (task !== undefined) clearTimeout(task)
|
|
3933
4230
|
clearTimeout(timer)
|
|
3934
4231
|
delete globalThis[${JSON.stringify(key)}]
|
|
3935
4232
|
resolve(value)
|
|
3936
4233
|
}
|
|
3937
4234
|
const check = () => {
|
|
3938
|
-
|
|
4235
|
+
task = undefined
|
|
3939
4236
|
if (${predicate ?? "true"}) finish(true)
|
|
3940
4237
|
}
|
|
3941
4238
|
globalThis[${JSON.stringify(key)}] = () => finish(false)
|
|
3942
|
-
|
|
3943
|
-
if (
|
|
3944
|
-
}
|
|
4239
|
+
const schedule = () => {
|
|
4240
|
+
if (task === undefined) task = setTimeout(check, 0)
|
|
4241
|
+
}
|
|
4242
|
+
observer = new MutationObserver(schedule)
|
|
3945
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 })
|
|
3946
4245
|
timer = setTimeout(() => finish(false), ${JSON.stringify(deadline)})
|
|
3947
4246
|
${predicate === void 0 ? "" : "check()"}
|
|
3948
4247
|
})`;
|
|
3949
4248
|
}
|
|
3950
4249
|
/**
|
|
3951
|
-
* Compiles a visible
|
|
4250
|
+
* Compiles a wait for the presence or absence of visible text, coalesced by tasks.
|
|
3952
4251
|
* @param text - Text to find in the main document body
|
|
3953
4252
|
* @param deadline - Maximum time in milliseconds
|
|
3954
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`
|
|
3955
4255
|
* @returns Mutation-driven promise expression with one deadline
|
|
3956
4256
|
*/
|
|
3957
|
-
function compileTextWaitExpression(text, deadline, key) {
|
|
3958
|
-
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)})`);
|
|
3959
4259
|
}
|
|
3960
4260
|
/**
|
|
3961
4261
|
* Compiles text selection or select-option assignment against the resolved element.
|
|
@@ -4267,20 +4567,337 @@ ${expression}
|
|
|
4267
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 })()`;
|
|
4268
4568
|
}
|
|
4269
4569
|
/**
|
|
4270
|
-
* 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.
|
|
4271
4571
|
*
|
|
4272
4572
|
* @remarks
|
|
4273
|
-
* The
|
|
4274
|
-
*
|
|
4275
|
-
*
|
|
4276
|
-
*
|
|
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}.
|
|
4277
4577
|
*
|
|
4278
4578
|
* @returns A function declaration source
|
|
4279
4579
|
*/
|
|
4280
4580
|
function compileReadFunction() {
|
|
4281
|
-
return `function() {
|
|
4282
|
-
const
|
|
4283
|
-
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
|
|
4284
4901
|
}`;
|
|
4285
4902
|
}
|
|
4286
4903
|
/**
|
|
@@ -4434,7 +5051,7 @@ function compileActionabilityFunction(options) {
|
|
|
4434
5051
|
if (${JSON.stringify(options.enabled === true)} && this.matches(':disabled')) throw new Error('Element is disabled')
|
|
4435
5052
|
if (${JSON.stringify(options.editable === true)} && (this.matches('[readonly]') || (!this.isContentEditable && !('value' in this)))) throw new Error('Element is not editable')
|
|
4436
5053
|
let previous
|
|
4437
|
-
for (let index = 0; index < 2; index += 1) {
|
|
5054
|
+
for (let index = 0; index < ${options.stable === true ? 2 : 0}; index += 1) {
|
|
4438
5055
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
4439
5056
|
const rect = this.getBoundingClientRect()
|
|
4440
5057
|
const current = [rect.x, rect.y, rect.width, rect.height]
|
|
@@ -5567,7 +6184,7 @@ var BrowserCodegen = class {
|
|
|
5567
6184
|
* title: 'Example',
|
|
5568
6185
|
* html: '<nav>Menu</nav><main><p>Body</p></main>',
|
|
5569
6186
|
* })
|
|
5570
|
-
* reading.markdown() // { text: 'Body', offset: 0, total: 4 }
|
|
6187
|
+
* reading.markdown({ distill: true }) // { text: 'Body', offset: 0, total: 4 }
|
|
5571
6188
|
* ```
|
|
5572
6189
|
*/
|
|
5573
6190
|
var BrowserReading = class {
|
|
@@ -5599,7 +6216,7 @@ var BrowserReading = class {
|
|
|
5599
6216
|
return this.#navigation !== void 0 && this.#navigation() !== this.#epoch;
|
|
5600
6217
|
}
|
|
5601
6218
|
markdown(options) {
|
|
5602
|
-
const distill = options?.distill ??
|
|
6219
|
+
const distill = options?.distill ?? false;
|
|
5603
6220
|
let text = this.#markdowns.get(distill);
|
|
5604
6221
|
if (text === void 0) {
|
|
5605
6222
|
text = (0, _orkestrel_markdown.renderMarkdown)((0, _orkestrel_markdown.htmlToMarkdown)(this.#source(distill).document));
|
|
@@ -5608,7 +6225,7 @@ var BrowserReading = class {
|
|
|
5608
6225
|
return extractBrowserSlice(text, options?.offset, options?.limit);
|
|
5609
6226
|
}
|
|
5610
6227
|
text(options) {
|
|
5611
|
-
const distill = options?.distill ??
|
|
6228
|
+
const distill = options?.distill ?? false;
|
|
5612
6229
|
let text = this.#texts.get(distill);
|
|
5613
6230
|
if (text === void 0) {
|
|
5614
6231
|
text = (0, _orkestrel_html.renderText)(this.#source(distill).document);
|
|
@@ -5617,7 +6234,10 @@ var BrowserReading = class {
|
|
|
5617
6234
|
return extractBrowserSlice(text, options?.offset, options?.limit);
|
|
5618
6235
|
}
|
|
5619
6236
|
#source(distill) {
|
|
5620
|
-
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);
|
|
5621
6241
|
this.#distilled ??= this.#html.distill({ base: this.#url });
|
|
5622
6242
|
return this.#distilled;
|
|
5623
6243
|
}
|
|
@@ -5688,8 +6308,7 @@ var BrowserPageElement = class {
|
|
|
5688
6308
|
async read(options) {
|
|
5689
6309
|
const epoch = this.#epoch();
|
|
5690
6310
|
const capture = await this.#call(`function() {
|
|
5691
|
-
const capture = (${compileReadFunction()})()
|
|
5692
|
-
capture.html = this.outerHTML
|
|
6311
|
+
const capture = (${compileReadFunction()})(this)
|
|
5693
6312
|
return ${compileGuardedEvaluateExpression("capture", BROWSER_RESULT_LIMIT)}
|
|
5694
6313
|
}`, options);
|
|
5695
6314
|
if (!(0, _orkestrel_contract.isRecord)(capture)) throw new BrowserElementError(this.reference, "UNKNOWN", "returned an invalid reading");
|
|
@@ -5890,6 +6509,7 @@ var BrowserPageElement = class {
|
|
|
5890
6509
|
}
|
|
5891
6510
|
async #pointer(options) {
|
|
5892
6511
|
this.#assert(options);
|
|
6512
|
+
await this.#input.page.send("Page.bringToFront", void 0, options);
|
|
5893
6513
|
await this.#input.client.send("DOM.scrollIntoViewIfNeeded", { backendNodeId: this.#input.backend }, {
|
|
5894
6514
|
session: this.#input.node.session,
|
|
5895
6515
|
...options
|
|
@@ -6010,7 +6630,7 @@ var BrowserElementManager = class {
|
|
|
6010
6630
|
...options
|
|
6011
6631
|
});
|
|
6012
6632
|
this.#assertCapture(this.#input.page.id, epoch);
|
|
6013
|
-
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);
|
|
6014
6634
|
} catch (error) {
|
|
6015
6635
|
if (options?.signal?.aborted === true || this.#changes === changes) throw error;
|
|
6016
6636
|
this.#lifetime.signal.throwIfAborted();
|
|
@@ -6100,34 +6720,52 @@ var BrowserElementManager = class {
|
|
|
6100
6720
|
const signal = options?.signal === void 0 ? this.#lifetime.signal : AbortSignal.any([options.signal, this.#lifetime.signal]);
|
|
6101
6721
|
while (true) {
|
|
6102
6722
|
signal.throwIfAborted();
|
|
6103
|
-
const remaining = Math.max(0, end - performance.now());
|
|
6104
|
-
const call = {
|
|
6105
|
-
timeout: remaining,
|
|
6106
|
-
signal
|
|
6107
|
-
};
|
|
6108
|
-
await this.#input.ready(call);
|
|
6109
|
-
const frame = this.#scopeFrame(query.within);
|
|
6110
|
-
const session = await this.#input.resolve(frame);
|
|
6111
|
-
const context = await this.#input.world(frame, session, call);
|
|
6112
6723
|
const key = `__browserQueryWait${++this.#sequence}`;
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
returnByValue: true,
|
|
6117
|
-
awaitPromise: true
|
|
6118
|
-
}, {
|
|
6119
|
-
session,
|
|
6120
|
-
timeout: remaining + 1e3,
|
|
6121
|
-
signal
|
|
6122
|
-
}).then((result) => ({ result }), (error) => ({ error }));
|
|
6724
|
+
let session;
|
|
6725
|
+
let context;
|
|
6726
|
+
const changes = this.#changes;
|
|
6123
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 }));
|
|
6124
6753
|
const found = await this.find(query, call);
|
|
6125
6754
|
if (options?.absent === true ? found.length === 0 : found.length > 0) return found;
|
|
6126
6755
|
const outcome = await pending;
|
|
6127
6756
|
if ("error" in outcome) throw outcome.error;
|
|
6128
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
|
+
});
|
|
6129
6767
|
} finally {
|
|
6130
|
-
await this.#input.client.send("Runtime.evaluate", {
|
|
6768
|
+
if (context !== void 0 && session !== void 0) await this.#input.client.send("Runtime.evaluate", {
|
|
6131
6769
|
expression: `globalThis[${JSON.stringify(key)}]?.()`,
|
|
6132
6770
|
contextId: context,
|
|
6133
6771
|
returnByValue: true
|
|
@@ -6138,6 +6776,11 @@ var BrowserElementManager = class {
|
|
|
6138
6776
|
}
|
|
6139
6777
|
}
|
|
6140
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
|
+
}
|
|
6141
6784
|
element(reference) {
|
|
6142
6785
|
const canonical = parseBrowserReference(reference);
|
|
6143
6786
|
return [...this.#records.values()].find((entry) => entry.node.reference === canonical)?.element;
|
|
@@ -6645,7 +7288,7 @@ var BrowserRegistry = class {
|
|
|
6645
7288
|
pending.reject(error);
|
|
6646
7289
|
}
|
|
6647
7290
|
async #executeAdopted(tool, synthetic, input, context) {
|
|
6648
|
-
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;
|
|
6649
7292
|
const result = await this.execute(tool, parameters, { signal: context.signal });
|
|
6650
7293
|
if (result.status !== "Completed") throw new BrowserError(`Browser tool ${result.status}: ${result.error ?? "No error description"}`.slice(0, BROWSER_REGISTRY_OUTPUT_LIMIT));
|
|
6651
7294
|
return renderBrowserToolOutput(result.output);
|
|
@@ -8962,6 +9605,8 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
8962
9605
|
#epoch = 0;
|
|
8963
9606
|
#floor = 0;
|
|
8964
9607
|
#closed = false;
|
|
9608
|
+
#waitRelease = new AbortController();
|
|
9609
|
+
#textWaits = /* @__PURE__ */ new Map();
|
|
8965
9610
|
#codegen;
|
|
8966
9611
|
#registry;
|
|
8967
9612
|
#codegenStart = new BrowserTransition();
|
|
@@ -9012,6 +9657,7 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9012
9657
|
session: sessionId,
|
|
9013
9658
|
resolve: this.#resolveFrameSession.bind(this),
|
|
9014
9659
|
world: this.#world.bind(this),
|
|
9660
|
+
recover: this.#recoverReadiness.bind(this),
|
|
9015
9661
|
reference: this.#reference,
|
|
9016
9662
|
ready: this.#ready.bind(this)
|
|
9017
9663
|
});
|
|
@@ -9075,6 +9721,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9075
9721
|
}
|
|
9076
9722
|
async wait(text, options) {
|
|
9077
9723
|
this.assert();
|
|
9724
|
+
options = {
|
|
9725
|
+
...options,
|
|
9726
|
+
signal: options?.signal === void 0 ? this.#waitRelease.signal : AbortSignal.any([options.signal, this.#waitRelease.signal])
|
|
9727
|
+
};
|
|
9078
9728
|
const timeout = options?.timeout ?? 3e4;
|
|
9079
9729
|
validateBrowserTimeout(timeout);
|
|
9080
9730
|
const end = performance.now() + timeout;
|
|
@@ -9087,9 +9737,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9087
9737
|
});
|
|
9088
9738
|
const context = await this.#world(this.id, this.#sessionId, options);
|
|
9089
9739
|
const key = `__browserTextWait${++this.#waitSequence}`;
|
|
9740
|
+
this.#textWaits.set(key, context);
|
|
9090
9741
|
try {
|
|
9091
9742
|
if (readEvaluationResult(await this.send("Runtime.evaluate", {
|
|
9092
|
-
expression: compileTextWaitExpression(text, remaining, key),
|
|
9743
|
+
expression: compileTextWaitExpression(text, remaining, key, options?.absent === true),
|
|
9093
9744
|
contextId: context,
|
|
9094
9745
|
returnByValue: true,
|
|
9095
9746
|
awaitPromise: true
|
|
@@ -9103,23 +9754,20 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9103
9754
|
return;
|
|
9104
9755
|
} catch (error) {
|
|
9105
9756
|
if (options?.signal?.aborted === true) {
|
|
9106
|
-
await this
|
|
9107
|
-
expression: `globalThis[${JSON.stringify(key)}]?.()`,
|
|
9108
|
-
contextId: context,
|
|
9109
|
-
returnByValue: true
|
|
9110
|
-
}, { timeout: 1e3 }).catch(() => void 0);
|
|
9757
|
+
if (!this.#closed) await this.#releaseTextWait(key, context);
|
|
9111
9758
|
throw options.signal.reason;
|
|
9112
9759
|
}
|
|
9113
|
-
if (!(0, _orkestrel_contract.isError)(error) ||
|
|
9114
|
-
this.#dom = void 0;
|
|
9760
|
+
if (!(0, _orkestrel_contract.isError)(error) || !BROWSER_CONTEXT_LOSS_PATTERN.test(error.message)) throw error;
|
|
9115
9761
|
if (performance.now() >= end) throw new BrowserError("Browser text wait timed out", "BROWSER_WAIT_TIMEOUT", {
|
|
9116
9762
|
text,
|
|
9117
9763
|
timeout
|
|
9118
9764
|
});
|
|
9119
|
-
await this.#
|
|
9765
|
+
await this.#recoverReadiness({
|
|
9120
9766
|
...options,
|
|
9121
9767
|
timeout: Math.max(0, end - performance.now())
|
|
9122
9768
|
});
|
|
9769
|
+
} finally {
|
|
9770
|
+
this.#textWaits.delete(key);
|
|
9123
9771
|
}
|
|
9124
9772
|
}
|
|
9125
9773
|
}
|
|
@@ -9507,6 +10155,8 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9507
10155
|
return release;
|
|
9508
10156
|
}
|
|
9509
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)));
|
|
9510
10160
|
this.#unhold();
|
|
9511
10161
|
this.#announcement.reject(new BrowserError("Browser session ended"));
|
|
9512
10162
|
this.#client.unsubscribe("Page.lifecycleEvent", this.#lifecycleHandler, this.#sessionId);
|
|
@@ -9556,6 +10206,16 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9556
10206
|
}
|
|
9557
10207
|
this.#steps.destroy();
|
|
9558
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
|
+
}
|
|
9559
10219
|
#frame(frame) {
|
|
9560
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));
|
|
9561
10221
|
}
|
|
@@ -9599,6 +10259,10 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9599
10259
|
#rejectSeed(seed) {
|
|
9600
10260
|
if (this.#seed === seed) this.#seed = void 0;
|
|
9601
10261
|
}
|
|
10262
|
+
#recoverReadiness(options) {
|
|
10263
|
+
this.#dom = void 0;
|
|
10264
|
+
return this.#parkReadiness(options);
|
|
10265
|
+
}
|
|
9602
10266
|
async #seedReadiness(options) {
|
|
9603
10267
|
const document = this.#floor;
|
|
9604
10268
|
const context = await this.#world(this.id, this.#sessionId, options);
|
|
@@ -9646,24 +10310,44 @@ var BrowserPage = class BrowserPage extends BrowserFrame {
|
|
|
9646
10310
|
this.#dom = this.#loader ?? "initial";
|
|
9647
10311
|
for (const id of this.#readiness.keys()) this.#settleReadiness(id)?.resolve();
|
|
9648
10312
|
}
|
|
9649
|
-
#world(frame, session, options) {
|
|
10313
|
+
async #world(frame, session, options) {
|
|
10314
|
+
options?.signal?.throwIfAborted();
|
|
9650
10315
|
const cached = this.#worlds.get(frame);
|
|
9651
10316
|
if (cached !== void 0) return Promise.resolve(cached.context);
|
|
9652
10317
|
const existing = this.#creating.get(frame);
|
|
9653
|
-
if (existing !== void 0) return existing.promise;
|
|
10318
|
+
if (existing !== void 0) return await this.#awaitWorld(existing.promise, options);
|
|
9654
10319
|
const pending = this.#client.send("Page.createIsolatedWorld", {
|
|
9655
10320
|
frameId: frame,
|
|
9656
10321
|
worldName: BROWSER_FRAME_WORLD_NAME
|
|
9657
10322
|
}, {
|
|
9658
10323
|
session,
|
|
9659
|
-
|
|
10324
|
+
signal: this.#waitRelease.signal
|
|
9660
10325
|
}).then(this.#decodeWorld.bind(this, frame));
|
|
9661
10326
|
this.#creating.set(frame, {
|
|
9662
10327
|
session,
|
|
9663
10328
|
promise: pending
|
|
9664
10329
|
});
|
|
9665
10330
|
pending.then(this.#publishWorld.bind(this, frame, session, pending), this.#settleWorld.bind(this, frame, pending));
|
|
9666
|
-
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);
|
|
9667
10351
|
}
|
|
9668
10352
|
#decodeWorld(frame, world) {
|
|
9669
10353
|
return readBrowserWorld(world, frame);
|
|
@@ -10500,6 +11184,7 @@ var BrowserContext = class {
|
|
|
10500
11184
|
#viewport;
|
|
10501
11185
|
#writer;
|
|
10502
11186
|
#downloads;
|
|
11187
|
+
#allocator;
|
|
10503
11188
|
#emitter;
|
|
10504
11189
|
#cookies;
|
|
10505
11190
|
#permissions;
|
|
@@ -10519,6 +11204,7 @@ var BrowserContext = class {
|
|
|
10519
11204
|
this.#viewport = viewport;
|
|
10520
11205
|
this.#writer = writer;
|
|
10521
11206
|
this.#downloads = downloads;
|
|
11207
|
+
this.#allocator = options?.reference;
|
|
10522
11208
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
10523
11209
|
...options?.on !== void 0 ? { on: options.on } : {},
|
|
10524
11210
|
...options?.error !== void 0 ? { error: options.error } : {}
|
|
@@ -10655,7 +11341,7 @@ var BrowserContext = class {
|
|
|
10655
11341
|
sessionId = await this.#openSession(targetId);
|
|
10656
11342
|
await this.#enableSession(sessionId);
|
|
10657
11343
|
const frameId = await this.#mainFrame(sessionId);
|
|
10658
|
-
page = new BrowserPage(this.#client, targetId, sessionId, this.#writer, url, frameId, this.#id, void 0, options, this.#nextReference.bind(this), ready);
|
|
11344
|
+
page = new BrowserPage(this.#client, targetId, sessionId, this.#writer, url, frameId, this.#id, void 0, options, this.#allocator ?? this.#nextReference.bind(this), ready);
|
|
10659
11345
|
this.#observe(page);
|
|
10660
11346
|
await this.#configurePage(page);
|
|
10661
11347
|
await this.#emulation.attach(page);
|
|
@@ -10674,7 +11360,7 @@ var BrowserContext = class {
|
|
|
10674
11360
|
sessionId = await this.#openSession(targetId);
|
|
10675
11361
|
await this.#enableSession(sessionId);
|
|
10676
11362
|
const frameId = await this.#mainFrame(sessionId);
|
|
10677
|
-
page = new BrowserPage(this.#client, targetId, sessionId, this.#writer, url, frameId, this.#id, void 0, void 0, this.#nextReference.bind(this), ready);
|
|
11363
|
+
page = new BrowserPage(this.#client, targetId, sessionId, this.#writer, url, frameId, this.#id, void 0, void 0, this.#allocator ?? this.#nextReference.bind(this), ready);
|
|
10678
11364
|
this.#observe(page);
|
|
10679
11365
|
await this.#configurePage(page);
|
|
10680
11366
|
await this.#emulation.attach(page);
|
|
@@ -11494,7 +12180,7 @@ function createBrowserReading(input) {
|
|
|
11494
12180
|
*
|
|
11495
12181
|
* const toolset = createBrowserToolset(page, { context })
|
|
11496
12182
|
* await toolset.start()
|
|
11497
|
-
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: {
|
|
12183
|
+
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'cart' } })
|
|
11498
12184
|
* ```
|
|
11499
12185
|
*
|
|
11500
12186
|
* @example Drive a page with a small model
|
|
@@ -11508,8 +12194,8 @@ function createBrowserReading(input) {
|
|
|
11508
12194
|
* const system =
|
|
11509
12195
|
* 'You control a web browser with tools and must call a tool before you answer. ' +
|
|
11510
12196
|
* 'The first message shows the page as look returns it; references such as e4 name its elements. ' +
|
|
11511
|
-
* 'To learn a fact, call read with
|
|
11512
|
-
*
|
|
12197
|
+
* '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. ' +
|
|
12198
|
+
* "To use the site's search box, call type with its reference, the words, and submit true. " +
|
|
11513
12199
|
* 'To press a button or follow a link, call click with its reference from the latest result. Never invent a reference. ' +
|
|
11514
12200
|
* 'If text you expect has not appeared, call wait once. ' +
|
|
11515
12201
|
* 'When the task is done, answer in one short sentence.'
|
|
@@ -11519,11 +12205,11 @@ function createBrowserReading(input) {
|
|
|
11519
12205
|
* const page = await browser.create({ url: 'https://shop.example.test/' })
|
|
11520
12206
|
* const toolset = createBrowserToolset(page, { tools: createToolManager() })
|
|
11521
12207
|
* await toolset.start()
|
|
11522
|
-
* toolset.tools.tools().map((tool) => tool.name) // ['look', 'read', 'click', 'type', 'press', 'navigate', 'wait']
|
|
12208
|
+
* toolset.tools.tools().map((tool) => tool.name) // ['look', 'read', 'plain', 'click', 'type', 'press', 'navigate', 'wait']
|
|
11523
12209
|
* const seeded = await toolset.tools.execute({
|
|
11524
12210
|
* id: 'seed',
|
|
11525
12211
|
* name: 'look',
|
|
11526
|
-
* arguments: {
|
|
12212
|
+
* arguments: { search: '' },
|
|
11527
12213
|
* })
|
|
11528
12214
|
* const view = seeded.success ? String(seeded.value) : seeded.error
|
|
11529
12215
|
* const agent = createAgent(createOllama({ model: 'qwen3.5:2b-q4_K_M' }), {
|
|
@@ -11743,6 +12429,10 @@ var BrowserJourneyToolset = class {
|
|
|
11743
12429
|
const offset = args["offset"] ?? 0;
|
|
11744
12430
|
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" });
|
|
11745
12431
|
const listings = [];
|
|
12432
|
+
const headings = [];
|
|
12433
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
12434
|
+
let headingOffset = 0;
|
|
12435
|
+
let listingOffset = 0;
|
|
11746
12436
|
const faults = /* @__PURE__ */ new Set();
|
|
11747
12437
|
let position = 0;
|
|
11748
12438
|
for (;;) {
|
|
@@ -11750,11 +12440,21 @@ var BrowserJourneyToolset = class {
|
|
|
11750
12440
|
signal,
|
|
11751
12441
|
offset: position
|
|
11752
12442
|
});
|
|
11753
|
-
|
|
12443
|
+
for (const entry of page.entries) {
|
|
12444
|
+
const listing = renderBrowserJourney(entry.journey);
|
|
12445
|
+
const heading = listing.split("\n", 1)[0] ?? "";
|
|
12446
|
+
headings.push(heading);
|
|
12447
|
+
offsets.set(headingOffset, listingOffset);
|
|
12448
|
+
headingOffset += heading.length + 1;
|
|
12449
|
+
listingOffset += listing.length + 2;
|
|
12450
|
+
listings.push(listing);
|
|
12451
|
+
}
|
|
11754
12452
|
for (const fault of page.faults) {
|
|
11755
12453
|
if (faults.has(fault.name)) continue;
|
|
11756
12454
|
faults.add(fault.name);
|
|
11757
|
-
|
|
12455
|
+
const listing = renderBrowserJourneyFault(fault);
|
|
12456
|
+
listings.push(listing);
|
|
12457
|
+
listingOffset += listing.length + 2;
|
|
11758
12458
|
}
|
|
11759
12459
|
const span = page.entries.length;
|
|
11760
12460
|
position += span;
|
|
@@ -11763,7 +12463,11 @@ var BrowserJourneyToolset = class {
|
|
|
11763
12463
|
if (listings.length === 0) return BROWSER_JOURNEY_EMPTY_LISTING;
|
|
11764
12464
|
const listing = listings.join("\n\n");
|
|
11765
12465
|
const start = offset < listing.length ? offset : 0;
|
|
11766
|
-
|
|
12466
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
12467
|
+
const matches = start === 0 ? scanBrowserText(headings.join("\n"), search) : [];
|
|
12468
|
+
const count = matches.length;
|
|
12469
|
+
const block = renderBrowserMatches(`${count} ${count === 1 ? "journey matches" : "journeys match"} ${JSON.stringify(search)}:`, matches.map((match) => `[${offsets.get(match.offset)}] ${match.text}`), this.#limit);
|
|
12470
|
+
let end = Math.min(listing.length, start + this.#limit - block.length);
|
|
11767
12471
|
const last = listing.charCodeAt(end - 1);
|
|
11768
12472
|
if (end < listing.length && last >= 55296 && last <= 56319) end -= 1;
|
|
11769
12473
|
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", {
|
|
@@ -11771,8 +12475,8 @@ var BrowserJourneyToolset = class {
|
|
|
11771
12475
|
offset: start
|
|
11772
12476
|
});
|
|
11773
12477
|
const text = listing.slice(start, end);
|
|
11774
|
-
if (start === 0 && end === listing.length) return text
|
|
11775
|
-
return `${text}\n\n[characters ${start}–${end} of ${listing.length}${end < listing.length ? `; call journeys with offset ${end} for more` : ""}]`;
|
|
12478
|
+
if (start === 0 && end === listing.length) return `${block}${text}`;
|
|
12479
|
+
return `${block}${text}\n\n[characters ${start}–${end} of ${listing.length}${end < listing.length ? `; call journeys with offset ${end} for more` : ""}]`;
|
|
11776
12480
|
}
|
|
11777
12481
|
async #edit(args, signal) {
|
|
11778
12482
|
if (this.#readonly) throw new BrowserError(BROWSER_JOURNEY_READONLY_REFUSAL, "BROWSER_JOURNEY_READONLY");
|
|
@@ -11938,7 +12642,7 @@ var BrowserJourneyToolset = class {
|
|
|
11938
12642
|
const performed = await this.#toolset.perform({
|
|
11939
12643
|
id: "journey",
|
|
11940
12644
|
name: "look",
|
|
11941
|
-
arguments: {
|
|
12645
|
+
arguments: { search: "" }
|
|
11942
12646
|
}, { signal });
|
|
11943
12647
|
return performed.result.success ? String(performed.result.value) : performed.result.error;
|
|
11944
12648
|
}
|
|
@@ -11966,7 +12670,7 @@ var BrowserJourneyToolset = class {
|
|
|
11966
12670
|
* view's own tools beside them.
|
|
11967
12671
|
*
|
|
11968
12672
|
* @remarks
|
|
11969
|
-
* Every toolset advertises `look`, `read`, `click`, `type`, and `wait`, which need only the
|
|
12673
|
+
* Every toolset advertises `look`, `read`, `plain`, `click`, `type`, and `wait`, which need only the
|
|
11970
12674
|
* `BrowserViewInterface` it is constructed over. With `options.page` it also advertises `press`
|
|
11971
12675
|
* and `navigate`, stages the `dialog` tool while a dialog is open on the current page, follows a
|
|
11972
12676
|
* popup the current page opens and returns to the opener when the popup closes, and adopts
|
|
@@ -11980,11 +12684,18 @@ var BrowserJourneyToolset = class {
|
|
|
11980
12684
|
* refuses every tool but `dialog` while a dialog is open, forwards `ToolContext.signal` into every
|
|
11981
12685
|
* protocol call, refuses a call to a toolset tool that carries a parameter the tool does not
|
|
11982
12686
|
* advertise, and cuts every returned string and every thrown message at `limit` characters plus
|
|
11983
|
-
* a footer; the footer of a cut
|
|
11984
|
-
*
|
|
11985
|
-
* the
|
|
11986
|
-
*
|
|
11987
|
-
* the
|
|
12687
|
+
* a footer; the footer of a cut action or `dialog` receipt that carries a view names `look` as the next
|
|
12688
|
+
* call. `look`, `read`, and `plain` read the page and return pages of at most `limit`
|
|
12689
|
+
* characters and name the offset the next page starts at. A page ends after the last line break
|
|
12690
|
+
* in its window that lies past the page's start, or at the window's end when no such break fits,
|
|
12691
|
+
* without splitting a surrogate pair. The last page ends at the end of the outline or reading. A `look` whose
|
|
12692
|
+
* `search` matches referenced rows lists those rows first, and its pages reach every referenced row.
|
|
12693
|
+
* A `press` receipt names the referenced element that has focus after the key. A page tool's
|
|
12694
|
+
* error message and JSON output reach the toolset already cut at `BROWSER_REGISTRY_OUTPUT_LIMIT`
|
|
12695
|
+
* (4 096) by the registry, so a `limit` over that shows at most 4 096 characters of either; a page
|
|
12696
|
+
* tool's text output reaches the boundary whole.
|
|
12697
|
+
* `wait` with `absent` set to `true` settles when the text is not on the page, and a miss
|
|
12698
|
+
* returns the `timeout` outcome as an appearance miss does.
|
|
11988
12699
|
*
|
|
11989
12700
|
* `click`, `type`, `press`, `navigate`, and `switch` are actions: one runs at a time, in call
|
|
11990
12701
|
* order, and holds the queue until its receipt. A queued action whose signal aborts leaves
|
|
@@ -12015,7 +12726,7 @@ var BrowserJourneyToolset = class {
|
|
|
12015
12726
|
* page placement's `type` with `submit` ends its action with `and submitted the form` when a read
|
|
12016
12727
|
* recorded a submission, or when the navigation the receipt settled names `formSubmissionGet` or
|
|
12017
12728
|
* `formSubmissionPost` as its reason, whatever the read answered, and with `and pressed Enter`
|
|
12018
|
-
* otherwise. A `read`
|
|
12729
|
+
* otherwise. A `read` or `plain`
|
|
12019
12730
|
* at an offset at or past its retained reading's end restarts that reading at 0. A receipt that
|
|
12020
12731
|
* waits for a requested navigation shares one `BROWSER_TOOL_TIMEOUT_MS` deadline between that
|
|
12021
12732
|
* wait and its view capture, of which `BROWSER_TOOL_CAPTURE_MS` is reserved for the capture. The
|
|
@@ -12031,9 +12742,9 @@ var BrowserJourneyToolset = class {
|
|
|
12031
12742
|
* A page tool is skipped, with `skip` emitted, when its name is reserved (`unresolved` included), when the manager holds
|
|
12032
12743
|
* its name under a tool the toolset did not add (checked again immediately before each addition),
|
|
12033
12744
|
* when its name falls outside `BROWSER_TOOL_NAME_PATTERN`, when its parameters or, for a source
|
|
12034
|
-
* that supplies `tools()`, its selected registration declare `
|
|
12745
|
+
* that supplies `tools()`, its selected registration declare `purpose` optional, or when that
|
|
12035
12746
|
* registration is marked `debugging`. The selected registration is the main frame's, then the
|
|
12036
|
-
* earlier one. A page tool whose parameters require nothing advertises a required `
|
|
12747
|
+
* earlier one. A page tool whose parameters require nothing advertises a required `purpose`, which
|
|
12037
12748
|
* is stripped before the tool runs. Every adopted tool is advertised `untrusted`. `destroy()`
|
|
12038
12749
|
* removes only the tools the manager still holds under the instances the toolset added, never
|
|
12039
12750
|
* closes a page, and ends only what `options.release` hands over, calling it one time last.
|
|
@@ -12044,7 +12755,7 @@ var BrowserJourneyToolset = class {
|
|
|
12044
12755
|
*
|
|
12045
12756
|
* const toolset = new BrowserToolset(page, { page })
|
|
12046
12757
|
* await toolset.start()
|
|
12047
|
-
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: {
|
|
12758
|
+
* const result = await toolset.tools.execute({ id: '1', name: 'look', arguments: { search: 'cart' } })
|
|
12048
12759
|
* await toolset.destroy()
|
|
12049
12760
|
* ```
|
|
12050
12761
|
*/
|
|
@@ -12108,20 +12819,23 @@ var BrowserToolset = class {
|
|
|
12108
12819
|
...options?.on === void 0 ? {} : { on: options.on },
|
|
12109
12820
|
...options?.error === void 0 ? {} : { error: options.error }
|
|
12110
12821
|
});
|
|
12111
|
-
const look = this.#create("look", this.#look.bind(this),
|
|
12112
|
-
const read = this.#create("read", this.#read.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12822
|
+
const look = this.#create("look", this.#look.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12823
|
+
const read = this.#create("read", this.#read.bind(this, "read", (reading) => reading.markdown()), BROWSER_TOOL_CUT_FOOTER);
|
|
12824
|
+
const plain = this.#create("plain", this.#read.bind(this, "plain", (reading) => reading.text()), BROWSER_TOOL_CUT_FOOTER);
|
|
12113
12825
|
const click = this.#create("click", this.#click.bind(this), BROWSER_TOOL_VIEW_FOOTER);
|
|
12114
12826
|
const type = this.#create("type", this.#type.bind(this), BROWSER_TOOL_VIEW_FOOTER);
|
|
12115
12827
|
const wait = this.#create("wait", this.#wait.bind(this), BROWSER_TOOL_CUT_FOOTER);
|
|
12116
12828
|
this.#native = Object.freeze(options?.page === void 0 ? [
|
|
12117
12829
|
look,
|
|
12118
12830
|
read,
|
|
12831
|
+
plain,
|
|
12119
12832
|
click,
|
|
12120
12833
|
type,
|
|
12121
12834
|
wait
|
|
12122
12835
|
] : [
|
|
12123
12836
|
look,
|
|
12124
12837
|
read,
|
|
12838
|
+
plain,
|
|
12125
12839
|
click,
|
|
12126
12840
|
type,
|
|
12127
12841
|
this.#create("press", this.#press.bind(this), BROWSER_TOOL_VIEW_FOOTER),
|
|
@@ -12424,44 +13138,74 @@ var BrowserToolset = class {
|
|
|
12424
13138
|
if ((0, _orkestrel_contract.isString)(secret) && secret.length > 0) message = message.replaceAll(JSON.stringify(secret), "[redacted]").replaceAll(secret, "[redacted]");
|
|
12425
13139
|
return clause === void 0 ? message : boundBrowserText(message, this.#limit, clause);
|
|
12426
13140
|
}
|
|
12427
|
-
async #look(
|
|
12428
|
-
|
|
13141
|
+
async #look(args, context) {
|
|
13142
|
+
const offset = this.#readOffset(args);
|
|
13143
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13144
|
+
const outline = await this.#race(this.#cursor.elements.outline({
|
|
13145
|
+
signal: context.signal,
|
|
13146
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
13147
|
+
search
|
|
13148
|
+
}), "");
|
|
13149
|
+
const total = outline.text.length;
|
|
13150
|
+
const start = offset < total ? offset : 0;
|
|
13151
|
+
const note = this.#drainNote();
|
|
13152
|
+
const room = this.#limit - note.length;
|
|
13153
|
+
const count = outline.matches.length;
|
|
13154
|
+
const block = start === 0 ? renderBrowserMatches(`${count} ${count === 1 ? "element matches" : "elements match"} ${JSON.stringify(search)}:`, outline.matches, room) : "";
|
|
13155
|
+
const space = room - block.length;
|
|
13156
|
+
const slice = space < 1 ? {
|
|
13157
|
+
text: "",
|
|
13158
|
+
offset: start,
|
|
13159
|
+
total
|
|
13160
|
+
} : extractBrowserSlice(outline.text, start, space);
|
|
13161
|
+
return this.#pageSlice("look", note, slice, block);
|
|
12429
13162
|
}
|
|
12430
|
-
async #read(args, context) {
|
|
12431
|
-
const offset = args
|
|
12432
|
-
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" });
|
|
13163
|
+
async #read(name, project, args, context) {
|
|
13164
|
+
const offset = this.#readOffset(args);
|
|
12433
13165
|
const view = this.#cursor;
|
|
12434
13166
|
const retained = this.#reading;
|
|
12435
13167
|
let reading = retained?.reading;
|
|
12436
|
-
if (offset === 0 || retained === void 0 || reading === void 0 || retained.view !== view || reading.stale) {
|
|
13168
|
+
if (offset === 0 || retained === void 0 || reading === void 0 || retained.name !== name || retained.view !== view || reading.stale) {
|
|
12437
13169
|
reading = await this.#race(view.read({ signal: context.signal }), "", context.signal);
|
|
12438
13170
|
this.#reading = {
|
|
12439
13171
|
view,
|
|
12440
|
-
reading
|
|
13172
|
+
reading,
|
|
13173
|
+
name
|
|
12441
13174
|
};
|
|
12442
13175
|
}
|
|
12443
|
-
const
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
}).total ? offset : 0;
|
|
12447
|
-
const note = boundBrowserText(this.#drain(), Math.max(1, this.#limit - 1), BROWSER_TOOL_CUT_FOOTER);
|
|
13176
|
+
const whole = project(reading);
|
|
13177
|
+
const start = reading === retained?.reading && offset < whole.total ? offset : 0;
|
|
13178
|
+
const note = this.#drainNote();
|
|
12448
13179
|
const room = this.#limit - note.length;
|
|
12449
|
-
const
|
|
13180
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13181
|
+
const matches = start === 0 ? scanBrowserText(whole.text, search) : [];
|
|
13182
|
+
const count = matches.length;
|
|
13183
|
+
const block = renderBrowserMatches(`${count} ${count === 1 ? "line matches" : "lines match"} ${JSON.stringify(search)}:`, matches.map((match) => `[${match.offset}] ${match.text}`), room);
|
|
13184
|
+
const space = room - block.length;
|
|
13185
|
+
const slice = space < 1 ? {
|
|
12450
13186
|
text: "",
|
|
12451
13187
|
offset: start,
|
|
12452
|
-
total:
|
|
12453
|
-
} :
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
13188
|
+
total: whole.total
|
|
13189
|
+
} : extractBrowserSlice(whole.text, start, space);
|
|
13190
|
+
return this.#pageSlice(name, note, slice, block);
|
|
13191
|
+
}
|
|
13192
|
+
#readOffset(args) {
|
|
13193
|
+
const offset = args["offset"] ?? 0;
|
|
13194
|
+
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" });
|
|
13195
|
+
return offset;
|
|
13196
|
+
}
|
|
13197
|
+
#drainNote() {
|
|
13198
|
+
return boundBrowserText(this.#drain(), Math.max(1, this.#limit - 1), BROWSER_TOOL_CUT_FOOTER);
|
|
13199
|
+
}
|
|
13200
|
+
#pageSlice(name, note, slice, block = "") {
|
|
13201
|
+
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", {
|
|
12458
13202
|
limit: this.#limit,
|
|
12459
|
-
offset:
|
|
13203
|
+
offset: slice.offset
|
|
12460
13204
|
});
|
|
12461
13205
|
const end = slice.offset + slice.text.length;
|
|
12462
13206
|
const more = end < slice.total;
|
|
12463
|
-
if (
|
|
12464
|
-
return [`${note}${slice.text}`, `\n\n[characters ${
|
|
13207
|
+
if (slice.offset === 0 && !more) return [`${note}${block}${slice.text}`, ""];
|
|
13208
|
+
return [`${note}${block}${slice.text}`, `\n\n[characters ${slice.offset}–${end} of ${slice.total}${more ? `; call ${name} with offset ${end} for more` : ""}]`];
|
|
12465
13209
|
}
|
|
12466
13210
|
async #click(args, context) {
|
|
12467
13211
|
const turn = await this.#acquire(context.signal);
|
|
@@ -12564,7 +13308,7 @@ var BrowserToolset = class {
|
|
|
12564
13308
|
return [await this.#settle(page.keyboard.press(key), action, context.signal, true, record, popups, observation, key === "Enter" ? {
|
|
12565
13309
|
explicit: false,
|
|
12566
13310
|
action
|
|
12567
|
-
} : void 0), ""];
|
|
13311
|
+
} : void 0, true), ""];
|
|
12568
13312
|
} finally {
|
|
12569
13313
|
record?.destroy();
|
|
12570
13314
|
popups?.destroy();
|
|
@@ -12603,7 +13347,7 @@ var BrowserToolset = class {
|
|
|
12603
13347
|
const action = `Navigated to ${result.url}`;
|
|
12604
13348
|
return [renderBrowserReceipt({
|
|
12605
13349
|
action,
|
|
12606
|
-
view: await this.#capture(action, context.signal)
|
|
13350
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12607
13351
|
}), ""];
|
|
12608
13352
|
} finally {
|
|
12609
13353
|
if (this.#navigating === page) this.#navigating = void 0;
|
|
@@ -12614,22 +13358,25 @@ var BrowserToolset = class {
|
|
|
12614
13358
|
async #wait(args, context) {
|
|
12615
13359
|
const text = readBrowserToolString(args, "text");
|
|
12616
13360
|
const seconds = args["timeout"];
|
|
13361
|
+
const absent = args["absent"];
|
|
13362
|
+
if (absent !== void 0 && !(0, _orkestrel_contract.isBoolean)(absent)) throw new BrowserError("The absent parameter must be a boolean.", "BROWSER_TOOLSET_ARGUMENT", { key: "absent" });
|
|
12617
13363
|
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" });
|
|
12618
13364
|
const timeout = Math.min(seconds === void 0 ? BROWSER_TOOL_TIMEOUT_MS : Math.ceil(seconds * 1e3), BROWSER_TOOL_TIMEOUT_LIMIT_MS);
|
|
12619
13365
|
const quoted = JSON.stringify(text);
|
|
12620
13366
|
try {
|
|
12621
13367
|
await this.#race(this.#cursor.wait(text, {
|
|
12622
13368
|
timeout,
|
|
12623
|
-
signal: context.signal
|
|
12624
|
-
|
|
12625
|
-
|
|
13369
|
+
signal: context.signal,
|
|
13370
|
+
absent: absent === true
|
|
13371
|
+
}), `Waited for ${quoted}${absent === true ? " to leave" : ""}`, context.signal);
|
|
13372
|
+
return [`${quoted} is ${absent === true ? "not " : ""}on the page.`, ""];
|
|
12626
13373
|
} catch (error) {
|
|
12627
13374
|
if (!context.signal.aborted && isBrowserError(error) && error.code === "BROWSER_WAIT_TIMEOUT") {
|
|
12628
13375
|
this.#actions.set(context.signal, {
|
|
12629
13376
|
...this.#actions.get(context.signal),
|
|
12630
13377
|
outcome: "timeout"
|
|
12631
13378
|
});
|
|
12632
|
-
return [`${quoted} did not appear within ${timeout / 1e3} s.`, ""];
|
|
13379
|
+
return [absent === true ? `${quoted} is still on the page after ${timeout / 1e3} s.` : `${quoted} did not appear within ${timeout / 1e3} s.`, ""];
|
|
12633
13380
|
}
|
|
12634
13381
|
throw error;
|
|
12635
13382
|
}
|
|
@@ -12652,14 +13399,19 @@ var BrowserToolset = class {
|
|
|
12652
13399
|
const action = `${accept ? "Accepted" : "Dismissed"} the ${dialog.category} dialog ${JSON.stringify(dialog.message)}`;
|
|
12653
13400
|
return [renderBrowserReceipt({
|
|
12654
13401
|
action,
|
|
12655
|
-
view: await this.#capture(action, context.signal)
|
|
13402
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12656
13403
|
}), ""];
|
|
12657
13404
|
} finally {
|
|
12658
13405
|
turn.resolve();
|
|
12659
13406
|
}
|
|
12660
13407
|
}
|
|
12661
|
-
async #tabs(
|
|
12662
|
-
|
|
13408
|
+
async #tabs(args, context) {
|
|
13409
|
+
const text = (await this.#list(context.signal, true)).map((tab) => `${tab.id} ${JSON.stringify(tab.title)} ${tab.url}${tab.current ? " (current)" : ""}`).join("\n");
|
|
13410
|
+
const search = (0, _orkestrel_contract.isString)(args["search"]) ? args["search"] : "";
|
|
13411
|
+
const matches = scanBrowserText(text, search);
|
|
13412
|
+
const count = matches.length;
|
|
13413
|
+
const note = this.#drainNote();
|
|
13414
|
+
return [`${note}${renderBrowserMatches(`${count} ${count === 1 ? "tab matches" : "tabs match"} ${JSON.stringify(search)}:`, matches.map((match) => match.text), this.#limit - note.length)}${text}`, ""];
|
|
12663
13415
|
}
|
|
12664
13416
|
async #switch(args, context) {
|
|
12665
13417
|
const tab = readBrowserToolString(args, "tab");
|
|
@@ -12682,7 +13434,7 @@ var BrowserToolset = class {
|
|
|
12682
13434
|
await this.#command(front, action, front);
|
|
12683
13435
|
return [renderBrowserReceipt({
|
|
12684
13436
|
action,
|
|
12685
|
-
view: await this.#capture(action, context.signal)
|
|
13437
|
+
view: (await this.#capture(action, context.signal)).view
|
|
12686
13438
|
}), ""];
|
|
12687
13439
|
} finally {
|
|
12688
13440
|
turn.resolve();
|
|
@@ -12691,7 +13443,7 @@ var BrowserToolset = class {
|
|
|
12691
13443
|
async #invoke(tool, synthetic, args, context) {
|
|
12692
13444
|
const turn = await this.#acquire(context.signal);
|
|
12693
13445
|
try {
|
|
12694
|
-
const input = synthetic ? Object.fromEntries(Object.entries(args).filter(([key]) => key !== "
|
|
13446
|
+
const input = synthetic ? Object.fromEntries(Object.entries(args).filter(([key]) => key !== "purpose")) : args;
|
|
12695
13447
|
const command = Promise.resolve(tool.execute(input, context));
|
|
12696
13448
|
return [renderBrowserToolOutput(await this.#command(command, `Called ${tool.name}`, command, context.signal)), ""];
|
|
12697
13449
|
} finally {
|
|
@@ -12771,7 +13523,7 @@ var BrowserToolset = class {
|
|
|
12771
13523
|
}
|
|
12772
13524
|
return turn;
|
|
12773
13525
|
}
|
|
12774
|
-
async #settle(command, action, signal, trusted, record, popups, observation, enter) {
|
|
13526
|
+
async #settle(command, action, signal, trusted, record, popups, observation, enter, focus = false) {
|
|
12775
13527
|
let deadline;
|
|
12776
13528
|
try {
|
|
12777
13529
|
const started = record?.wait({ signal }).then(() => true, () => new Promise(() => void 0));
|
|
@@ -12807,11 +13559,13 @@ var BrowserToolset = class {
|
|
|
12807
13559
|
const unmoved = settled === void 0 && submissions.destinations.length === 0;
|
|
12808
13560
|
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;
|
|
12809
13561
|
const line = enter !== void 0 && (submissions.submitted === true || settled?.reason === "formSubmissionGet" || settled?.reason === "formSubmissionPost") ? enter.action : action;
|
|
13562
|
+
const captured = await this.#capture(line, signal, deadline);
|
|
13563
|
+
const parts = [status, focus && captured.focus !== void 0 ? `focus is on ${captured.focus}` : void 0].filter((part) => part !== void 0);
|
|
12810
13564
|
return renderBrowserReceipt({
|
|
12811
13565
|
action: line,
|
|
12812
13566
|
trusted,
|
|
12813
|
-
...
|
|
12814
|
-
view:
|
|
13567
|
+
...parts.length === 0 ? {} : { status: parts.join("; ") },
|
|
13568
|
+
view: captured.view
|
|
12815
13569
|
});
|
|
12816
13570
|
} finally {
|
|
12817
13571
|
record?.destroy();
|
|
@@ -12978,7 +13732,10 @@ var BrowserToolset = class {
|
|
|
12978
13732
|
}
|
|
12979
13733
|
}
|
|
12980
13734
|
async #capture(action, signal, deadline = performance.now() + BROWSER_TOOL_TIMEOUT_MS) {
|
|
12981
|
-
if (deadline - performance.now() < 1) return
|
|
13735
|
+
if (deadline - performance.now() < 1) return {
|
|
13736
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13737
|
+
focus: void 0
|
|
13738
|
+
};
|
|
12982
13739
|
const receipt = new AbortController();
|
|
12983
13740
|
const options = {
|
|
12984
13741
|
signal: AbortSignal.any([signal, receipt.signal]),
|
|
@@ -12986,16 +13743,34 @@ var BrowserToolset = class {
|
|
|
12986
13743
|
};
|
|
12987
13744
|
try {
|
|
12988
13745
|
const outline = await this.#bounded(this.#cursor.elements.outline(options), deadline, action);
|
|
12989
|
-
return outline === void 0 ?
|
|
13746
|
+
return outline === void 0 ? {
|
|
13747
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13748
|
+
focus: void 0
|
|
13749
|
+
} : {
|
|
13750
|
+
view: outline.text,
|
|
13751
|
+
focus: outline.focus
|
|
13752
|
+
};
|
|
12990
13753
|
} catch (error) {
|
|
12991
13754
|
if (signal.aborted || isBrowserError(error) && error.code === "BROWSER_TOOLSET_RECEIPT") throw error;
|
|
12992
|
-
if (!isBrowserElementError(error) || error.context?.["reason"] !== "GONE") return
|
|
13755
|
+
if (!isBrowserElementError(error) || error.context?.["reason"] !== "GONE") return {
|
|
13756
|
+
view: `(The view could not be read: ${(0, _orkestrel_contract.isError)(error) ? error.message : String(error)}; call look.)`,
|
|
13757
|
+
focus: void 0
|
|
13758
|
+
};
|
|
12993
13759
|
try {
|
|
12994
13760
|
const outline = await this.#bounded(this.#cursor.elements.outline(options), deadline, action);
|
|
12995
|
-
return outline === void 0 ?
|
|
13761
|
+
return outline === void 0 ? {
|
|
13762
|
+
view: BROWSER_TOOL_DEADLINE_NOTE,
|
|
13763
|
+
focus: void 0
|
|
13764
|
+
} : {
|
|
13765
|
+
view: outline.text,
|
|
13766
|
+
focus: outline.focus
|
|
13767
|
+
};
|
|
12996
13768
|
} catch (retry) {
|
|
12997
13769
|
if (signal.aborted || isBrowserError(retry) && retry.code === "BROWSER_TOOLSET_RECEIPT") throw retry;
|
|
12998
|
-
return
|
|
13770
|
+
return {
|
|
13771
|
+
view: BROWSER_TOOL_CHANGED_NOTE,
|
|
13772
|
+
focus: void 0
|
|
13773
|
+
};
|
|
12999
13774
|
}
|
|
13000
13775
|
} finally {
|
|
13001
13776
|
receipt.abort(new BrowserError("the receipt settled", "BROWSER_TOOLSET_SETTLED"));
|
|
@@ -13235,6 +14010,7 @@ exports.BROWSER_ACTION_OUTCOMES = BROWSER_ACTION_OUTCOMES;
|
|
|
13235
14010
|
exports.BROWSER_ACTION_STAGES = BROWSER_ACTION_STAGES;
|
|
13236
14011
|
exports.BROWSER_CODEGEN_BINDING_NAME = BROWSER_CODEGEN_BINDING_NAME;
|
|
13237
14012
|
exports.BROWSER_CODEGEN_SOURCE = BROWSER_CODEGEN_SOURCE;
|
|
14013
|
+
exports.BROWSER_CONTEXT_LOSS_PATTERN = BROWSER_CONTEXT_LOSS_PATTERN;
|
|
13238
14014
|
exports.BROWSER_DEFAULT_TIMEOUT_MS = BROWSER_DEFAULT_TIMEOUT_MS;
|
|
13239
14015
|
exports.BROWSER_DEFAULT_VIEWPORT_HEIGHT = BROWSER_DEFAULT_VIEWPORT_HEIGHT;
|
|
13240
14016
|
exports.BROWSER_DEFAULT_VIEWPORT_WIDTH = BROWSER_DEFAULT_VIEWPORT_WIDTH;
|
|
@@ -13269,6 +14045,7 @@ exports.BROWSER_RESULT_LIMIT_SENTINEL_PREFIX = BROWSER_RESULT_LIMIT_SENTINEL_PRE
|
|
|
13269
14045
|
exports.BROWSER_RUN_ID_PATTERN = BROWSER_RUN_ID_PATTERN;
|
|
13270
14046
|
exports.BROWSER_SCHEMES = BROWSER_SCHEMES;
|
|
13271
14047
|
exports.BROWSER_SCREENSHOT_ATTRIBUTE = BROWSER_SCREENSHOT_ATTRIBUTE;
|
|
14048
|
+
exports.BROWSER_SEARCH_PATTERN = BROWSER_SEARCH_PATTERN;
|
|
13272
14049
|
exports.BROWSER_SNAPSHOT_NODE_LIMIT = BROWSER_SNAPSHOT_NODE_LIMIT;
|
|
13273
14050
|
exports.BROWSER_STABLE_FRAME_COUNT = BROWSER_STABLE_FRAME_COUNT;
|
|
13274
14051
|
exports.BROWSER_STOP_LOADING_TIMEOUT_MS = BROWSER_STOP_LOADING_TIMEOUT_MS;
|
|
@@ -13288,6 +14065,7 @@ exports.BROWSER_TOOL_TIMEOUT_LIMIT_MS = BROWSER_TOOL_TIMEOUT_LIMIT_MS;
|
|
|
13288
14065
|
exports.BROWSER_TOOL_TIMEOUT_MS = BROWSER_TOOL_TIMEOUT_MS;
|
|
13289
14066
|
exports.BROWSER_TOOL_VIEW_FOOTER = BROWSER_TOOL_VIEW_FOOTER;
|
|
13290
14067
|
exports.BROWSER_TYPED_ROLES = BROWSER_TYPED_ROLES;
|
|
14068
|
+
exports.BROWSER_WAIT_EVENTS = BROWSER_WAIT_EVENTS;
|
|
13291
14069
|
exports.BrowserAccessibility = BrowserAccessibility;
|
|
13292
14070
|
exports.BrowserClock = BrowserClock;
|
|
13293
14071
|
exports.BrowserCodegen = BrowserCodegen;
|
|
@@ -13340,6 +14118,7 @@ exports.buildBrowserJourney = buildBrowserJourney;
|
|
|
13340
14118
|
exports.bytesToText = bytesToText;
|
|
13341
14119
|
exports.collectBrowserJourneyBindings = collectBrowserJourneyBindings;
|
|
13342
14120
|
exports.collectBrowserJourneyTextBindings = collectBrowserJourneyTextBindings;
|
|
14121
|
+
exports.collectBrowserWords = collectBrowserWords;
|
|
13343
14122
|
exports.compileActionabilityFunction = compileActionabilityFunction;
|
|
13344
14123
|
exports.compileBrowserBindingCleanup = compileBrowserBindingCleanup;
|
|
13345
14124
|
exports.compileBrowserBindingResult = compileBrowserBindingResult;
|
|
@@ -13464,7 +14243,9 @@ exports.readRareStringData = readRareStringData;
|
|
|
13464
14243
|
exports.renderBrowserElement = renderBrowserElement;
|
|
13465
14244
|
exports.renderBrowserJourney = renderBrowserJourney;
|
|
13466
14245
|
exports.renderBrowserJourneyFault = renderBrowserJourneyFault;
|
|
14246
|
+
exports.renderBrowserMatches = renderBrowserMatches;
|
|
13467
14247
|
exports.renderBrowserOutline = renderBrowserOutline;
|
|
14248
|
+
exports.renderBrowserOutlineRow = renderBrowserOutlineRow;
|
|
13468
14249
|
exports.renderBrowserReceipt = renderBrowserReceipt;
|
|
13469
14250
|
exports.renderBrowserRun = renderBrowserRun;
|
|
13470
14251
|
exports.renderBrowserRunResult = renderBrowserRunResult;
|
|
@@ -13472,6 +14253,8 @@ exports.renderBrowserToolOutput = renderBrowserToolOutput;
|
|
|
13472
14253
|
exports.requireBrowserReference = requireBrowserReference;
|
|
13473
14254
|
exports.requireBrowserString = requireBrowserString;
|
|
13474
14255
|
exports.resolveBrowserJourneyBinding = resolveBrowserJourneyBinding;
|
|
14256
|
+
exports.scanBrowserOutline = scanBrowserOutline;
|
|
14257
|
+
exports.scanBrowserText = scanBrowserText;
|
|
13475
14258
|
exports.settleBrowserTeardown = settleBrowserTeardown;
|
|
13476
14259
|
exports.textToBytes = textToBytes;
|
|
13477
14260
|
exports.validateBrowserAccessibilityOptions = validateBrowserAccessibilityOptions;
|