dsh-browser-playwright-codex 0.5.2 → 0.5.3
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/CHANGELOG.md +30 -0
- package/lib/browser-lifecycle.d.ts +12 -0
- package/lib/browser-lifecycle.js +186 -0
- package/lib/challenge.d.ts +20 -0
- package/lib/challenge.js +40 -0
- package/lib/config.d.ts +81 -0
- package/lib/config.js +61 -0
- package/lib/extract.d.ts +13 -0
- package/lib/extract.js +34 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.js +3 -1
- package/lib/login-state.d.ts +10 -0
- package/lib/login-state.js +83 -0
- package/lib/page-actions.d.ts +134 -0
- package/lib/page-actions.js +725 -0
- package/lib/page-diagnostics.d.ts +65 -0
- package/lib/page-diagnostics.js +181 -0
- package/lib/page-probes.d.ts +78 -0
- package/lib/page-probes.js +281 -0
- package/lib/page-snapshot.d.ts +96 -0
- package/lib/page-snapshot.js +280 -0
- package/lib/playwright.d.ts +5 -644
- package/lib/playwright.js +28 -2602
- package/lib/provider.d.ts +157 -0
- package/lib/provider.js +352 -0
- package/lib/session.d.ts +410 -0
- package/lib/session.js +633 -0
- package/lib/tool-render.d.ts +20 -0
- package/lib/tool-render.js +137 -0
- package/lib/tool-schemas.d.ts +76 -0
- package/lib/tool-schemas.js +46 -0
- package/lib/tool-types.d.ts +82 -0
- package/lib/tool-types.js +1 -0
- package/lib/tool.d.ts +2 -26
- package/lib/tool.js +5 -252
- package/lib/url-policy.d.ts +2 -0
- package/lib/url-policy.js +26 -0
- package/lib/window-focus.d.ts +27 -0
- package/lib/window-focus.js +54 -0
- package/package.json +1 -1
- package/scripts/dsh-browser-verify.mjs +2 -1
- package/scripts/prompt-cost.mjs +1 -1
package/lib/playwright.d.ts
CHANGED
|
@@ -1,655 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
* Playwright provider for the browser capability: owns the browser binary,
|
|
3
|
-
* one context per owner key, idle disposal, and the snapshot engine.
|
|
4
|
-
* @module dsh-browser-playwright-codex/playwright
|
|
5
|
-
*/
|
|
1
|
+
import { type PlaywrightConfig } from './config.ts';
|
|
6
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
-
import { type Browser, type BrowserContext, type CDPSession, type Dialog, type Locator, type Page, type Request } from 'playwright-core';
|
|
8
|
-
import z from '@deepseek-ai/schemastery';
|
|
9
|
-
import type { BrowserNode, BrowserSnapshot, DiagnosticsEntry, LoadState } from './types.ts';
|
|
10
3
|
/** Cordis plugin name used by loader diagnostics. */
|
|
11
4
|
export declare const name = "browser-playwright";
|
|
12
5
|
/** The browser runtime this provider registers into. */
|
|
13
6
|
export declare const inject: string[];
|
|
14
|
-
/** Schemastery validation for {@link PlaywrightConfig}. */
|
|
15
|
-
/** Launch configuration for the Playwright provider. */
|
|
16
|
-
export interface PlaywrightConfig {
|
|
17
|
-
launch: {
|
|
18
|
-
/** Absolute path to a Chromium-family binary; takes precedence over channel. */
|
|
19
|
-
executablePath?: string;
|
|
20
|
-
/** Browser channel: chromium, chrome, msedge. Omitted = auto-detect in that order. */
|
|
21
|
-
channel?: string;
|
|
22
|
-
/** Run headful so the user can watch and rescue the browser by hand. */
|
|
23
|
-
headless: boolean;
|
|
24
|
-
/** Persistent mode: one shared profile-backed window whose login survives. */
|
|
25
|
-
persistent: boolean;
|
|
26
|
-
/** Profile directory for persistent mode; empty means ~/.dsh/browser-profiles/playwright. */
|
|
27
|
-
profileDir: string;
|
|
28
|
-
viewport: {
|
|
29
|
-
width: number;
|
|
30
|
-
height: number;
|
|
31
|
-
};
|
|
32
|
-
/** Per-action navigation/click timeout in milliseconds. */
|
|
33
|
-
navigationTimeoutMs: number;
|
|
34
|
-
/** Ignore HTTPS certificate errors. */
|
|
35
|
-
ignoreHTTPSErrors: boolean;
|
|
36
|
-
};
|
|
37
|
-
/** Host suffixes the browser may visit. Empty = any http(s) host. */
|
|
38
|
-
allowedDomains?: string[];
|
|
39
|
-
/** Close an idle session's browser context after this many milliseconds. 0 disables. */
|
|
40
|
-
idleTimeoutMs: number;
|
|
41
|
-
/** Maximum concurrent browser contexts; acquiring beyond it evicts the least recently used. */
|
|
42
|
-
maxSessions: number;
|
|
43
|
-
snapshot: {
|
|
44
|
-
/** 'legacy' keeps the injected DOM walker; 'aria' uses the official ariaSnapshot(mode:'ai') engine. */
|
|
45
|
-
engine: 'legacy' | 'aria';
|
|
46
|
-
maxNodes: number;
|
|
47
|
-
maxNameLength: number;
|
|
48
|
-
maxTextLength: number;
|
|
49
|
-
/** Incremental ref-diff mode; false = always full snapshots (zero behavioral change). */
|
|
50
|
-
diff: boolean;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/** Bounded page data the extraction consumer feeds to a model. */
|
|
54
|
-
export interface PageData {
|
|
55
|
-
readonly url: string;
|
|
56
|
-
readonly title: string;
|
|
57
|
-
readonly text: string;
|
|
58
|
-
readonly truncated: boolean;
|
|
59
|
-
readonly links: readonly {
|
|
60
|
-
readonly text: string;
|
|
61
|
-
readonly href: string;
|
|
62
|
-
}[];
|
|
63
|
-
readonly inputs: readonly {
|
|
64
|
-
readonly tag: string;
|
|
65
|
-
readonly type: string;
|
|
66
|
-
readonly name: string;
|
|
67
|
-
readonly value: string;
|
|
68
|
-
readonly label: string;
|
|
69
|
-
readonly checked: boolean | null;
|
|
70
|
-
}[];
|
|
71
|
-
}
|
|
72
|
-
/** Schemastery validation for {@link PlaywrightConfig}. */
|
|
73
|
-
export declare const Config: z<PlaywrightConfig>;
|
|
74
7
|
/**
|
|
75
8
|
* Register this provider on the browser runtime for the plugin's lifetime.
|
|
76
9
|
* @param ctx - plugin context carrying the browser runtime.
|
|
77
10
|
* @param config - launch and fleet configuration.
|
|
78
11
|
*/
|
|
12
|
+
export { Config, type PageData, type PlaywrightConfig } from './config.ts';
|
|
13
|
+
export { PlaywrightProvider } from './provider.ts';
|
|
14
|
+
export { assertAllowedUrl } from './url-policy.ts';
|
|
15
|
+
export { resolveSnapshotEngine } from './page-snapshot.ts';
|
|
79
16
|
export declare function apply(ctx: Context, config: PlaywrightConfig): void;
|
|
80
|
-
/** Validate one absolute URL against the navigation policy. */
|
|
81
|
-
export declare function assertAllowedUrl(raw: string, allowedDomains: readonly string[]): URL;
|
|
82
|
-
/**
|
|
83
|
-
* Which page-reading mode a capture uses. The user's choice on the settings page
|
|
84
|
-
* (Settings → 浏览器 → 页面识别方式) wins over the deployment default, so flipping
|
|
85
|
-
* that switch changes the very next operation — no restart, no config edit.
|
|
86
|
-
* @param configured - the mode from this provider's config.
|
|
87
|
-
* @returns the effective mode.
|
|
88
|
-
*/
|
|
89
|
-
export declare function resolveSnapshotEngine(configured: 'legacy' | 'aria'): 'legacy' | 'aria';
|
|
90
|
-
/**
|
|
91
|
-
* Playwright-backed {@link BrowserProvider}: one shared browser, one context
|
|
92
|
-
* per owner key, LRU eviction, idle disposal, ref-based snapshot interaction.
|
|
93
|
-
*/
|
|
94
|
-
/** One owner's live session handle inside the provider's map. */
|
|
95
|
-
interface OwnerEntry {
|
|
96
|
-
context: BrowserContext;
|
|
97
|
-
session: PlaywrightSession;
|
|
98
|
-
lastUsed: number;
|
|
99
|
-
/** Operations in flight; idle disposal defers while this is non-zero. */
|
|
100
|
-
pending: number;
|
|
101
|
-
timer: NodeJS.Timeout | undefined;
|
|
102
|
-
}
|
|
103
|
-
/** One action's outcome: either a native dialog blocked it, or it produced a value. */
|
|
104
|
-
interface ActionResult {
|
|
105
|
-
blocked: boolean;
|
|
106
|
-
pending?: PendingDialogRecord | undefined;
|
|
107
|
-
value?: unknown;
|
|
108
|
-
}
|
|
109
|
-
/** A native dialog parked on one page, waiting for an explicit answer. */
|
|
110
|
-
interface PendingDialogRecord {
|
|
111
|
-
dialog: Dialog;
|
|
112
|
-
type: string;
|
|
113
|
-
message: string;
|
|
114
|
-
owner: string | undefined;
|
|
115
|
-
}
|
|
116
|
-
export declare class PlaywrightProvider {
|
|
117
|
-
config: PlaywrightConfig;
|
|
118
|
-
id: string;
|
|
119
|
-
/** Legacy (persistent:false) shared browser instance. */
|
|
120
|
-
browser: Browser | undefined;
|
|
121
|
-
/** Persistent-mode shared profile-backed context (the personal window). */
|
|
122
|
-
context: BrowserContext | undefined;
|
|
123
|
-
entries: Map<string, OwnerEntry>;
|
|
124
|
-
launchError: unknown;
|
|
125
|
-
/** Resolved profile directory (persistent mode only). */
|
|
126
|
-
profileDir: string;
|
|
127
|
-
/**
|
|
128
|
-
* Where cookies+localStorage are exported for the session-cookie fallback.
|
|
129
|
-
* Undefined outside persistent mode, and that is load-bearing: every writer
|
|
130
|
-
* and reader below guards on `undefined`, while an empty string would make
|
|
131
|
-
* `'' + '.tmp'` land in the process working directory.
|
|
132
|
-
*/
|
|
133
|
-
stateFile: string | undefined;
|
|
134
|
-
/** Owner that raised the dialog currently parked on a page. */
|
|
135
|
-
lastActor: string | undefined;
|
|
136
|
-
lastStatePersist: number;
|
|
137
|
-
constructor(config: PlaywrightConfig);
|
|
138
|
-
available(): boolean;
|
|
139
|
-
/** Pages already carrying the dialog guard (WeakSet: pages stay collectable). */
|
|
140
|
-
armedDialogs: WeakSet<WeakKey>;
|
|
141
|
-
/** Pending native dialog per page: { dialog, type, message }. Never auto-resolved. */
|
|
142
|
-
pendingDialogs: Map<Page, PendingDialogRecord>;
|
|
143
|
-
/** Resolvers waiting for the next dialog on a page (the action/dialog race). */
|
|
144
|
-
dialogWaits: Map<any, any>;
|
|
145
|
-
/**
|
|
146
|
-
* Make a page report native dialogs instead of having them silently
|
|
147
|
-
* dismissed. The dialog is parked as a per-page pending state and left
|
|
148
|
-
* untouched: the page stays blocked until browser_dialog answers it, and
|
|
149
|
-
* whichever action raised it returns early through the dialog race.
|
|
150
|
-
* See DIALOG-POLICY.md for why this shape beats any parameter-level check.
|
|
151
|
-
*/
|
|
152
|
-
armDialogGuard(page: Page): void;
|
|
153
|
-
/** The dialog currently blocking this page, or undefined. */
|
|
154
|
-
pendingDialogFor(page: Page): PendingDialogRecord | undefined;
|
|
155
|
-
/**
|
|
156
|
-
* Remove and return the pending dialog (atomic: one dialog, one answer).
|
|
157
|
-
* Only the owner that raised it may take it: in persistent mode several
|
|
158
|
-
* sessions share one page, and answering another session's dialog would
|
|
159
|
-
* execute its action on their behalf.
|
|
160
|
-
*/
|
|
161
|
-
takePendingDialog(page: Page, owner: string): PendingDialogRecord | undefined;
|
|
162
|
-
/**
|
|
163
|
-
* Wake when a dialog appears on this page. cancel() unregisters the waiter
|
|
164
|
-
* so a finished action leaves nothing behind.
|
|
165
|
-
*/
|
|
166
|
-
armDialogWait(page: Page): {
|
|
167
|
-
promise: Promise<true>;
|
|
168
|
-
cancel: () => void;
|
|
169
|
-
};
|
|
170
|
-
/** One line describing a parked dialog for the tool result. */
|
|
171
|
-
dialogNoteFor(pending: PendingDialogRecord): string;
|
|
172
|
-
acquire(owner: string): Promise<PlaywrightSession>;
|
|
173
|
-
disposeOwner(owner: string): Promise<void>;
|
|
174
|
-
dispose(): Promise<void>;
|
|
175
|
-
/** Touch the entry's last-used clock and re-arm its idle timer. */
|
|
176
|
-
touch(owner: string): void;
|
|
177
|
-
/** Mark one operation in flight so the idle timer cannot kill it mid-way. */
|
|
178
|
-
beginOp(owner: string): void;
|
|
179
|
-
/** Settle one operation and restart the idle countdown. */
|
|
180
|
-
endOp(owner: string): void;
|
|
181
|
-
/** Ensure the session entry for an owner still exists. */
|
|
182
|
-
isLive(owner: string): boolean;
|
|
183
|
-
/** Drop an owner's entry after a page-level crash without closing the window. */
|
|
184
|
-
invalidateOwner(owner: string): void;
|
|
185
|
-
armIdle(entry: OwnerEntry): void;
|
|
186
|
-
/**
|
|
187
|
-
* Ensure the shared persistent context is alive (persistent mode). A
|
|
188
|
-
* profile-backed window is launched once and reused until closed by the
|
|
189
|
-
* last owner, idle disposal, or an external close; login state lives in
|
|
190
|
-
* the profile plus the exported state file, so reopening is seamless.
|
|
191
|
-
*/
|
|
192
|
-
ensureContext(): Promise<BrowserContext>;
|
|
193
|
-
/** Remove stale Chrome singleton lock files inside the profile directory. */
|
|
194
|
-
clearChromeLocks(): Promise<void>;
|
|
195
|
-
/**
|
|
196
|
-
* Collect the login-state fallback WITHOUT ever opening a page while the
|
|
197
|
-
* window is put away.
|
|
198
|
-
*
|
|
199
|
-
* `context.storageState()` is not a pure read. For every origin this context
|
|
200
|
-
* has visited whose page is since gone, it opens a TEMPORARY PAGE, navigates
|
|
201
|
-
* it to that origin, reads its storage, and closes it again
|
|
202
|
-
* (playwright-core 1.62.1 `coreBundle.js:51663-51683`). The origins set is
|
|
203
|
-
* accumulated by `addVisitedOrigin` on every navigation
|
|
204
|
-
* (`coreBundle.js:22352` → `51634`) and only reset by `setStorageState`, so a
|
|
205
|
-
* long-lived context always has leftovers. That temporary page is created
|
|
206
|
-
* through `Target.createTarget` without `background: true`
|
|
207
|
-
* (`coreBundle.js:38340-38342`) — and Chromium activates the window on every
|
|
208
|
-
* tab creation. Same root as Cause 2 in FOCUS-STEALING.md, different trigger:
|
|
209
|
-
* a bookkeeping step that ran after every call could drag a minimized window
|
|
210
|
-
* back onto the screen.
|
|
211
|
-
*
|
|
212
|
-
* Cookies alone never open a page (`coreBundle.js:51639`), so when the window
|
|
213
|
-
* is minimized — or its state cannot be read at all — export cookies only.
|
|
214
|
-
* Nothing durable is lost: this file's job is the session-cookie fallback, and
|
|
215
|
-
* localStorage already survives in the persistent profile. When the window is
|
|
216
|
-
* positively visible we take the full `storageState()`, because a temporary
|
|
217
|
-
* page cannot disturb someone who is already looking at the window.
|
|
218
|
-
*/
|
|
219
|
-
collectLoginState(context: BrowserContext): Promise<{
|
|
220
|
-
cookies: unknown[];
|
|
221
|
-
origins: unknown[];
|
|
222
|
-
}>;
|
|
223
|
-
/** Export the login-state fallback to the state file (session-cookie fallback). */
|
|
224
|
-
persistState(context: BrowserContext): Promise<void>;
|
|
225
|
-
/**
|
|
226
|
-
* Throttled, fire-and-forget variant called after every operation.
|
|
227
|
-
*
|
|
228
|
-
* The "is the window minimized?" question moved inside `collectLoginState`,
|
|
229
|
-
* which answers it by choosing a safe API rather than by giving up: the export
|
|
230
|
-
* now runs in either window state, so the session-cookie fallback stops having
|
|
231
|
-
* a hole exactly where it matters most (a user who minimized the window is the
|
|
232
|
-
* one most likely to close it by hand).
|
|
233
|
-
*/
|
|
234
|
-
persistStateSoon(context: BrowserContext): void;
|
|
235
|
-
/**
|
|
236
|
-
* Re-inject a previously exported state. Durable cookies already live in
|
|
237
|
-
* the profile; this covers session cookies the browser drops on close and
|
|
238
|
-
* localStorage entries, so logins survive window close/reopen.
|
|
239
|
-
*/
|
|
240
|
-
restoreState(context: BrowserContext): Promise<void>;
|
|
241
|
-
/** Launch the browser once, probing the configured or auto-detected channel. */
|
|
242
|
-
ensureBrowser(): Promise<Browser>;
|
|
243
|
-
}
|
|
244
|
-
/** Feature fingerprint of one ref-bearing node, for cross-snapshot diffing. */
|
|
245
|
-
interface RefSignature {
|
|
246
|
-
role?: string;
|
|
247
|
-
name?: string;
|
|
248
|
-
level?: number;
|
|
249
|
-
checked?: boolean;
|
|
250
|
-
selected?: boolean;
|
|
251
|
-
disabled?: boolean;
|
|
252
|
-
href?: string;
|
|
253
|
-
parentRef?: string;
|
|
254
|
-
}
|
|
255
|
-
/** Live session over one browser context owned by one caller. */
|
|
256
|
-
declare class PlaywrightSession {
|
|
257
|
-
provider: PlaywrightProvider;
|
|
258
|
-
owner: string;
|
|
259
|
-
context: BrowserContext;
|
|
260
|
-
currentIndex: number;
|
|
261
|
-
closed: boolean;
|
|
262
|
-
/** Set while answering a dialog or reporting it: page access must not be refused then. */
|
|
263
|
-
bypassDialogGuard: boolean;
|
|
264
|
-
/** Reused CDP session for the current page (DOM nodeIds are session-bound). */
|
|
265
|
-
cdpSession: CDPSession | undefined;
|
|
266
|
-
/** The page the cached CDP session is attached to. */
|
|
267
|
-
cdpPage: Page | undefined;
|
|
268
|
-
/** Pages already wired for console/network capture (WeakSet: pages stay collectable). */
|
|
269
|
-
trackedPages: WeakSet<Page>;
|
|
270
|
-
/** Stable id per page, so diagnostics can be reported for one page only. */
|
|
271
|
-
pageIds: WeakMap<Page, number>;
|
|
272
|
-
/** Ids handed out so far (monotonic within the session). */
|
|
273
|
-
pageIdSeq: number;
|
|
274
|
-
/** Bounded console + pageerror ring, shared by every tracked page of this session. */
|
|
275
|
-
consoleLog: DiagnosticsEntry[];
|
|
276
|
-
/** Bounded network ring, shared by every tracked page of this session. */
|
|
277
|
-
networkLog: DiagnosticsEntry[];
|
|
278
|
-
/** Monotonic sequence handed to each captured entry. */
|
|
279
|
-
diagSeq: number;
|
|
280
|
-
/** Evicted entries, so truncation is visible instead of silent. */
|
|
281
|
-
diagDropped: {
|
|
282
|
-
console: number;
|
|
283
|
-
network: number;
|
|
284
|
-
};
|
|
285
|
-
/** In-flight requests keyed by request object, for status and duration attribution. */
|
|
286
|
-
requestEntries: WeakMap<Request, {
|
|
287
|
-
entry: DiagnosticsEntry;
|
|
288
|
-
startedAt: number;
|
|
289
|
-
}>;
|
|
290
|
-
/** Ring capacity per stream. */
|
|
291
|
-
diagLimit: number;
|
|
292
|
-
/** ref -> feature signature of the previous capture (diff baseline). */
|
|
293
|
-
lastRefSignatures: Map<string, RefSignature> | undefined;
|
|
294
|
-
/** Nonce of the baseline ref space; a change means navigation reset. */
|
|
295
|
-
lastRefNonce: string | undefined;
|
|
296
|
-
constructor(provider: PlaywrightProvider, owner: string, context: BrowserContext);
|
|
297
|
-
/** Run one operation under busy tracking: idle disposal defers until it settles. */
|
|
298
|
-
run<T>(operation: () => Promise<T>): Promise<T>;
|
|
299
|
-
/** Fast actionability pre-check (visible/enabled/editable) before an action. */
|
|
300
|
-
assertActionable(locator: Locator, options?: {
|
|
301
|
-
editable?: boolean;
|
|
302
|
-
}): Promise<void>;
|
|
303
|
-
/** Wait for an in-flight navigation (started by a click) to settle. */
|
|
304
|
-
settleNavigation(page: Page): Promise<void>;
|
|
305
|
-
/** Page-level render stability, bounded — a busy page never stalls the agent. */
|
|
306
|
-
waitForPageStable(page: Page, timeoutMs?: number): Promise<void>;
|
|
307
|
-
/** Element-level stability before a pointer/keyboard action, bounded. Returns true when stable. */
|
|
308
|
-
waitForElementStable(locator: Locator): Promise<boolean>;
|
|
309
|
-
/** Post-action settle: wait out a real navigation, then render-stabilize before snapshotting. */
|
|
310
|
-
settleAndSnapshot(page: Page, navChanged: boolean): Promise<BrowserSnapshot>;
|
|
311
|
-
/**
|
|
312
|
-
* Snapshot substitute while a native dialog blocks the page: only locally
|
|
313
|
-
* cached data (page.url() needs no round trip) — page.evaluate() and
|
|
314
|
-
* page.title() would both stall behind the dialog.
|
|
315
|
-
*/
|
|
316
|
-
blockedSnapshot(page: Page, pending: PendingDialogRecord | undefined): {
|
|
317
|
-
url: string;
|
|
318
|
-
title: string;
|
|
319
|
-
nodes: never[];
|
|
320
|
-
totalRefs: number;
|
|
321
|
-
truncated: boolean;
|
|
322
|
-
dialogNote: string;
|
|
323
|
-
};
|
|
324
|
-
/**
|
|
325
|
-
* Run one page action that may raise a native dialog. Such an action never
|
|
326
|
-
* resolves while the dialog is up, so race it against the dialog appearing
|
|
327
|
-
* and hand the caller the pending state instead (Playwright-MCP pattern).
|
|
328
|
-
*/
|
|
329
|
-
runAction(page: Page, action: () => Promise<unknown>): Promise<ActionResult>;
|
|
330
|
-
/**
|
|
331
|
-
* Answer the pending native dialog and let the action blocked behind it
|
|
332
|
-
* finish. This is the only place a dialog is ever accepted or dismissed.
|
|
333
|
-
*/
|
|
334
|
-
resolveDialog(accept: boolean, promptText: string | undefined, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
335
|
-
/** Read the snapshot tree from one page (must run inside a run()). */
|
|
336
|
-
capture(page: Page, opts: {
|
|
337
|
-
interactiveOnly?: boolean;
|
|
338
|
-
} | undefined): Promise<BrowserSnapshot>;
|
|
339
|
-
/**
|
|
340
|
-
* Semantic-tree capture via Playwright's official ariaSnapshot(mode:'ai').
|
|
341
|
-
* An unavailable or unreadable aria tree (`missing`) degrades to the legacy
|
|
342
|
-
* DOM walker instead of returning an empty snapshot: the model must never
|
|
343
|
-
* receive "the page has no elements" because the engine failed to parse.
|
|
344
|
-
*/
|
|
345
|
-
captureAria(page: Page, opts: {
|
|
346
|
-
interactiveOnly?: boolean;
|
|
347
|
-
} | undefined): Promise<{
|
|
348
|
-
engineNote: string;
|
|
349
|
-
url: string;
|
|
350
|
-
title: string;
|
|
351
|
-
nodes: BrowserNode[];
|
|
352
|
-
totalRefs: number;
|
|
353
|
-
truncated: boolean;
|
|
354
|
-
} | {
|
|
355
|
-
url: string;
|
|
356
|
-
title: string;
|
|
357
|
-
nodes: BrowserNode[];
|
|
358
|
-
totalRefs: number;
|
|
359
|
-
truncated: boolean;
|
|
360
|
-
}>;
|
|
361
|
-
/** Legacy capture: injected DOM walker (default engine, byte-identical output). */
|
|
362
|
-
captureLegacy(page: Page, opts: {
|
|
363
|
-
interactiveOnly?: boolean;
|
|
364
|
-
} | undefined): Promise<{
|
|
365
|
-
url: string;
|
|
366
|
-
title: string;
|
|
367
|
-
nodes: BrowserNode[];
|
|
368
|
-
totalRefs: number;
|
|
369
|
-
truncated: boolean;
|
|
370
|
-
}>;
|
|
371
|
-
/**
|
|
372
|
-
* Attach an incremental diff when snapshot.diff is enabled. Absence of the
|
|
373
|
-
* diff field means "full snapshot follows": first capture of the session,
|
|
374
|
-
* navigation reset, or a truncated tree. The diff is keyed by the stable
|
|
375
|
-
* data-dsh-ref, so legacy and aria engines share the same contract.
|
|
376
|
-
*/
|
|
377
|
-
attachDiff(snap: BrowserSnapshot): BrowserSnapshot;
|
|
378
|
-
/** Build ref -> feature signature map from a captured tree, with parent refs. */
|
|
379
|
-
buildRefSignatures(nodes: readonly BrowserNode[]): Map<string, RefSignature>;
|
|
380
|
-
/** The document nonce shared by this snapshot's refs, or undefined. */
|
|
381
|
-
refNonce(nodes: readonly BrowserNode[]): string | undefined;
|
|
382
|
-
navigate(url: string, waitUntil: LoadState, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
383
|
-
url: string;
|
|
384
|
-
title: string;
|
|
385
|
-
nodes: never[];
|
|
386
|
-
totalRefs: number;
|
|
387
|
-
truncated: boolean;
|
|
388
|
-
dialogNote: string;
|
|
389
|
-
}>;
|
|
390
|
-
snapshot(opts?: {
|
|
391
|
-
interactiveOnly?: boolean;
|
|
392
|
-
}): Promise<BrowserSnapshot | {
|
|
393
|
-
url: string;
|
|
394
|
-
title: string;
|
|
395
|
-
nodes: never[];
|
|
396
|
-
totalRefs: number;
|
|
397
|
-
truncated: boolean;
|
|
398
|
-
dialogNote: string;
|
|
399
|
-
}>;
|
|
400
|
-
/**
|
|
401
|
-
* Codex-style click-point resolution. Tries a scroll-alignment ladder
|
|
402
|
-
* (centre, then end, then start). For each alignment: scroll the element
|
|
403
|
-
* into view with a native scrollIntoView, wait for its bounding rect to
|
|
404
|
-
* stop moving, sample the rect centre in-page (sub-pixel, no screenshot
|
|
405
|
-
* measurement), then hit-test that exact point with elementFromPoint — so
|
|
406
|
-
* an overlaying element can never swallow the click silently. Returns the
|
|
407
|
-
* first unobstructed {x, y}, or throws describing what intercepted it.
|
|
408
|
-
*/
|
|
409
|
-
resolveClickPoint(locator: Locator, signal: AbortSignal | undefined): Promise<{
|
|
410
|
-
x: number;
|
|
411
|
-
y: number;
|
|
412
|
-
hitDesc: string;
|
|
413
|
-
hitSelf: boolean;
|
|
414
|
-
}>;
|
|
415
|
-
click(ref: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
416
|
-
url: string;
|
|
417
|
-
title: string;
|
|
418
|
-
nodes: never[];
|
|
419
|
-
totalRefs: number;
|
|
420
|
-
truncated: boolean;
|
|
421
|
-
dialogNote: string;
|
|
422
|
-
}>;
|
|
423
|
-
/**
|
|
424
|
-
* Click an element the snapshot gave no ref, addressed by its visible label
|
|
425
|
-
* (e.g. a menu item revealed by browser_hover that is plain text). The
|
|
426
|
-
* label must equal an element's own direct text; the deepest match wins,
|
|
427
|
-
* and the landing note names what was actually clicked plus how many
|
|
428
|
-
* elements shared the label.
|
|
429
|
-
*/
|
|
430
|
-
clickText(text: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
431
|
-
url: string;
|
|
432
|
-
title: string;
|
|
433
|
-
nodes: never[];
|
|
434
|
-
totalRefs: number;
|
|
435
|
-
truncated: boolean;
|
|
436
|
-
dialogNote: string;
|
|
437
|
-
}>;
|
|
438
|
-
/**
|
|
439
|
-
* Enforce the navigation policy for the link a click is about to follow.
|
|
440
|
-
* Click navigation does not run through navigate(), so this is the only place
|
|
441
|
-
* the rule lives and every click path (ref, text, geometric ref, raw x/y)
|
|
442
|
-
* calls it — a new path cannot quietly skip the allow-list.
|
|
443
|
-
*
|
|
444
|
-
* The policy is about which HOSTS this browser may visit, so it applies to
|
|
445
|
-
* http(s) destinations. An in-page `javascript:` link runs the page's own
|
|
446
|
-
* code without navigating anywhere and is allowed (refusing it made ordinary
|
|
447
|
-
* JS links unclickable); every other scheme — file:, data:, mailto:, tel:,
|
|
448
|
-
* custom ones — stays refused.
|
|
449
|
-
*/
|
|
450
|
-
assertClickTargetAllowed(href: string | null): void;
|
|
451
|
-
/**
|
|
452
|
-
* Enforce the host policy on a tab before this session starts driving it. A
|
|
453
|
-
* page can open a tab by itself (`target="_blank"`, `window.open`) and the
|
|
454
|
-
* plugin never sees that navigation, so the policy is applied at the point of
|
|
455
|
-
* use instead: a tab outside the allow-list stays open — the user may well be
|
|
456
|
-
* using it — but this agent refuses to switch to it.
|
|
457
|
-
*
|
|
458
|
-
* http(s) is checked against allowedDomains; `about:blank` is allowed because
|
|
459
|
-
* it is the plugin's own interim state and has no host; every other scheme
|
|
460
|
-
* (file:, data:, chrome:, devtools:) is refused outright.
|
|
461
|
-
*/
|
|
462
|
-
assertTabAllowed(page: Page): void;
|
|
463
|
-
/**
|
|
464
|
-
* Shared click core: enforce the URL policy for link targets, run the
|
|
465
|
-
* Codex-style geometry ladder with the Playwright actionability fallback,
|
|
466
|
-
* then report what actually received the click. The label names the target
|
|
467
|
-
* in the landing note; shared is how many elements matched it (0 for refs).
|
|
468
|
-
*/
|
|
469
|
-
clickLocator(locator: Locator, signal: AbortSignal | undefined, label: string, shared: number): Promise<BrowserSnapshot | {
|
|
470
|
-
url: string;
|
|
471
|
-
title: string;
|
|
472
|
-
nodes: never[];
|
|
473
|
-
totalRefs: number;
|
|
474
|
-
truncated: boolean;
|
|
475
|
-
dialogNote: string;
|
|
476
|
-
}>;
|
|
477
|
-
clickAt(x: number, y: number, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
478
|
-
url: string;
|
|
479
|
-
title: string;
|
|
480
|
-
nodes: never[];
|
|
481
|
-
totalRefs: number;
|
|
482
|
-
truncated: boolean;
|
|
483
|
-
dialogNote: string;
|
|
484
|
-
}>;
|
|
485
|
-
clickAtRef(ref: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
486
|
-
url: string;
|
|
487
|
-
title: string;
|
|
488
|
-
nodes: never[];
|
|
489
|
-
totalRefs: number;
|
|
490
|
-
truncated: boolean;
|
|
491
|
-
dialogNote: string;
|
|
492
|
-
}>;
|
|
493
|
-
fill(ref: string, text: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
494
|
-
/** Read the field back and decide whether the fill landed. */
|
|
495
|
-
verifyFill(locator: Locator, tag: string, text: string): Promise<boolean>;
|
|
496
|
-
press(ref: string, key: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
497
|
-
url: string;
|
|
498
|
-
title: string;
|
|
499
|
-
nodes: never[];
|
|
500
|
-
totalRefs: number;
|
|
501
|
-
truncated: boolean;
|
|
502
|
-
dialogNote: string;
|
|
503
|
-
}>;
|
|
504
|
-
/**
|
|
505
|
-
* Hover the referenced element: rest the pointer on it so hover-revealed
|
|
506
|
-
* content appears (CSS :hover rules, JS mouseenter handlers, submenus,
|
|
507
|
-
* tooltips, chart values, player controls). Hover never activates
|
|
508
|
-
* anything: it only reveals. The ref count before and after the pointer
|
|
509
|
-
* moves is the only cheap proof that something appeared, so the returned
|
|
510
|
-
* snapshot carries a note comparing them.
|
|
511
|
-
*/
|
|
512
|
-
hover(ref: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
513
|
-
/**
|
|
514
|
-
* Hover a trigger the snapshot gave no ref, addressed by its visible label
|
|
515
|
-
* (e.g. a plain span that reveals a menu on mouseenter). The label must
|
|
516
|
-
* equal an element's own direct text; the deepest match wins, and the
|
|
517
|
-
* landing note names what was hovered plus how many elements shared it.
|
|
518
|
-
*/
|
|
519
|
-
hoverText(text: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
520
|
-
/**
|
|
521
|
-
* Shared hover core: rest the pointer on a resolved locator, then report
|
|
522
|
-
* what the actionable ref count did. The label names the target in the
|
|
523
|
-
* landing note; shared is how many elements matched that label (0 when the
|
|
524
|
-
* target came from a ref).
|
|
525
|
-
*/
|
|
526
|
-
hoverLocator(locator: Locator, signal: AbortSignal | undefined, label: string, shared: number): Promise<BrowserSnapshot>;
|
|
527
|
-
scroll(direction: 'up' | 'down', amount: number, ref: string, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
528
|
-
back(signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
529
|
-
forward(signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
530
|
-
wait(ms: number, signal: AbortSignal | undefined): Promise<void>;
|
|
531
|
-
tabs(): Promise<{
|
|
532
|
-
index: number;
|
|
533
|
-
url: string;
|
|
534
|
-
title: string;
|
|
535
|
-
active: boolean;
|
|
536
|
-
}[]>;
|
|
537
|
-
switchTab(index: number, signal: AbortSignal | undefined): Promise<BrowserSnapshot | {
|
|
538
|
-
url: string;
|
|
539
|
-
title: string;
|
|
540
|
-
nodes: never[];
|
|
541
|
-
totalRefs: number;
|
|
542
|
-
truncated: boolean;
|
|
543
|
-
dialogNote: string;
|
|
544
|
-
}>;
|
|
545
|
-
openTab(url: string, waitUntil: LoadState, signal: AbortSignal | undefined): Promise<BrowserSnapshot>;
|
|
546
|
-
closeTab(index: number, signal: AbortSignal | undefined): Promise<void>;
|
|
547
|
-
/**
|
|
548
|
-
* Screenshots get their own floor instead of reusing the navigation budget: a
|
|
549
|
-
* full-page capture is sized by the page and encoded by the CPU, so a loaded machine
|
|
550
|
-
* legitimately needs seconds (2026-10-05: the Windows runner blew through the 2500ms
|
|
551
|
-
* navigation timeout while encoding a full-page PNG — a slow runner, not a defect).
|
|
552
|
-
* The floor only bounds failures; a capture that is ready returns immediately.
|
|
553
|
-
*/
|
|
554
|
-
screenshotTimeoutMs(): number;
|
|
555
|
-
screenshot(opts: {
|
|
556
|
-
fullPage?: boolean;
|
|
557
|
-
ref?: string;
|
|
558
|
-
} | undefined, signal: AbortSignal | undefined): Promise<{
|
|
559
|
-
mime: "image/png";
|
|
560
|
-
bytes: Buffer<ArrayBufferLike>;
|
|
561
|
-
}>;
|
|
562
|
-
pageData(): Promise<unknown>;
|
|
563
|
-
evaluate(expression: string, signal: AbortSignal | undefined): Promise<unknown>;
|
|
564
|
-
/**
|
|
565
|
-
* Send one raw CDP command on the current page's session and return its
|
|
566
|
-
* result. This is the "direct protocol" escape hatch behind every other
|
|
567
|
-
* tool — the model can inspect geometry, dispatch trusted input events or
|
|
568
|
-
* query the DOM the same way Chrome DevTools does. Policy (which methods
|
|
569
|
-
* are allowed) lives in the tool layer via lib/cdp-policy.js; this method
|
|
570
|
-
* itself performs no filtering so internal callers keep full control.
|
|
571
|
-
*/
|
|
572
|
-
cdp(method: string, params: Record<string, unknown>, signal: AbortSignal | undefined): Promise<unknown>;
|
|
573
|
-
close(): Promise<void>;
|
|
574
|
-
timeoutMs(): number;
|
|
575
|
-
ensurePage(): Promise<Page>;
|
|
576
|
-
/**
|
|
577
|
-
* Wire bounded console/network capture onto one page, exactly once. Capture is
|
|
578
|
-
* best-effort observation for the model: it must never throw into a page that is
|
|
579
|
-
* otherwise healthy, and it must never grow without a bound.
|
|
580
|
-
*/
|
|
581
|
-
trackPage(page: Page): void;
|
|
582
|
-
/** Stable id for one page of this session (used to scope diagnostics). */
|
|
583
|
-
pageIdFor(page: Page): number;
|
|
584
|
-
/** Id of the page this session is driving, or undefined when there is none. */
|
|
585
|
-
currentPageId(): number | undefined;
|
|
586
|
-
/**
|
|
587
|
-
* Split diagnostics into "the page this session is driving" and "everything
|
|
588
|
-
* else". The rings keep every page's entries so chronology and eviction stay
|
|
589
|
-
* honest, but a reader that promises one tab must not serve another tab's
|
|
590
|
-
* lines: that is how a model diagnoses an error that belongs to a page it is
|
|
591
|
-
* not even looking at.
|
|
592
|
-
*/
|
|
593
|
-
scopeToCurrentPage(entries: readonly DiagnosticsEntry[]): {
|
|
594
|
-
kept: DiagnosticsEntry[];
|
|
595
|
-
otherPages: number;
|
|
596
|
-
};
|
|
597
|
-
/** URL of the tab this session is driving right now, or null. */
|
|
598
|
-
currentUrl(): string | null;
|
|
599
|
-
/** Recent console messages and uncaught page errors, newest last. */
|
|
600
|
-
consoleMessages(args: {
|
|
601
|
-
level?: string;
|
|
602
|
-
limit?: number;
|
|
603
|
-
}): {
|
|
604
|
-
url: string | null;
|
|
605
|
-
level: string;
|
|
606
|
-
total: number;
|
|
607
|
-
returned: number;
|
|
608
|
-
dropped: number;
|
|
609
|
-
otherPages: number;
|
|
610
|
-
note: string;
|
|
611
|
-
entries: {
|
|
612
|
-
n: number;
|
|
613
|
-
level: string | undefined;
|
|
614
|
-
text: string;
|
|
615
|
-
source: string;
|
|
616
|
-
}[];
|
|
617
|
-
};
|
|
618
|
-
/** Recent network requests, newest last, with status and duration where known. */
|
|
619
|
-
networkRequests(args: {
|
|
620
|
-
urlContains?: string;
|
|
621
|
-
failedOnly?: boolean;
|
|
622
|
-
limit?: number;
|
|
623
|
-
}): {
|
|
624
|
-
url: string | null;
|
|
625
|
-
total: number;
|
|
626
|
-
returned: number;
|
|
627
|
-
dropped: number;
|
|
628
|
-
otherPages: number;
|
|
629
|
-
note: string;
|
|
630
|
-
entries: {
|
|
631
|
-
n: number;
|
|
632
|
-
method: string | undefined;
|
|
633
|
-
url: string;
|
|
634
|
-
status: number | null | undefined;
|
|
635
|
-
kind: string | undefined;
|
|
636
|
-
ms: number | null | undefined;
|
|
637
|
-
failure: string | null | undefined;
|
|
638
|
-
}[];
|
|
639
|
-
};
|
|
640
|
-
/**
|
|
641
|
-
* Refuse page access while a native dialog blocks the page: every call
|
|
642
|
-
* would otherwise stall until the action timeout. Only browser_dialog and
|
|
643
|
-
* the blocked-snapshot report are allowed through (DIALOG-POLICY.md).
|
|
644
|
-
*/
|
|
645
|
-
guardBlockedDialog(page: Page): void;
|
|
646
|
-
/** Refuse context-wide tab work while any page in it has a parked dialog. */
|
|
647
|
-
guardBlockedContext(): void;
|
|
648
|
-
/** Run one internal step with the dialog guard lifted. */
|
|
649
|
-
withDialogGuardLifted<T>(fn: () => Promise<T>): Promise<T>;
|
|
650
|
-
pageAt(index: number): Page | undefined;
|
|
651
|
-
refLocator(ref: string): Promise<Locator>;
|
|
652
|
-
currentPageSync(): Page;
|
|
653
|
-
assertLive(): void;
|
|
654
|
-
}
|
|
655
|
-
export {};
|