@jobshimo/browser-link 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/agent-instructions/content.js +11 -0
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/cdp/client.d.ts +35 -0
- package/dist/cdp/client.js +149 -0
- package/dist/cdp/client.js.map +1 -0
- package/dist/cdp/flow.d.ts +178 -0
- package/dist/cdp/flow.js +217 -0
- package/dist/cdp/flow.js.map +1 -0
- package/dist/cdp/gate.d.ts +31 -0
- package/dist/cdp/gate.js +35 -0
- package/dist/cdp/gate.js.map +1 -0
- package/dist/cdp/grant.d.ts +45 -0
- package/dist/cdp/grant.js +81 -0
- package/dist/cdp/grant.js.map +1 -0
- package/dist/cdp/inpage/builders.d.ts +165 -0
- package/dist/cdp/inpage/builders.js +501 -0
- package/dist/cdp/inpage/builders.js.map +1 -0
- package/dist/cdp/inpage/deep-query.d.ts +57 -0
- package/dist/cdp/inpage/deep-query.js +325 -0
- package/dist/cdp/inpage/deep-query.js.map +1 -0
- package/dist/cdp/inpage/dom-helpers.d.ts +27 -0
- package/dist/cdp/inpage/dom-helpers.js +147 -0
- package/dist/cdp/inpage/dom-helpers.js.map +1 -0
- package/dist/cdp/keymap.d.ts +83 -0
- package/dist/cdp/keymap.js +205 -0
- package/dist/cdp/keymap.js.map +1 -0
- package/dist/cdp/settle.d.ts +38 -0
- package/dist/cdp/settle.js +76 -0
- package/dist/cdp/settle.js.map +1 -0
- package/dist/cdp/support.d.ts +26 -0
- package/dist/cdp/support.js +58 -0
- package/dist/cdp/support.js.map +1 -0
- package/dist/cdp/targets.d.ts +54 -0
- package/dist/cdp/targets.js +176 -0
- package/dist/cdp/targets.js.map +1 -0
- package/dist/cdp/transport.d.ts +31 -0
- package/dist/cdp/transport.js +531 -0
- package/dist/cdp/transport.js.map +1 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.js +13 -0
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/cdp.d.ts +28 -0
- package/dist/commands/cdp.js +172 -0
- package/dist/commands/cdp.js.map +1 -0
- package/dist/commands/config.d.ts +92 -0
- package/dist/commands/config.js +173 -12
- package/dist/commands/config.js.map +1 -1
- package/dist/config.d.ts +70 -0
- package/dist/config.js +94 -1
- package/dist/config.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +4 -3
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +31 -0
- package/dist/tools/browser-dispatch.js +88 -40
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.js +19 -0
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type CdpDirectGate = {
|
|
2
|
+
ok: true;
|
|
3
|
+
} | {
|
|
4
|
+
ok: false;
|
|
5
|
+
error: string;
|
|
6
|
+
};
|
|
7
|
+
/** Exact wording agents receive when cdp-direct is off entirely. Agents get
|
|
8
|
+
* this string and NOTHING else — there is no bypass, no partial access, no
|
|
9
|
+
* alternate path to a `cdp:` tab while this check fails. */
|
|
10
|
+
export declare const CDP_DIRECT_DISABLED_ERROR = "cdp-direct is disabled. The user can enable it with: browser-link config set cdp-direct.enabled true";
|
|
11
|
+
/** Exact wording agents receive when cdp-direct is enabled but no live
|
|
12
|
+
* grant exists (never granted, revoked, or expired). */
|
|
13
|
+
export declare const CDP_DIRECT_NO_GRANT_ERROR = "cdp-direct requires an active grant. Ask the user to run: browser-link cdp allow";
|
|
14
|
+
/**
|
|
15
|
+
* The ONE gate every cdp-direct code path runs through, in the exact order
|
|
16
|
+
* the feature's permission model requires: (1) is cdp-direct enabled at
|
|
17
|
+
* all, (2) is there a live, unexpired grant. Re-evaluated on every call —
|
|
18
|
+
* never cached — because either condition can flip mid-session (the user
|
|
19
|
+
* disables the setting, or a time-boxed grant simply expires) and the very
|
|
20
|
+
* next tool call must see that.
|
|
21
|
+
*
|
|
22
|
+
* Called from two places by design: `tools/browser-dispatch.ts`'s routing
|
|
23
|
+
* layer (so an agent gets the exact error text above before any cdp-direct
|
|
24
|
+
* code runs at all) AND `cdp/transport.ts` itself (defense in depth, so the
|
|
25
|
+
* transport module is never safe to call without this check even if a
|
|
26
|
+
* future caller reaches it directly). `cdp/targets.ts`'s `browser.list_tabs`
|
|
27
|
+
* discovery also gates on this, but treats a failing gate as "show nothing"
|
|
28
|
+
* rather than an error — an agent that has never heard of cdp-direct should
|
|
29
|
+
* not see it mentioned in a routine list_tabs call.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkCdpDirectGate(now?: number): CdpDirectGate;
|
package/dist/cdp/gate.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { isGrantLive, loadGrant } from './grant.js';
|
|
3
|
+
/** Exact wording agents receive when cdp-direct is off entirely. Agents get
|
|
4
|
+
* this string and NOTHING else — there is no bypass, no partial access, no
|
|
5
|
+
* alternate path to a `cdp:` tab while this check fails. */
|
|
6
|
+
export const CDP_DIRECT_DISABLED_ERROR = 'cdp-direct is disabled. The user can enable it with: browser-link config set cdp-direct.enabled true';
|
|
7
|
+
/** Exact wording agents receive when cdp-direct is enabled but no live
|
|
8
|
+
* grant exists (never granted, revoked, or expired). */
|
|
9
|
+
export const CDP_DIRECT_NO_GRANT_ERROR = 'cdp-direct requires an active grant. Ask the user to run: browser-link cdp allow';
|
|
10
|
+
/**
|
|
11
|
+
* The ONE gate every cdp-direct code path runs through, in the exact order
|
|
12
|
+
* the feature's permission model requires: (1) is cdp-direct enabled at
|
|
13
|
+
* all, (2) is there a live, unexpired grant. Re-evaluated on every call —
|
|
14
|
+
* never cached — because either condition can flip mid-session (the user
|
|
15
|
+
* disables the setting, or a time-boxed grant simply expires) and the very
|
|
16
|
+
* next tool call must see that.
|
|
17
|
+
*
|
|
18
|
+
* Called from two places by design: `tools/browser-dispatch.ts`'s routing
|
|
19
|
+
* layer (so an agent gets the exact error text above before any cdp-direct
|
|
20
|
+
* code runs at all) AND `cdp/transport.ts` itself (defense in depth, so the
|
|
21
|
+
* transport module is never safe to call without this check even if a
|
|
22
|
+
* future caller reaches it directly). `cdp/targets.ts`'s `browser.list_tabs`
|
|
23
|
+
* discovery also gates on this, but treats a failing gate as "show nothing"
|
|
24
|
+
* rather than an error — an agent that has never heard of cdp-direct should
|
|
25
|
+
* not see it mentioned in a routine list_tabs call.
|
|
26
|
+
*/
|
|
27
|
+
export function checkCdpDirectGate(now = Date.now()) {
|
|
28
|
+
const cfg = loadConfig();
|
|
29
|
+
if (cfg.cdpDirectEnabled !== true)
|
|
30
|
+
return { ok: false, error: CDP_DIRECT_DISABLED_ERROR };
|
|
31
|
+
if (!isGrantLive(loadGrant(), now))
|
|
32
|
+
return { ok: false, error: CDP_DIRECT_NO_GRANT_ERROR };
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/cdp/gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIpD;;4DAE4D;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GACpC,sGAAsG,CAAC;AAEzG;wDACwD;AACxD,MAAM,CAAC,MAAM,yBAAyB,GACpC,kFAAkF,CAAC;AAErF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACzD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,GAAG,CAAC,gBAAgB,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;IAC1F,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;IAC3F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk record of a `browser-link cdp allow` grant — the time-boxed human
|
|
3
|
+
* consent that, together with `cdp-direct.enabled`, is required before any
|
|
4
|
+
* tool may address a `cdp:` tab (see `gate.ts`). Lives in its own small JSON
|
|
5
|
+
* file in the data dir, never in the repo, never in `config.json` (a grant
|
|
6
|
+
* is a momentary permission, not a persistent preference).
|
|
7
|
+
*/
|
|
8
|
+
export interface CdpGrant {
|
|
9
|
+
grantedAt: number;
|
|
10
|
+
/** `null` means the grant never expires ("never" / `--minutes 0`) —
|
|
11
|
+
* documented in the CLI help and README as reducing the security
|
|
12
|
+
* posture, not a default anyone should reach for casually. */
|
|
13
|
+
expiresAt: number | null;
|
|
14
|
+
}
|
|
15
|
+
/** Absolute path of the grant file — exported so the CLI can name it in an
|
|
16
|
+
* honest "could not remove" error (see `commands/cdp.ts`) rather than
|
|
17
|
+
* leaving the user guessing which file to delete by hand. */
|
|
18
|
+
export declare function grantFilePath(): string;
|
|
19
|
+
/** Read the current grant, or `null` when none exists or the file is
|
|
20
|
+
* corrupt/unreadable — corruption degrades to "no grant" (the safer
|
|
21
|
+
* failure mode for a permission file) rather than throwing. */
|
|
22
|
+
export declare function loadGrant(): CdpGrant | null;
|
|
23
|
+
/** Record a fresh grant. `ttlMinutes: 0` means "never expires"
|
|
24
|
+
* (`expiresAt: null`); any other value is the grant's lifetime in minutes
|
|
25
|
+
* from `now`. `now` is a parameter so tests do not need to mock the clock. */
|
|
26
|
+
export declare function saveGrant(ttlMinutes: number, now?: number): CdpGrant;
|
|
27
|
+
/** Revoke the current grant, if any. Idempotent — revoking with no grant
|
|
28
|
+
* present is not an error and is a no-op.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately does NOT swallow an unlink failure: this is a SECURITY
|
|
31
|
+
* operation, and a grant file that survives a "revoke" (a transient
|
|
32
|
+
* Windows lock, an EPERM, a read-only dir) is still honoured by the gate
|
|
33
|
+
* on the very next tool call — so reporting success while the door stays
|
|
34
|
+
* open would be a lie. The error propagates; the CLI (`revokeCdpDirect` in
|
|
35
|
+
* `commands/cdp.ts`) catches it and tells the user the grant may still be
|
|
36
|
+
* active and to remove the file by hand. */
|
|
37
|
+
export declare function clearGrant(): void;
|
|
38
|
+
/** Whether `grant` is currently live: present, and either non-expiring or
|
|
39
|
+
* not yet past its `expiresAt`. */
|
|
40
|
+
export declare function isGrantLive(grant: CdpGrant | null, now?: number): boolean;
|
|
41
|
+
/** Milliseconds remaining on `grant`, or `null` when it never expires or
|
|
42
|
+
* does not exist (callers distinguish "no grant" from "never expires" by
|
|
43
|
+
* checking `grant` itself first). Never negative — an expired grant reads
|
|
44
|
+
* as `0` remaining, not a negative number. */
|
|
45
|
+
export declare function remainingMs(grant: CdpGrant | null, now?: number): number | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { getDataDir } from '../map/paths.js';
|
|
4
|
+
function grantFile() {
|
|
5
|
+
return join(getDataDir(), 'cdp-grant.json');
|
|
6
|
+
}
|
|
7
|
+
/** Absolute path of the grant file — exported so the CLI can name it in an
|
|
8
|
+
* honest "could not remove" error (see `commands/cdp.ts`) rather than
|
|
9
|
+
* leaving the user guessing which file to delete by hand. */
|
|
10
|
+
export function grantFilePath() {
|
|
11
|
+
return grantFile();
|
|
12
|
+
}
|
|
13
|
+
function isCdpGrant(value) {
|
|
14
|
+
if (typeof value !== 'object' || value === null)
|
|
15
|
+
return false;
|
|
16
|
+
const v = value;
|
|
17
|
+
return (typeof v.grantedAt === 'number' && (v.expiresAt === null || typeof v.expiresAt === 'number'));
|
|
18
|
+
}
|
|
19
|
+
/** Read the current grant, or `null` when none exists or the file is
|
|
20
|
+
* corrupt/unreadable — corruption degrades to "no grant" (the safer
|
|
21
|
+
* failure mode for a permission file) rather than throwing. */
|
|
22
|
+
export function loadGrant() {
|
|
23
|
+
const path = grantFile();
|
|
24
|
+
if (!existsSync(path))
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
28
|
+
return isCdpGrant(parsed) ? parsed : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Record a fresh grant. `ttlMinutes: 0` means "never expires"
|
|
35
|
+
* (`expiresAt: null`); any other value is the grant's lifetime in minutes
|
|
36
|
+
* from `now`. `now` is a parameter so tests do not need to mock the clock. */
|
|
37
|
+
export function saveGrant(ttlMinutes, now = Date.now()) {
|
|
38
|
+
const path = grantFile();
|
|
39
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
40
|
+
const grant = {
|
|
41
|
+
grantedAt: now,
|
|
42
|
+
expiresAt: ttlMinutes === 0 ? null : now + ttlMinutes * 60_000,
|
|
43
|
+
};
|
|
44
|
+
writeFileSync(path, JSON.stringify(grant, null, 2) + '\n', 'utf8');
|
|
45
|
+
return grant;
|
|
46
|
+
}
|
|
47
|
+
/** Revoke the current grant, if any. Idempotent — revoking with no grant
|
|
48
|
+
* present is not an error and is a no-op.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately does NOT swallow an unlink failure: this is a SECURITY
|
|
51
|
+
* operation, and a grant file that survives a "revoke" (a transient
|
|
52
|
+
* Windows lock, an EPERM, a read-only dir) is still honoured by the gate
|
|
53
|
+
* on the very next tool call — so reporting success while the door stays
|
|
54
|
+
* open would be a lie. The error propagates; the CLI (`revokeCdpDirect` in
|
|
55
|
+
* `commands/cdp.ts`) catches it and tells the user the grant may still be
|
|
56
|
+
* active and to remove the file by hand. */
|
|
57
|
+
export function clearGrant() {
|
|
58
|
+
const path = grantFile();
|
|
59
|
+
if (!existsSync(path))
|
|
60
|
+
return;
|
|
61
|
+
unlinkSync(path);
|
|
62
|
+
}
|
|
63
|
+
/** Whether `grant` is currently live: present, and either non-expiring or
|
|
64
|
+
* not yet past its `expiresAt`. */
|
|
65
|
+
export function isGrantLive(grant, now = Date.now()) {
|
|
66
|
+
if (!grant)
|
|
67
|
+
return false;
|
|
68
|
+
if (grant.expiresAt === null)
|
|
69
|
+
return true;
|
|
70
|
+
return grant.expiresAt > now;
|
|
71
|
+
}
|
|
72
|
+
/** Milliseconds remaining on `grant`, or `null` when it never expires or
|
|
73
|
+
* does not exist (callers distinguish "no grant" from "never expires" by
|
|
74
|
+
* checking `grant` itself first). Never negative — an expired grant reads
|
|
75
|
+
* as `0` remaining, not a negative number. */
|
|
76
|
+
export function remainingMs(grant, now = Date.now()) {
|
|
77
|
+
if (!grant || grant.expiresAt === null)
|
|
78
|
+
return null;
|
|
79
|
+
return Math.max(0, grant.expiresAt - now);
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=grant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant.js","sourceRoot":"","sources":["../../src/cdp/grant.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the snapshot expression. Filters are applied INSIDE the page so the
|
|
3
|
+
* server never receives the dropped material at all — that's where the token
|
|
4
|
+
* win for `within_selector` / `only_interactive` / `exclude` actually lives.
|
|
5
|
+
* The serializer omits empty-string fields per entry (token win that applies
|
|
6
|
+
* unconditionally on every snapshot).
|
|
7
|
+
*
|
|
8
|
+
* The scan pierces open Shadow DOM roots and same-origin iframes via
|
|
9
|
+
* `deepQueryAll` (see `deep-query.ts`). `within_selector` itself is resolved
|
|
10
|
+
* with `deepQueryFirst`, so a subtree inside a shadow root or iframe can be
|
|
11
|
+
* targeted too. Matched entries that live inside an iframe carry an
|
|
12
|
+
* optional `frame` field with the innermost hosting iframe's selector, and
|
|
13
|
+
* entries whose generated selector could not be made unique across the deep
|
|
14
|
+
* search scope carry `ambiguous: true` (see `genSelectorInfo`).
|
|
15
|
+
*/
|
|
16
|
+
export interface SnapshotOpts {
|
|
17
|
+
within_selector?: string;
|
|
18
|
+
only_interactive?: boolean;
|
|
19
|
+
exclude?: string[];
|
|
20
|
+
max_interactive?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildSnapshotJs(opts?: SnapshotOpts): string;
|
|
23
|
+
/** Build the find-by-text expression. Returns one of:
|
|
24
|
+
* { matched: true, selector, coords:{x,y}, tag, text, frame?, ambiguous? }
|
|
25
|
+
* { matched: false, reason: 'not-found', error?, near_misses?: [{text,selector,role?}] }
|
|
26
|
+
* { matched: false, reason: 'multiple-matches', candidates: [{selector,text,tag}] }
|
|
27
|
+
*
|
|
28
|
+
* Role-aware: when `role` is provided, only elements whose explicit ARIA
|
|
29
|
+
* role or implicit role match are considered. When omitted, the search
|
|
30
|
+
* scans a broad set of interactive + clickable elements.
|
|
31
|
+
*
|
|
32
|
+
* The scan pierces open Shadow DOM roots and same-origin iframes via
|
|
33
|
+
* `deepQueryAll`. `coords` are mapped to TOP-LEVEL viewport coordinates
|
|
34
|
+
* (accounting for ancestor iframe offsets) so they are directly usable by
|
|
35
|
+
* `browser.drag` and other coordinate-based tools.
|
|
36
|
+
*
|
|
37
|
+
* `not-found` carries up to 3 `near_misses` — VISIBLE interactive-ish
|
|
38
|
+
* elements ranked by (1) case-insensitive substring containment of the
|
|
39
|
+
* query in the candidate's accessible text/name, then (2) token overlap —
|
|
40
|
+
* so the agent gets a "did you mean" instead of a bare miss. Omitted
|
|
41
|
+
* entirely when nothing scores (no junk suggestions). When `role` narrowed
|
|
42
|
+
* the scan and the text DOES exist outside that role, `error` names the
|
|
43
|
+
* exclusion explicitly (e.g. `text matched 2 elements but none with role
|
|
44
|
+
* "button" — closest: <div onclick> "GIF picker"`) and `near_misses` is
|
|
45
|
+
* populated from that broader, role-agnostic match set instead.
|
|
46
|
+
*
|
|
47
|
+
* Near-miss entries use `genSelector` (not `genSelectorInfo`), so the
|
|
48
|
+
* `ambiguous` flag is intentionally NOT carried — a near-miss selector
|
|
49
|
+
* that collides with structurally-identical twins in other roots resolves
|
|
50
|
+
* first-match-wins silently. Documented on the tool as: hints for the
|
|
51
|
+
* next `find`, never click targets.
|
|
52
|
+
*/
|
|
53
|
+
export interface FindOpts {
|
|
54
|
+
text: string;
|
|
55
|
+
role?: string;
|
|
56
|
+
exact?: boolean;
|
|
57
|
+
}
|
|
58
|
+
export declare function buildFindJs(opts: FindOpts): string;
|
|
59
|
+
/**
|
|
60
|
+
* Build the click-resolution expression: resolves the selector across the
|
|
61
|
+
* deep search scope, scrolls the element (and every ancestor iframe) into
|
|
62
|
+
* view, computes the TOP-LEVEL viewport click point, and — unless `force`
|
|
63
|
+
* is set — hit-tests that point before returning it, so a covering overlay
|
|
64
|
+
* is caught before dispatching CDP mouse events. Returns:
|
|
65
|
+
* { ok: true, x, y, tag }
|
|
66
|
+
* { ok: false, reason: 'invalid-selector', error }
|
|
67
|
+
* { ok: false, reason: 'not-found' }
|
|
68
|
+
* { ok: false, reason: 'occluded', blocker }
|
|
69
|
+
*
|
|
70
|
+
* The `invalid-selector` check runs BEFORE `deepQueryFirst` — a malformed
|
|
71
|
+
* CSS selector throws the identical `SyntaxError` from every root's own
|
|
72
|
+
* `querySelector`, which `deepQueryFirst`'s per-root try/catch otherwise
|
|
73
|
+
* swallows into an indistinguishable "not-found". Checking once against
|
|
74
|
+
* `document` up front separates "the selector text is broken" from "the
|
|
75
|
+
* selector is valid but nothing on the page matches it".
|
|
76
|
+
*/
|
|
77
|
+
export interface ClickResolveOpts {
|
|
78
|
+
selector: string;
|
|
79
|
+
force: boolean;
|
|
80
|
+
}
|
|
81
|
+
export declare function buildClickResolveJs(opts: ClickResolveOpts): string;
|
|
82
|
+
/**
|
|
83
|
+
* Build the type-resolution expression: resolves the selector across the
|
|
84
|
+
* deep search scope, focuses it, optionally clears its value, and reports
|
|
85
|
+
* success so the caller can follow up with CDP `Input.insertText` (which
|
|
86
|
+
* types into whatever currently has focus at the browser level — works the
|
|
87
|
+
* same whether that focus target lives in the top document, a shadow root,
|
|
88
|
+
* or a same-origin iframe). Returns:
|
|
89
|
+
* { ok: true }
|
|
90
|
+
* { ok: false, reason: 'invalid-selector', error }
|
|
91
|
+
* { ok: false, reason: 'not-found' }
|
|
92
|
+
*
|
|
93
|
+
* Same invalid-selector pre-check as `buildClickResolveJs` — see its doc
|
|
94
|
+
* comment for why the check has to run before `deepQueryFirst`.
|
|
95
|
+
*/
|
|
96
|
+
export interface TypeResolveOpts {
|
|
97
|
+
selector: string;
|
|
98
|
+
clear: boolean;
|
|
99
|
+
}
|
|
100
|
+
export declare function buildTypeResolveJs(opts: TypeResolveOpts): string;
|
|
101
|
+
/**
|
|
102
|
+
* Build the focus-resolution expression for `browser.press`'s optional
|
|
103
|
+
* `selector`: resolves the selector across the deep search scope and
|
|
104
|
+
* focuses it, reporting success so the caller can follow up with the CDP
|
|
105
|
+
* key event sequence (which — like `Input.insertText` — targets whatever
|
|
106
|
+
* currently has focus at the browser level). Intentionally separate from
|
|
107
|
+
* `buildTypeResolveJs`: press never clears a value, and naming it
|
|
108
|
+
* distinctly keeps the press code path readable on its own.
|
|
109
|
+
*/
|
|
110
|
+
export interface FocusResolveOpts {
|
|
111
|
+
selector: string;
|
|
112
|
+
}
|
|
113
|
+
export declare function buildFocusJs(opts: FocusResolveOpts): string;
|
|
114
|
+
/**
|
|
115
|
+
* Build the settle-await expression shared by `browser.click`, `.type` and
|
|
116
|
+
* `.press`. Called AFTER the action's CDP input events have been
|
|
117
|
+
* dispatched: installs one `MutationObserver` on `document` (subtree,
|
|
118
|
+
* childList, attributes, characterData) and resolves once no mutation has
|
|
119
|
+
* landed for `settle_ms` consecutive milliseconds, or once `settle_timeout_ms`
|
|
120
|
+
* total has elapsed — whichever comes first. Returned as a Promise so the
|
|
121
|
+
* caller's `evaluateInTab` (CDP `Runtime.evaluate` with `awaitPromise: true`)
|
|
122
|
+
* waits for it naturally.
|
|
123
|
+
*
|
|
124
|
+
* Baseline `url`/`activeElement` are captured at the TOP of this
|
|
125
|
+
* expression — i.e. right after the action's own focus/navigation side
|
|
126
|
+
* effects have already landed (dispatch already happened by the time this
|
|
127
|
+
* runs), so `focus_moved`/`url_changed` report drift that happened DURING
|
|
128
|
+
* the settle window, not the action's own expected effect.
|
|
129
|
+
*
|
|
130
|
+
* The result omits `url_changed` / `focus_moved` when they did not change
|
|
131
|
+
* — token-lean by construction, no post-filtering needed.
|
|
132
|
+
*/
|
|
133
|
+
export interface SettleOpts {
|
|
134
|
+
/** Quiet-period length in ms. Caller is expected to have already
|
|
135
|
+
* clamped this to (0, 2000] — 0 means "don't call this builder at all". */
|
|
136
|
+
settle_ms: number;
|
|
137
|
+
/** Overall cap in ms. Caller is expected to have already clamped this
|
|
138
|
+
* to [0, 10000]. */
|
|
139
|
+
settle_timeout_ms: number;
|
|
140
|
+
}
|
|
141
|
+
export declare function buildSettleJs(opts: SettleOpts): string;
|
|
142
|
+
/**
|
|
143
|
+
* Build the `browser.state` expression: a compact orientation snapshot —
|
|
144
|
+
* current url/title, the deep-resolved focused element, visible dialog-role
|
|
145
|
+
* elements, scroll position, and viewport size. Cheaper than a full
|
|
146
|
+
* `browser.snapshot` when the agent only needs "where am I right now".
|
|
147
|
+
*
|
|
148
|
+
* `focused` descends through `document.activeElement` past shadow-root and
|
|
149
|
+
* same-origin-iframe boundaries to the real innermost focused element (a
|
|
150
|
+
* shadow host or an <iframe> being "active" at one level is not the actual
|
|
151
|
+
* focus target the agent cares about). Its `selector` goes through the same
|
|
152
|
+
* `genSelectorInfo` uniqueness check `snapshot`/`find` use, so it carries
|
|
153
|
+
* `ambiguous: true` under the same structurally-identical-twins condition.
|
|
154
|
+
*
|
|
155
|
+
* `dialogs` matches visible `[role=dialog]`, `[role=alertdialog]` and open
|
|
156
|
+
* `<dialog>` elements found via the same deep walk, with a best-effort
|
|
157
|
+
* `label` resolved from aria-label / aria-labelledby / the first heading
|
|
158
|
+
* inside the dialog.
|
|
159
|
+
*
|
|
160
|
+
* Every optional field is omitted when there is nothing to report: no
|
|
161
|
+
* `focused` beyond `<body>`, no `dialogs` when none are open, and no
|
|
162
|
+
* `scroll` when the page is at its default (0,0) position — token-lean by
|
|
163
|
+
* construction, matching the omit-falsy convention the other builders use.
|
|
164
|
+
*/
|
|
165
|
+
export declare function buildStateJs(): string;
|