dsh-browser-plus 0.0.0-stage → 0.5.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/LICENSE +22 -0
  3. package/NOTICE.md +7 -0
  4. package/README.en.md +100 -0
  5. package/README.md +99 -2
  6. package/assets/dsh-browser-plus-256.png +0 -0
  7. package/assets/dsh-browser-plus-512.png +0 -0
  8. package/assets/dsh-browser-plus-small.svg +9 -0
  9. package/assets/dsh-browser-plus.ico +0 -0
  10. package/assets/dsh-browser-plus.svg +11 -0
  11. package/assets/readme-workspace.png +0 -0
  12. package/cordis.patch.yml +17 -0
  13. package/docs/MIGRATION.md +48 -0
  14. package/docs/README.md +22 -0
  15. package/docs/SOAK-CHECKLIST.md +98 -0
  16. package/docs/architecture.md +88 -0
  17. package/docs/tool-reference.md +124 -0
  18. package/docs/user-guide.md +121 -0
  19. package/docs/why-browser.md +45 -0
  20. package/lib/browser/runtime.d.ts +225 -0
  21. package/lib/browser/runtime.js +302 -0
  22. package/lib/browser/types.d.ts +668 -0
  23. package/lib/browser/types.js +18 -0
  24. package/lib/browser-electron/auth-cookies.d.ts +54 -0
  25. package/lib/browser-electron/auth-cookies.js +83 -0
  26. package/lib/browser-electron/chrome-state.d.ts +187 -0
  27. package/lib/browser-electron/chrome-state.js +12 -0
  28. package/lib/browser-electron/entry.d.ts +66 -0
  29. package/lib/browser-electron/entry.js +62 -0
  30. package/lib/browser-electron/fingerprint.d.ts +29 -0
  31. package/lib/browser-electron/fingerprint.js +42 -0
  32. package/lib/browser-electron/host-main.d.ts +18 -0
  33. package/lib/browser-electron/host-main.js +2494 -0
  34. package/lib/browser-electron/icon.d.ts +11 -0
  35. package/lib/browser-electron/icon.js +23 -0
  36. package/lib/browser-electron/page-chrome.d.ts +21 -0
  37. package/lib/browser-electron/page-chrome.js +2034 -0
  38. package/lib/browser-electron/provider.d.ts +709 -0
  39. package/lib/browser-electron/provider.js +2575 -0
  40. package/lib/browser-electron/remote-host.d.ts +143 -0
  41. package/lib/browser-electron/remote-host.js +952 -0
  42. package/lib/browser-electron/task-summary.d.ts +2 -0
  43. package/lib/browser-electron/task-summary.js +12 -0
  44. package/lib/browser-electron/task-thumbnail.d.ts +11 -0
  45. package/lib/browser-electron/task-thumbnail.js +9 -0
  46. package/lib/browser-electron/write-guard.d.ts +41 -0
  47. package/lib/browser-electron/write-guard.js +123 -0
  48. package/lib/index.d.ts +16 -0
  49. package/lib/index.js +14 -0
  50. package/lib/tool-browser/index.d.ts +31 -0
  51. package/lib/tool-browser/index.js +1931 -0
  52. package/package.json +95 -4
  53. package/screenshots.json +3 -0
  54. package/scripts/build-icons.mjs +80 -0
  55. package/scripts/capture-window.ps1 +79 -0
  56. package/scripts/crop-image.ps1 +20 -0
  57. package/scripts/smoke-browser-tools.mjs +1968 -0
  58. package/scripts/smoke-chrome-world.mjs +63 -0
  59. package/scripts/smoke-electron-host.mjs +50 -0
  60. package/src/browser/runtime.ts +470 -0
  61. package/src/browser/types.ts +649 -0
  62. package/src/browser-electron/auth-cookies.ts +125 -0
  63. package/src/browser-electron/chrome-state.ts +174 -0
  64. package/src/browser-electron/entry.ts +115 -0
  65. package/src/browser-electron/fingerprint.ts +45 -0
  66. package/src/browser-electron/host-main.ts +2330 -0
  67. package/src/browser-electron/icon.ts +26 -0
  68. package/src/browser-electron/page-chrome.ts +2046 -0
  69. package/src/browser-electron/provider.ts +3088 -0
  70. package/src/browser-electron/remote-host.ts +1004 -0
  71. package/src/browser-electron/task-summary.ts +10 -0
  72. package/src/browser-electron/task-thumbnail.ts +17 -0
  73. package/src/browser-electron/write-guard.ts +134 -0
  74. package/src/index.ts +52 -0
  75. package/src/tool-browser/index.ts +1974 -0
  76. package/src/types/electron-shim.d.ts +143 -0
@@ -0,0 +1,709 @@
1
+ /**
2
+ * Electron-backed browser provider: `WebContentsView` sessions driven over
3
+ * `webContents.debugger` (CDP). The provider itself does not import Electron — it operates through the {@link ElectronBrowserViewHost} seam, which the
4
+ * desktop shell implements with real Electron objects. That keeps this
5
+ * package testable under plain Node and leaves the Electron dependency to the
6
+ * shell that owns the `BrowserWindow`.
7
+ * @module dsh-browser-plus/browser-electron
8
+ */
9
+ import type { BrowserChallenge, BrowserClearAuthRequest, BrowserClearAuthResult, BrowserContentRequest, BrowserContentResult, BrowserDragRequest, BrowserDragResult, BrowserPointerResult, BrowserPointerTarget, BrowserExecuteRequest, BrowserExecuteResult, BrowserFillRequest, BrowserFillResult, BrowserHandoffState, BrowserHistoryEntry, BrowserOpenOptions, BrowserOpenRequest, BrowserPressKeyRequest, BrowserProvider, BrowserRefRequest, BrowserScrapeRequest, BrowserScrapeStatus, BrowserScrollIntoViewRequest, BrowserScrollRequest, BrowserScrollResult, BrowserSessionId, BrowserSnapshotResult, BrowserSpaceInfo, BrowserTab, BrowserTaskInfo, BrowserTaskUpdate, BrowserUploadFileRequest, BrowserUploadFileResult, BrowserWaitForRequest, BrowserWaitForResult, ExportedCookie } from '../browser/types.ts';
10
+ /** Stable provider id registered with `ctx.browser`. */
11
+ export declare const ELECTRON_BROWSER_PROVIDER_ID = "electron";
12
+ /**
13
+ * One tab request raised by the injected chrome because a human used it.
14
+ *
15
+ * The host owns the pixels, the tab strip and the per-view secret, so it is the
16
+ * only party that can authenticate such a request; the provider owns the tab
17
+ * model, so it is the only party that may act on one. `tabId` is the host's own
18
+ * view id, which is also the provider's `ElectronViewHandle.id`, so no extra
19
+ * mapping table is needed.
20
+ */
21
+ export interface ChromeHostEvent {
22
+ /** For a 'move-tab' event: the index the tab was dropped at, after removal. */
23
+ readonly toIndex?: number;
24
+ readonly type: 'new-tab' | 'close-tab' | 'activate-tab' | 'move-tab';
25
+ /** Task key of the chrome that raised it. */
26
+ readonly taskKey: string;
27
+ /** Host view id of the tab; absent for `new-tab`. */
28
+ readonly tabId?: string;
29
+ /** For `new-tab`: open this http(s) url in the new tab (a bookmark click). */
30
+ readonly url?: string;
31
+ }
32
+ /**
33
+ * The minimal Electron surface this provider needs. Implemented by the
34
+ * desktop shell with a real `WebContentsView`; a fake implements it in tests.
35
+ */
36
+ export interface ElectronBrowserViewHost {
37
+ /**
38
+ * Subscribe to tab requests the host raises without being asked (a human
39
+ * clicking the chrome's own `+`, `×` or tab strip). Optional: a host whose
40
+ * chrome cannot speak first simply never raises one.
41
+ * @param listener - invoked once per authenticated chrome request.
42
+ */
43
+ onChromeEvent?(listener: (event: ChromeHostEvent) => void): void;
44
+ /**
45
+ * Create a new browser view and return a handle to its webContents-like
46
+ * surface. `key` (default 'default') identifies an isolated browser task in
47
+ * the shared BrowserWindow; `label` names that task. The host owns view
48
+ * attachment, sizing, task visibility, and removal; the provider owns
49
+ * CDP-driven behavior.
50
+ */
51
+ createView(key?: string, label?: string): ElectronViewHandle;
52
+ /**
53
+ * Destroy a view created by this host. Called on session close; idempotent
54
+ * for an already-destroyed view.
55
+ * @param handle - the handle returned by {@link createView}.
56
+ */
57
+ destroyView(handle: ElectronViewHandle): void;
58
+ /**
59
+ * Notify the host that this session selected a tab. In the shared-window
60
+ * host, a background task updates its active view without changing the
61
+ * human-selected visible task. Optional for headless/probe hosts.
62
+ * @param handle - the handle selected by its session.
63
+ */
64
+ showView?(handle: ElectronViewHandle): void;
65
+ /**
66
+ * Send a CDP `Input.*` command to the host's chrome frame view.
67
+ *
68
+ * The frame is not a tab and has no handle, so this is its own channel. It
69
+ * exists because the chrome can live in a view of its own (which is what lets
70
+ * the page viewport really shrink): input aimed at a page never reaches that
71
+ * view, and CDP input targets a webContents regardless of view stacking, so
72
+ * the page cannot stand in for it. Optional for hosts without a frame view.
73
+ * @param method - a CDP Input domain command, e.g. 'Input.dispatchMouseEvent'.
74
+ * @param params - that command's parameters.
75
+ */
76
+ chromeInput?(method: string, params?: Record<string, unknown>): Promise<void>;
77
+ /**
78
+ * Evaluate an expression inside the chrome frame's own document.
79
+ *
80
+ * The frame is a view of its own, so no page-directed call can read it. This is
81
+ * how the tests assert the toolbar's own animations instead of inferring them
82
+ * from the page's copy of the chrome. Optional for hosts without a frame view.
83
+ * @param expression - JavaScript evaluated in the frame, by value.
84
+ */
85
+ chromeEval?(expression: string): Promise<unknown>;
86
+ /**
87
+ * Append one operation to the human-facing trail for a view. Optional.
88
+ * @param viewId - the view to attribute the operation to.
89
+ * @param entry - the trail entry ({ action, params, ok, at }).
90
+ */
91
+ trace?(viewId: string, entry: unknown): void;
92
+ /**
93
+ * Cheap local usability probe for this host. MUST NOT make network calls and
94
+ * MUST NOT throw (a throw is reported as unavailable). Optional: a host that
95
+ * omits it is assumed usable, which keeps a desktop shell's shell-owned
96
+ * viewHost and test fakes working. A self-hosted host reports false when the
97
+ * pinned Electron binary cannot be resolved, so the seam can pick another
98
+ * provider (BROWSER_PROVIDER_UNAVAILABLE / BROWSER_PROVIDER_AMBIGUOUS)
99
+ * instead of failing later on the first open().
100
+ */
101
+ isAvailable?(): boolean;
102
+ /** List browser tasks with their labels. Legacy method name retained for compatibility. */
103
+ listWindows?(): Promise<Array<{
104
+ key: string;
105
+ label: string;
106
+ }>>;
107
+ /** List task summaries when the host exposes a visible workspace. */
108
+ listTasks?(): Promise<readonly BrowserTaskInfo[]>;
109
+ /** Read one task summary from the visible workspace. */
110
+ getTask?(key: string): Promise<BrowserTaskInfo | undefined>;
111
+ /** Apply a task status/control update to the visible workspace. */
112
+ updateTask?(key: string, update: BrowserTaskUpdate): Promise<BrowserTaskInfo | undefined>;
113
+ }
114
+ /**
115
+ * A CDP-capable view handle. This is the subset of Electron's
116
+ * `WebContents`/`WebContentsView` the provider drives; the shell's real
117
+ * implementation adapts `webContents.debugger` to it.
118
+ */
119
+ export interface ElectronViewHandle {
120
+ /** Unique id of the backing view, used for diagnostics. */
121
+ readonly id: string;
122
+ /**
123
+ * Send one CDP command and resolve with its result. Rejects when the
124
+ * debugger is not attached or the command fails.
125
+ * @param method - CDP method, e.g. `Page.navigate`.
126
+ * @param params - CDP command parameters.
127
+ * @returns the CDP `result` object.
128
+ */
129
+ sendCommand(method: string, params?: Record<string, unknown>): Promise<Record<string, unknown>>;
130
+ /**
131
+ * Read the most recent auto-accepted JS dialog for this view (and clear it).
132
+ * Optional: hosts without JS-dialog supervision omit it.
133
+ * @returns the dialog detail ({ type, message, prompt? }) or null.
134
+ */
135
+ clearDialog?(): Promise<unknown>;
136
+ /** Optional: hosts without JS-dialog supervision omit it. */
137
+ setDialogPolicy?(policy: DialogPolicy): Promise<unknown>;
138
+ /** Optional: bounded console capture. */
139
+ readConsole?(clear?: boolean): Promise<unknown>;
140
+ /** Optional: bounded network capture. */
141
+ readNetwork?(clear?: boolean): Promise<unknown>;
142
+ /**
143
+ * Remove cookies matching a domain/name filter. Optional: hosts without a
144
+ * deletable cookie store omit it.
145
+ */
146
+ clearCookies?(filter: {
147
+ readonly domain?: string;
148
+ readonly name?: string;
149
+ readonly all?: boolean;
150
+ }): Promise<{
151
+ readonly removed: number;
152
+ readonly names: readonly string[];
153
+ }>;
154
+ /** Set this view's browser task label; it titles the shared window only when selected. Optional. */
155
+ label?(label: string): Promise<void>;
156
+ /**
157
+ * Re-apply the host's own page chrome to the current document. Optional: a host
158
+ * that does not own the chrome omits it, and the provider then injects its own
159
+ * tokenless copy as a fallback.
160
+ */
161
+ reinstallChrome?(): Promise<void>;
162
+ }
163
+ /**
164
+ * How the host answers a JS dialog (alert/confirm/prompt).
165
+ *
166
+ * A dialog freezes the renderer until it is answered, so the default stays
167
+ * `accept` - automation must never hang on one. `dismiss` is for pages whose
168
+ * confirmation is part of what is being driven (delete prompts and the like),
169
+ * and `promptText` supplies the value for a prompt.
170
+ */
171
+ /** One captured console message. */
172
+ export interface BrowserConsoleMessage {
173
+ level: string;
174
+ text: string;
175
+ at: string;
176
+ }
177
+ /** One captured network request. */
178
+ export interface BrowserNetworkRequest {
179
+ method: string;
180
+ url: string;
181
+ status?: number;
182
+ mime?: string;
183
+ kind?: string;
184
+ failed?: string;
185
+ ms?: number;
186
+ at: string;
187
+ }
188
+ /** Device/viewport/media emulation for one tab (browser_emulate). */
189
+ export interface EmulateOptions {
190
+ readonly width?: number;
191
+ readonly height?: number;
192
+ readonly deviceScaleFactor?: number;
193
+ readonly mobile?: boolean;
194
+ readonly userAgent?: string;
195
+ readonly colorScheme?: 'light' | 'dark' | 'no-preference';
196
+ /** Undo everything this tool set on the tab. */
197
+ readonly clear?: boolean;
198
+ }
199
+ export interface DialogPolicy {
200
+ readonly behavior: 'accept' | 'dismiss';
201
+ readonly promptText?: string;
202
+ }
203
+ /** Provider config: navigation admission defaults and snapshot caps. */
204
+ export interface ElectronBrowserProviderConfig {
205
+ /** Allow navigation only to HTTP(S) URLs; reject anything else. Default true. */
206
+ readonly httpOnly?: boolean;
207
+ /** Maximum snapshot elements before truncation. Default 60. */
208
+ readonly snapshotMaxElements?: number;
209
+ /** Maximum content characters before truncation when no maxChars is given. Default 100_000. */
210
+ readonly contentMaxChars?: number;
211
+ /**
212
+ * Absolute directories a screenshot or download may write into. Defaults to
213
+ * the workspace and the OS temp directory ({@link defaultWriteRoots}); an
214
+ * empty list denies every write.
215
+ */
216
+ readonly writeRoots?: readonly string[];
217
+ /**
218
+ * Absolute directories `browser_upload_file` may read from. Defaults to the
219
+ * same roots as {@link writeRoots}; an empty list denies every upload.
220
+ */
221
+ readonly readRoots?: readonly string[];
222
+ }
223
+ /**
224
+ * CDP method/params for `Page.navigate`, as sent to {@link ElectronViewHandle.sendCommand}.
225
+ */
226
+ export interface CdpNavigateParams {
227
+ readonly url: string;
228
+ }
229
+ /**
230
+ * CDP method/params for `Input.dispatchMouseEvent` (a click press+release pair).
231
+ */
232
+ export interface CdpMouseParams {
233
+ readonly type: 'mousePressed' | 'mouseReleased' | 'mouseMoved';
234
+ readonly x: number;
235
+ readonly y: number;
236
+ readonly button: 'left' | 'right' | 'middle' | 'none';
237
+ readonly clickCount?: number;
238
+ /** Buttons held during the event; 1 while a left drag is in flight. */
239
+ readonly buttons?: number;
240
+ /** CDP modifier bitmask (Alt 1, Ctrl 2, Meta 4, Shift 8); see modifierMask. */
241
+ readonly modifiers?: number;
242
+ }
243
+ /** CDP method/params for `Input.insertText`. */
244
+ export interface CdpInsertTextParams {
245
+ readonly text: string;
246
+ }
247
+ /** CDP method/params for `Runtime.evaluate`. */
248
+ export interface CdpEvaluateParams {
249
+ readonly expression: string;
250
+ readonly returnByValue: boolean;
251
+ readonly awaitPromise?: boolean;
252
+ }
253
+ /** CDP method for a full-page screenshot capture. */
254
+ export declare const CDP_PAGE_CAPTURE_SCREENSHOT = "Page.captureScreenshot";
255
+ /** CDP method for runtime evaluation (the execute path). */
256
+ export declare const CDP_RUNTIME_EVALUATE = "Runtime.evaluate";
257
+ /**
258
+ * Decide how to hand a script to `Runtime.evaluate`.
259
+ *
260
+ * CDP evaluates an *expression*, so a script made of statements is a syntax
261
+ * error there. Try the expression form first - that keeps bare expressions and
262
+ * object literals returning their value, which is what the tool has always
263
+ * done - then fall back to a statement body. `const x = 1; return x` is the
264
+ * shape people actually type, and it used to come back as a bare SyntaxError.
265
+ */
266
+ export declare function buildEvaluateBody(script: string): {
267
+ body: string;
268
+ } | {
269
+ error: string;
270
+ };
271
+ /** CDP method for keyboard input. */
272
+ export declare const CDP_INPUT_DISPATCH_KEY_EVENT = "Input.dispatchKeyEvent";
273
+ /** CDP method for navigation. */
274
+ export declare const CDP_PAGE_NAVIGATE = "Page.navigate";
275
+ /** CDP methods used by native browser navigation controls. */
276
+ export declare const CDP_PAGE_GET_NAVIGATION_HISTORY = "Page.getNavigationHistory";
277
+ export declare const CDP_PAGE_NAVIGATE_TO_HISTORY_ENTRY = "Page.navigateToHistoryEntry";
278
+ export declare const CDP_PAGE_RELOAD = "Page.reload";
279
+ export declare const CDP_PAGE_STOP_LOADING = "Page.stopLoading";
280
+ /**
281
+ * Browser provider over Electron views. Sessions hold an ordered list of
282
+ * tabs; each tab is one view created by the host. The active tab receives
283
+ * every operation; switching tabs calls the host's optional `showView` and
284
+ * never loses state. Navigation is admitted only for HTTP(S) targets unless
285
+ * {@link ElectronBrowserProviderConfig.httpOnly} is disabled.
286
+ */
287
+ export declare class ElectronBrowserProvider implements BrowserProvider {
288
+ private readonly host;
289
+ readonly id = "electron";
290
+ private readonly sessions;
291
+ /** Stable task-key index so callers can recover a session after tool-layer state loss. */
292
+ private readonly sessionsByTask;
293
+ private readonly taskStates;
294
+ private readonly httpOnly;
295
+ private readonly snapshotMaxElements;
296
+ private readonly contentMaxChars;
297
+ private readonly writeRoots;
298
+ private readonly readRoots;
299
+ /** Background scrape batches, keyed by id; rows live on disk, not here. */
300
+ private readonly scrapes;
301
+ constructor(host: ElectronBrowserViewHost, config?: ElectronBrowserProviderConfig);
302
+ /**
303
+ * Apply one authenticated chrome request to the session that owns its task.
304
+ *
305
+ * Every field is re-checked here: the host authenticates the sender, this
306
+ * method decides whether the request still makes sense against the live tab
307
+ * model. A request that resolves to nothing (a stale strip, a tab closed a
308
+ * moment ago, a task with no session) is dropped rather than thrown, because
309
+ * a human click must never surface as an error inside a running tool call.
310
+ */
311
+ private handleChromeEvent;
312
+ /**
313
+ * Usable whenever the host can create views. A host that exposes a local
314
+ * {@link ElectronBrowserViewHost.isAvailable} probe is believed; a host that
315
+ * omits it (a desktop shell's known-good viewHost, or a test fake) is assumed
316
+ * usable. The probe is cheap and local, so this stays callable from the seam's
317
+ * provider-selection path; the host owns any caching it needs.
318
+ */
319
+ available(): boolean;
320
+ /**
321
+ * Open or recover the browser session for a task key. The tool layer normally
322
+ * caches this id, but the Provider is authoritative so a scoped tool reload or
323
+ * a lost cache cannot create a second task session with a different tab set.
324
+ * Sessions keep isolated tabs, active tab, and history while the host keeps one
325
+ * human-selected task view visible in the shared BrowserWindow.
326
+ */
327
+ open(options?: BrowserOpenOptions): Promise<BrowserSessionId>;
328
+ /** Open a URL in the active tab (default) or a new tab. */
329
+ openUrl(session: BrowserSessionId, request: BrowserOpenRequest, signal?: AbortSignal): Promise<void>;
330
+ /** List the session's tabs with their titles. */
331
+ listTabs(session: BrowserSessionId): Promise<readonly BrowserTab[]>;
332
+ /** Switch to a tab by id; background task tabs stay hidden until user-selected. */
333
+ switchTab(session: BrowserSessionId, tabId: string): Promise<void>;
334
+ /**
335
+ * Close one tab; closing the active tab activates the next. Resolves false when
336
+ * the id is not open in this session, so a miss is distinguishable from a close.
337
+ */
338
+ closeTab(session: BrowserSessionId, tabId: string): Promise<boolean>;
339
+ /** Close every tab and reset to one blank tab. */
340
+ reset(session: BrowserSessionId): Promise<void>;
341
+ /**
342
+ * Dispatch one input command under the same hang guard as the CDP reads. A
343
+ * renderer blocked in synchronous JS never acknowledges, so an unbounded await
344
+ * here would hang the tool call until the caller's budget expired.
345
+ * @param handle - the view to dispatch into.
346
+ * @param method - the CDP input method.
347
+ * @param params - its parameters.
348
+ * @param signal - optional caller signal.
349
+ */
350
+ private dispatchInput;
351
+ /**
352
+ * Tell a renderer it is focused, once, before synthesized input.
353
+ *
354
+ * Chromium drops a synthesized mouse *press* when the renderer does not
355
+ * believe it has focus — which is the normal state for a background task's
356
+ * view, and on a real page even for the visible one while its window is not
357
+ * active. Moves are not gated, so hover looked fine while every click
358
+ * resolved its target, reported success, and left the page untouched.
359
+ *
360
+ * Focus emulation keeps this on the trusted CDP input path: no synthetic
361
+ * DOM click, so the events stay isTrusted and nothing about the page's
362
+ * view of the browser changes.
363
+ */
364
+ private ensureInputFocus;
365
+ /**
366
+ * Admit one URL for a provider-driven fetch (navigation or download).
367
+ * The whole check is gated by `httpOnly`: when it is disabled, callers are
368
+ * trusted with any scheme. When it is enabled, only HTTP(S) is admitted and
369
+ * URL-embedded credentials are refused, so a target can never be reached
370
+ * with in-URL auth.
371
+ * @param url - the candidate URL.
372
+ * @param subject - the operation name used in the error text.
373
+ */
374
+ private admitUrl;
375
+ /** Navigate the active tab's view to a URL, honoring HTTP(S)-only admission. */
376
+ navigate(session: BrowserSessionId, request: {
377
+ readonly url: string;
378
+ }, signal?: AbortSignal): Promise<void>;
379
+ /**
380
+ * Navigate one tab. A scrape worker passes its own tab so a batch never races
381
+ * a tool call for the session's active tab.
382
+ * @param show - bring the tab to the front; a background worker passes false.
383
+ * @param settleMs - post-ready paint delay; a DOM-only reader passes 0.
384
+ */
385
+ private navigateTab;
386
+ /** Navigate to the previous history entry when one exists. */
387
+ back(session: BrowserSessionId, signal?: AbortSignal): Promise<boolean>;
388
+ /** Navigate to the next history entry when one exists. */
389
+ forward(session: BrowserSessionId, signal?: AbortSignal): Promise<boolean>;
390
+ /** Reload the active page and restore the browser chrome afterwards. */
391
+ reload(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
392
+ /** Stop loading the active page. */
393
+ stopLoading(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
394
+ /** Execute JS in the active tab's page context. */
395
+ execute(session: BrowserSessionId, request: BrowserExecuteRequest, signal?: AbortSignal): Promise<BrowserExecuteResult>;
396
+ /** Evaluate in one tab's page context. */
397
+ private executeTab;
398
+ /** Produce an AI-friendly snapshot of the active tab. */
399
+ snapshot(session: BrowserSessionId, options?: {
400
+ query?: string;
401
+ limit?: number;
402
+ }, signal?: AbortSignal): Promise<BrowserSnapshotResult>;
403
+ /** Click one element that belongs to a retained exact page snapshot. */
404
+ clickRef(session: BrowserSessionId, request: BrowserRefRequest, signal?: AbortSignal): Promise<void>;
405
+ /** Scroll one element that belongs to a retained exact page snapshot into view. */
406
+ scrollIntoView(session: BrowserSessionId, request: BrowserScrollIntoViewRequest, signal?: AbortSignal): Promise<BrowserScrollResult>;
407
+ /** Check whether a human-verification challenge is blocking the active tab. */
408
+ detectChallenge(session: BrowserSessionId, signal?: AbortSignal): Promise<BrowserChallenge>;
409
+ /** Fetch page content in a requested format. */
410
+ content(session: BrowserSessionId, request: BrowserContentRequest, signal?: AbortSignal): Promise<BrowserContentResult>;
411
+ /** Click at viewport coordinates (CDP mousePressed + mouseReleased). */
412
+ click(session: BrowserSessionId, target: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
413
+ /** Double-click a target (physical input; clickCount 2). */
414
+ doubleClick(session: BrowserSessionId, target: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
415
+ /** Move the pointer over a target (no click). */
416
+ hover(session: BrowserSessionId, target: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
417
+ /**
418
+ * Press on one target, move to another, release.
419
+ *
420
+ * A hand does not teleport: the intermediate moves are what pointer-based
421
+ * sliders and sortable libraries listen for, so a press straight onto the
422
+ * destination would be ignored. Note this drives *pointer* drags only —
423
+ * HTML5 drag-and-drop needs dragstart/drop, which synthesized mouse moves do
424
+ * not produce; use the page's own controls, or a click-based reorder, there.
425
+ */
426
+ drag(session: BrowserSessionId, request: BrowserDragRequest, signal?: AbortSignal): Promise<BrowserDragResult>;
427
+ /** Scroll the active page by CSS-pixel deltas and return the final position. */
428
+ scroll(session: BrowserSessionId, request: BrowserScrollRequest, signal?: AbortSignal): Promise<BrowserScrollResult>;
429
+ /**
430
+ * Attach a local file to the first matching file input. Uses the CDP DOM
431
+ * domain (nodeId path), which — unlike a synthetic change event — makes the
432
+ * input's files list true (real file selection), so pages that read
433
+ * input.files or upload on change behave exactly like a real pick.
434
+ */
435
+ uploadFile(session: BrowserSessionId, request: BrowserUploadFileRequest, signal?: AbortSignal): Promise<BrowserUploadFileResult>;
436
+ /**
437
+ * Poll until an element matching the selector exists (and is visible).
438
+ * Bounds the total wait; a timeout surfaces as BROWSER_WAIT_TIMEOUT.
439
+ */
440
+ waitForElement(session: BrowserSessionId, request: BrowserWaitForRequest, signal?: AbortSignal): Promise<BrowserWaitForResult>;
441
+ /** Poll one tab until the selector matches. */
442
+ private waitForElementTab;
443
+ /** Type into the focused element. */
444
+ type(session: BrowserSessionId, request: {
445
+ readonly text: string;
446
+ }, signal?: AbortSignal): Promise<void>;
447
+ /**
448
+ * Drive the host's chrome frame view with a raw CDP command.
449
+ *
450
+ * The chrome can live in a view of its own so the page viewport can really
451
+ * shrink; that view is not a tab, so this is the only way to click the toolbar
452
+ * (the click tests use it, and so does anything that needs to exercise the
453
+ * chrome the way a person does).
454
+ */
455
+ chromeInput(method: string, params?: Record<string, unknown>): Promise<void>;
456
+ /**
457
+ * Read a value back out of the chrome frame's own document.
458
+ *
459
+ * The frame is not a tab, so a page-directed evaluate cannot see it; without
460
+ * this the toolbar's animations could only be inferred from the page's copy.
461
+ */
462
+ chromeEval(expression: string): Promise<unknown>;
463
+ /** Press a key into the page (keyDown + keyUp), as a physical-input path
464
+ * for shortcuts and keyboard-driven UI. */
465
+ pressKey(session: BrowserSessionId, request: BrowserPressKeyRequest, signal?: AbortSignal): Promise<void>;
466
+ /**
467
+ * Fill a form's fields in one batch. Runs one page-context script that
468
+ * resolves each field (selector, or name/label/placeholder among visible
469
+ * controls), sets its value with the native prototype setter (React/Vue
470
+ * controlled inputs included) plus input/change events, handles
471
+ * select/checkbox/radio/contenteditable, and optionally submits the form.
472
+ */
473
+ fillForm(session: BrowserSessionId, request: BrowserFillRequest, signal?: AbortSignal): Promise<BrowserFillResult>;
474
+ /**
475
+ * Download a URL to a local file, keeping the session's cookies/login.
476
+ * Requires the self-hosted host (which implements view-level download); the
477
+ * desktop shell's embedded views delegate downloads to the real browser UI.
478
+ */
479
+ download(session: BrowserSessionId, request: {
480
+ readonly url: string;
481
+ readonly savePath: string;
482
+ }, signal?: AbortSignal): Promise<{
483
+ readonly path: string;
484
+ }>;
485
+ /**
486
+ * Export the session's cookies (login state) as serializable objects.
487
+ * Self-hosted only; the desktop shell's embedded views use the real profile.
488
+ */
489
+ flushAuth(session: BrowserSessionId): Promise<readonly ExportedCookie[]>;
490
+ /**
491
+ * Remove cookies for one site scope. Challenge cookies that rotate their names
492
+ * (WAF challenges) otherwise pile up generation after generation, and two live
493
+ * generations in one request can be rejected by the site. Self-hosted only.
494
+ */
495
+ clearAuth(session: BrowserSessionId, request: BrowserClearAuthRequest): Promise<BrowserClearAuthResult>;
496
+ /**
497
+ * Import cookies from a JSON export on disk.
498
+ *
499
+ * The path is read-guarded exactly like browser_upload_file: a prompt-injected
500
+ * path must not turn this into a way to read a file the operator never allowed.
501
+ * A browser cookie export cannot be produced automatically — Chrome and Edge
502
+ * 127+ encrypt cookie values with App-Bound Encryption, so a copied profile
503
+ * yields nothing — which is why this takes a file the user exported.
504
+ */
505
+ importAuth(session: BrowserSessionId, path: string): Promise<{
506
+ restored: number;
507
+ failed: number;
508
+ }>;
509
+ /** Import cookies into the session (restore login state). Self-hosted only. */
510
+ restoreAuth(session: BrowserSessionId, cookies: readonly ExportedCookie[]): Promise<number>;
511
+ /**
512
+ * Start a background scrape batch.
513
+ *
514
+ * It runs detached on purpose: one tool call has a ~60s budget while a large
515
+ * batch takes minutes. Progress is polled with scrapeStatus, and each row is
516
+ * appended the moment it is produced, so a stopped or interrupted batch keeps
517
+ * everything it managed. `outPath` is write-guarded like any other browser
518
+ * write, and truncated up front so a re-run never mixes two batches.
519
+ */
520
+ startScrape(session: BrowserSessionId, request: BrowserScrapeRequest): Promise<BrowserScrapeStatus>;
521
+ /** Progress of one batch. */
522
+ scrapeStatus(id: string): Promise<BrowserScrapeStatus>;
523
+ /** Ask a running batch to stop; rows already written stay. */
524
+ stopScrape(id: string): Promise<BrowserScrapeStatus>;
525
+ /** Every batch this process knows about, oldest first. */
526
+ listScrapes(): Promise<readonly BrowserScrapeStatus[]>;
527
+ private scrapeJob;
528
+ /**
529
+ * Visit each URL once, appending one JSONL row per page.
530
+ *
531
+ * Workers pull from one shared index, so `concurrency` sets the throughput
532
+ * without changing the work. Rows therefore land in completion order; each row
533
+ * carries its URL's index as `seq` so the caller can restore the original.
534
+ */
535
+ private runScrape;
536
+ /** Drop a batch's private tabs (and their views) once the batch is over. */
537
+ private destroyScrapeTabs;
538
+ /** Capture the current page, optionally full-page. PNG only (CDP JPEG hangs on Electron 43). */
539
+ screenshot(session: BrowserSessionId, request?: {
540
+ readonly fullPage?: boolean;
541
+ readonly savePath?: string;
542
+ }, signal?: AbortSignal): Promise<{
543
+ readonly dataUrl: string;
544
+ readonly path?: string;
545
+ }>;
546
+ /** Build the data URL and optionally write the PNG to disk. */
547
+ private saveScreenshot;
548
+ /**
549
+ * Pick up (and forget) any JS dialog the host auto-accepted, so the
550
+ * operation trail shows the human/agent what the page asked. Best-effort.
551
+ */
552
+ private drainDialog;
553
+ /**
554
+ * Set how the host answers the next JS dialog, and report the resulting state.
555
+ *
556
+ * The policy lives in the host (it answers the CDP event there, where a
557
+ * round-trip would already be too late), so this is a push, not a pull.
558
+ */
559
+ setDialogPolicy(session: BrowserSessionId, policy: DialogPolicy): Promise<{
560
+ dialog: unknown;
561
+ policy: DialogPolicy;
562
+ }>;
563
+ /**
564
+ * Console messages the host captured for the active tab.
565
+ *
566
+ * Reading does NOT clear by default: debugging is usually a look-again loop, so
567
+ * `clear: true` is explicit. The host keeps a bounded ring, so old entries fall
568
+ * off on their own.
569
+ */
570
+ consoleMessages(session: BrowserSessionId, options?: {
571
+ limit?: number;
572
+ level?: string;
573
+ clear?: boolean;
574
+ }): Promise<{
575
+ messages: BrowserConsoleMessage[];
576
+ }>;
577
+ /** Network requests the host captured for the active tab (bounded ring). */
578
+ networkRequests(session: BrowserSessionId, options?: {
579
+ limit?: number;
580
+ failedOnly?: boolean;
581
+ urlContains?: string;
582
+ clear?: boolean;
583
+ }): Promise<{
584
+ requests: BrowserNetworkRequest[];
585
+ }>;
586
+ /**
587
+ * Apply device/viewport/media emulation to the active tab.
588
+ *
589
+ * Plain CDP through the existing command path, so no host change was needed.
590
+ * `clear` undoes all three: metrics, user agent, and emulated media.
591
+ */
592
+ emulate(session: BrowserSessionId, options?: EmulateOptions): Promise<{
593
+ applied: string[];
594
+ }>;
595
+ /**
596
+ * Drain any dialog that opened since the last input call, then report the last
597
+ * one and the current policy.
598
+ *
599
+ * `dialogState` alone is not enough for the tool: the host only hands a dialog
600
+ * over when something drains it, so an inspect that does not drain misses exactly
601
+ * the dialog the caller just triggered. (Found on the real machine.)
602
+ */
603
+ inspectDialog(session: BrowserSessionId): Promise<{
604
+ dialog: unknown;
605
+ policy: DialogPolicy;
606
+ }>;
607
+ /** The last JS dialog the host reported, plus the current policy. */
608
+ dialogState(session: BrowserSessionId): {
609
+ dialog: unknown;
610
+ policy: DialogPolicy;
611
+ };
612
+ /** Name this browser task (space). */
613
+ setSpace(session: BrowserSessionId, label: string): Promise<void>;
614
+ /** List every browser task (space) with its label. */
615
+ listSpaces(): Promise<readonly BrowserSpaceInfo[]>;
616
+ /** List browser tasks with live collaboration status. */
617
+ listTasks(): Promise<readonly BrowserTaskInfo[]>;
618
+ /** Read the collaboration state for one session's task. */
619
+ getTask(session: BrowserSessionId): Promise<BrowserTaskInfo>;
620
+ /** Apply one visible task state update and mirror it to a supporting host. */
621
+ updateTask(session: BrowserSessionId, update: BrowserTaskUpdate): Promise<BrowserTaskInfo>;
622
+ /** Hand control to the user or return it to Agent-driven actions. */
623
+ setHandoff(session: BrowserSessionId, state: BrowserHandoffState): Promise<BrowserTaskInfo>;
624
+ /** Append one operation to the session's history. */
625
+ private record;
626
+ /** Return the session's chronological operation log (newest last). */
627
+ history(session: BrowserSessionId): Promise<readonly BrowserHistoryEntry[]>;
628
+ /**
629
+ * Replay one recorded operation by sequence number. Navigate/click/type are
630
+ * re-issued against the current page; execute re-runs its script. The
631
+ * replayed step is appended to history as a new entry.
632
+ * @param session - the session id.
633
+ * @param seq - the recorded entry's sequence number to replay.
634
+ */
635
+ replay(session: BrowserSessionId, seq: number): Promise<void>;
636
+ /** Close the session and destroy all its views. Idempotent. */
637
+ close(session: BrowserSessionId): Promise<void>;
638
+ /** Recover the live session associated with a stable task key. */
639
+ private sessionForTask;
640
+ /** Look up a session or throw the unknown-session error. */
641
+ private session;
642
+ /** The active tab of a session. */
643
+ private activeTab;
644
+ /** Navigate through the browser history while preserving page readiness behavior. */
645
+ private navigateHistory;
646
+ /** Drop every reference that was captured before a document transition. */
647
+ private invalidateSnapshots;
648
+ /** Resolve one exact snapshot reference, rejecting any changed or missing target. */
649
+ private resolveSnapshotTarget;
650
+ /** Sync provider fallback cache from the host's authoritative workspace state. */
651
+ private rememberHostedTask;
652
+ /** Build the provider-side task summary when a host has no richer workspace. */
653
+ private localTaskInfo;
654
+ /** Create a tab with its short-lived snapshot reference store. */
655
+ private createTab;
656
+ /** Append a fresh tab and make it active. */
657
+ private newTab;
658
+ /** Notify the host of the active tab; it preserves the human-selected task view. */
659
+ /**
660
+ * Fire-and-forget host call: these run while the provider keeps going, so a rejection
661
+ * must never escape. Electron's host answers `unknown view` for a handle it no longer
662
+ * knows (a host restart leaves the provider holding stale ones), and an unhandled
663
+ * rejection surfaces as *some other* tool call failing - Ctrl+W did exactly that.
664
+ * The desired end state (view gone) holds either way, so swallowing is right here.
665
+ */
666
+ private ignoreHostFailure;
667
+ private showActive;
668
+ /** Read the current URL of a view through CDP. */
669
+ private currentUrl;
670
+ }
671
+ /**
672
+ * The in-page half of {@link resolvePointerTarget}: resolve the element, scroll
673
+ * it into view, and return its centre.
674
+ *
675
+ * Exported so it can be exercised. The matching rule — the innermost visible
676
+ * element whose label contains the text wins — is the part most likely to be
677
+ * wrong, and Node has no DOM to check it against.
678
+ */
679
+ export declare function pointerTargetScript(selector: string | undefined, text: string | undefined): string;
680
+ /**
681
+ * Minimal DOM shape {@link renderMarkdown} reads; a real DOM node fits it.
682
+ */
683
+ export interface MarkdownNode {
684
+ readonly nodeType?: number;
685
+ readonly tagName?: string | null;
686
+ readonly textContent?: string | null;
687
+ readonly childNodes?: ArrayLike<MarkdownNode> | null;
688
+ readonly href?: string | null;
689
+ readonly src?: string | null;
690
+ readonly alt?: string | null;
691
+ }
692
+ /**
693
+ * Best-effort markdown rendering of a DOM subtree, used by
694
+ * {@link ElectronBrowserProvider.content} for `format: 'markdown'`.
695
+ *
696
+ * Deliberately self-contained (no closures over module state, no imports):
697
+ * the provider embeds this function's source in the page with
698
+ * `Function.prototype.toString`, so the tests exercise the very code the page
699
+ * runs.
700
+ *
701
+ * Block containers (div/p/section/article/li/headings/...) recurse into their
702
+ * children and are joined with newlines, while adjacent inline runs are
703
+ * concatenated — text split by <b>/<span> stays one paragraph, and a container
704
+ * never emits its own `textContent` on top of its children (the old walker did,
705
+ * which flattened real pages — everything is wrapped in divs — to plain text).
706
+ * @param root - the subtree root (an element, or a text node).
707
+ * @returns the markdown text.
708
+ */
709
+ export declare function renderMarkdown(root: MarkdownNode): string;