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,668 @@
1
+ /**
2
+ * Vocabulary for the browser capability seam (`ctx.browser`). One seam owns
3
+ * provider registration, session lifecycle, cancellation, errors, and product
4
+ * configuration; providers differ only in what backs a session (an Electron
5
+ * `WebContentsView` in the desktop shell, a headless Chromium relay for
6
+ * remote deployments, and so on).
7
+ * @module dsh-browser-plus/browser/types
8
+ */
9
+ import { HarnessError } from '@deepseek-ai/dsh-llm';
10
+ /**
11
+ * Stable identity of one browser session, resolved by the Host through a
12
+ * Typert `LookupMap` to the live session object (the same mechanism
13
+ * `Agent → agentId` uses). The id is opaque to the wire; only the provider
14
+ * and its host binding can interpret it.
15
+ */
16
+ export type BrowserSessionId = string;
17
+ /**
18
+ * What one browser-capable backend is asked to do. Navigation is the minimal
19
+ * operation every provider shares; the seam deliberately keeps the request
20
+ * minimal so a provider swap never changes how the model asks.
21
+ */
22
+ export interface BrowserNavigateRequest {
23
+ /** The URL to open. Admission (HTTP(S) only, no credentials/private targets) is provider-owned. */
24
+ readonly url: string;
25
+ }
26
+ /**
27
+ * Click at viewport coordinates. Coordinates are viewport-relative pixels in
28
+ * the session's own coordinate space — the same space the human interacts
29
+ * with, so a real view and a relay agree without scaling.
30
+ */
31
+ /** Type text into the focused element. */
32
+ export interface BrowserTypeRequest {
33
+ /** The text to insert. */
34
+ readonly text: string;
35
+ }
36
+ /** Key press into the page, as one keyDown+keyUp pair. */
37
+ export interface BrowserPressKeyRequest {
38
+ /** The key to press: a single character ('a', '1'), an Enter/Tab/Escape,
39
+ * navigation key (ArrowUp/ArrowDown/…), Home/End/PageUp/PageDown,
40
+ * Backspace/Delete, or F1..F12. */
41
+ readonly key: string;
42
+ /** Modifier keys held during the press. */
43
+ readonly modifiers?: readonly ('alt' | 'ctrl' | 'meta' | 'shift')[];
44
+ }
45
+ /**
46
+ * How a pointer action addresses its target.
47
+ *
48
+ * Coordinates are used as given. A selector or text is resolved inside the page
49
+ * and scrolled into view first, so an agent can act on "the sign-in button"
50
+ * without spending a snapshot round-trip to learn its ref — the same way
51
+ * browser_fill already addresses its fields.
52
+ */
53
+ export interface BrowserPointerTarget {
54
+ /** Viewport-relative x in CSS pixels; required with y when neither selector nor text is given. */
55
+ readonly x?: number;
56
+ /** Viewport-relative y in CSS pixels. */
57
+ readonly y?: number;
58
+ /** CSS selector; the first visible match is used. */
59
+ readonly selector?: string;
60
+ /** Visible text, aria-label or value to match (case-insensitive); the innermost visible match wins. */
61
+ readonly text?: string;
62
+ /** Mouse button. Default left; right opens the page's own context menu. */
63
+ readonly button?: 'left' | 'right' | 'middle';
64
+ /** Modifiers held during the action: ctrl-click opens a link in a new tab, shift extends a selection. */
65
+ readonly modifiers?: readonly ('alt' | 'ctrl' | 'meta' | 'shift')[];
66
+ }
67
+ /**
68
+ * A press-drag-release gesture from one target to another. Both ends use the
69
+ * same addressing as a click, so a drag can go from "the slider thumb" to a
70
+ * coordinate, or between two selectors.
71
+ */
72
+ export interface BrowserDragRequest {
73
+ /** Where the gesture starts. */
74
+ readonly from: BrowserPointerTarget;
75
+ /** Where it ends. */
76
+ readonly to: BrowserPointerTarget;
77
+ /** Intermediate move events. More steps look more like a hand; default 12, max 60. */
78
+ readonly steps?: number;
79
+ }
80
+ /** Where a drag started and ended. */
81
+ export interface BrowserDragResult {
82
+ readonly from: BrowserPointerResult;
83
+ readonly to: BrowserPointerResult;
84
+ }
85
+ /** Where a pointer action actually landed. */
86
+ export interface BrowserPointerResult {
87
+ readonly x: number;
88
+ readonly y: number;
89
+ /** Short description of the element that was resolved; absent for a coordinate target. */
90
+ readonly target?: string;
91
+ }
92
+ /** Scroll the active page by a CSS-pixel delta. */
93
+ export interface BrowserScrollRequest {
94
+ /** Horizontal delta in CSS pixels. Default 0. */
95
+ readonly deltaX?: number;
96
+ /** Vertical delta in CSS pixels. Default one viewport height downward. */
97
+ readonly deltaY?: number;
98
+ }
99
+ /** Final page scroll position after a scroll operation. */
100
+ export interface BrowserScrollResult {
101
+ /** Horizontal scroll offset in CSS pixels. */
102
+ readonly x: number;
103
+ /** Vertical scroll offset in CSS pixels. */
104
+ readonly y: number;
105
+ /** Maximum horizontal scroll offset in CSS pixels. */
106
+ readonly maxX: number;
107
+ /** Maximum vertical scroll offset in CSS pixels. */
108
+ readonly maxY: number;
109
+ }
110
+ /** One element reference from a specific browser snapshot. */
111
+ export interface BrowserRefRequest {
112
+ /** Opaque id returned by browser_open or browser_snapshot. */
113
+ readonly snapshotId: string;
114
+ /** Element reference number from that snapshot. */
115
+ readonly ref: number;
116
+ }
117
+ /** Scroll one referenced element into the visible viewport. */
118
+ export interface BrowserScrollIntoViewRequest extends BrowserRefRequest {
119
+ /** Vertical alignment for the referenced element. Default center. */
120
+ readonly block?: 'start' | 'center' | 'end' | 'nearest';
121
+ }
122
+ /** Set a file input's value from a local path. */
123
+ export interface BrowserUploadFileRequest {
124
+ /** Absolute path of the file to attach. */
125
+ readonly filePath: string;
126
+ /** CSS selector of the file input; defaults to the first input[type="file"]. */
127
+ readonly selector?: string;
128
+ }
129
+ /** Outcome of a file upload. */
130
+ export interface BrowserUploadFileResult {
131
+ /** The path uploaded. */
132
+ readonly path: string;
133
+ }
134
+ /** Wait for an element matching a CSS selector to appear (optionally visible). */
135
+ export interface BrowserWaitForRequest {
136
+ /** CSS selector to wait for. */
137
+ readonly selector: string;
138
+ /** Total budget in ms. Default 15000. */
139
+ readonly timeoutMs?: number;
140
+ /** Wait for the element to be visible (at least 4x4 px and not visibility:hidden or display:none). Default true. */
141
+ readonly visible?: boolean;
142
+ }
143
+ /** Outcome of a successful wait. */
144
+ export interface BrowserWaitForResult {
145
+ readonly found: true;
146
+ readonly selector: string;
147
+ /** Matching element's tag name. */
148
+ readonly tag: string;
149
+ /** Matching element's visible text (first 200 chars). */
150
+ readonly text: string;
151
+ }
152
+ /** One field of a batch form fill. Match by selector, or by name/label/placeholder. */
153
+ export interface BrowserFillField {
154
+ /** CSS selector; when present, candidates are scoped to it. */
155
+ readonly selector?: string;
156
+ /** Match by the field's `name` attribute. */
157
+ readonly name?: string;
158
+ /** Match by associated `<label>` text or `aria-label`. */
159
+ readonly label?: string;
160
+ /** Match by `placeholder` text. */
161
+ readonly placeholder?: string;
162
+ /** Field kind; defaults to text. */
163
+ readonly kind?: 'text' | 'textarea' | 'checkbox' | 'radio' | 'select';
164
+ /** Value to set: string, number, or boolean (checkbox/radio). For a
165
+ * multi-select or radio group, the option value (or visible text). */
166
+ readonly value: string | number | boolean;
167
+ }
168
+ /** Batch form fill: set several fields, optionally submitting the form. */
169
+ export interface BrowserFillRequest {
170
+ /** The fields to fill, in order. */
171
+ readonly fields: readonly BrowserFillField[];
172
+ /** Submit the containing form after filling. Default false. */
173
+ readonly submit?: boolean;
174
+ /** Total evaluation budget for the whole batch in ms. Default 30000. */
175
+ readonly timeoutMs?: number;
176
+ }
177
+ /** One field's outcome in a batch fill. */
178
+ export interface BrowserFillFieldResult {
179
+ readonly ok: boolean;
180
+ /** The field description matched (selector/name/label/placeholder). */
181
+ readonly target: string;
182
+ /** How the value was applied: input | textarea | select | checkbox | radio | contenteditable. */
183
+ readonly method?: string;
184
+ /** Error text when the field could not be filled. */
185
+ readonly error?: string;
186
+ }
187
+ /** Outcome of a batch form fill. */
188
+ export interface BrowserFillResult {
189
+ /** Per-field outcomes, in request order. */
190
+ readonly fields: readonly BrowserFillFieldResult[];
191
+ /** Whether the containing form was submitted. */
192
+ readonly submitted: boolean;
193
+ }
194
+ /**
195
+ * A screenshot of the current page state. `dataUrl` is a base64 data URL the
196
+ * tool layer renders directly; the live view itself never crosses the wire
197
+ * (the human watches the real view in the desktop shape).
198
+ */
199
+ export interface BrowserScreenshotResult {
200
+ /** Base64 PNG data URL of the capture. */
201
+ readonly dataUrl: string;
202
+ /** File path the capture was also saved to, when the request asked for it. */
203
+ readonly path?: string;
204
+ }
205
+ /** Screenshot capture options. PNG only (CDP JPEG hangs on Electron 43). */
206
+ export interface BrowserScreenshotRequest {
207
+ /** Capture the full scrollable page instead of the viewport. Default false. */
208
+ readonly fullPage?: boolean;
209
+ /** Absolute file path to also save the PNG to (for vision-tool reads). */
210
+ readonly savePath?: string;
211
+ }
212
+ /** Download options: fetch a URL into a local file, keeping the session's cookies. */
213
+ export interface BrowserDownloadRequest {
214
+ /** The URL to download. */
215
+ readonly url: string;
216
+ /** Absolute path of the file to write. */
217
+ readonly savePath: string;
218
+ }
219
+ /** A serializable cookie, exported from or imported into the browser session. */
220
+ export interface ExportedCookie {
221
+ /** Cookie URL (scheme + domain + path), as accepted by cookies.set. */
222
+ readonly url: string;
223
+ readonly name: string;
224
+ readonly value: string;
225
+ readonly domain?: string;
226
+ readonly path?: string;
227
+ readonly secure?: boolean;
228
+ readonly httpOnly?: boolean;
229
+ readonly expirationDate?: number;
230
+ /** SameSite policy in Chromium's spelling; both spellings are accepted on import. */
231
+ readonly sameSite?: 'no_restriction' | 'lax' | 'strict' | 'unspecified';
232
+ }
233
+ /** Which cookies a clear request removes: a domain scope, one name, or an explicit wipe. */
234
+ export interface BrowserClearAuthRequest {
235
+ /** Domain scope; matches that domain and every subdomain. */
236
+ readonly domain?: string;
237
+ /** Exact cookie name to remove within the scope. */
238
+ readonly name?: string;
239
+ /** Remove every addressable cookie; requires this explicit opt-in. */
240
+ readonly all?: boolean;
241
+ }
242
+ /** Outcome of a cookie clear: how many went and which names. */
243
+ export interface BrowserClearAuthResult {
244
+ readonly removed: number;
245
+ readonly names: readonly string[];
246
+ }
247
+ /**
248
+ * Execute arbitrary JavaScript in the session's page context. This is the
249
+ * primary interaction path — DOM-referenced, not screen-coordinate-based:
250
+ * focus/input/click go through the page's own JS (native setters for
251
+ * framework-controlled inputs), matching how a human-driven agent operates.
252
+ */
253
+ export interface BrowserExecuteRequest {
254
+ /** The JavaScript expression to evaluate in the page context. */
255
+ readonly script: string;
256
+ /** Optional arguments injected into the script scope as `arguments[0..n]`. */
257
+ readonly args?: readonly unknown[];
258
+ /** Evaluation budget in ms; a wedged page rejects with a timeout. Default 30000. */
259
+ readonly timeoutMs?: number;
260
+ }
261
+ /**
262
+ * Result of an execute. Mirrors CDP `Runtime.evaluate` semantics: either a
263
+ * value (by value, not by reference) or the exception thrown.
264
+ */
265
+ export type BrowserExecuteResult = {
266
+ readonly ok: true;
267
+ readonly value: unknown;
268
+ } | {
269
+ readonly ok: false;
270
+ readonly exception: string;
271
+ };
272
+ /**
273
+ * A background scrape batch. The point is that results never travel back
274
+ * through the model: each page appends one JSON line to `outPath`, so a batch
275
+ * of thousands costs the same number of tokens as a batch of one.
276
+ */
277
+ export interface BrowserScrapeRequest {
278
+ /** URLs to visit, in order. */
279
+ readonly urls: readonly string[];
280
+ /**
281
+ * Expression evaluated on each page once it is ready. Its JSON value becomes
282
+ * the record's `data`; an async IIFE is fine, the evaluation awaits promises.
283
+ */
284
+ readonly script: string;
285
+ /** JSONL destination; must resolve inside the browser-electron write roots. */
286
+ readonly outPath: string;
287
+ /** Optional CSS selector awaited on each page before the script runs. */
288
+ readonly waitFor?: string;
289
+ /** Per-URL budget in ms for the wait and the extraction. Default 30000. */
290
+ readonly timeoutMs?: number;
291
+ /**
292
+ * How many pages to load at once. Default 1, which writes rows in URL order.
293
+ * Each worker owns a tab of its own, so a higher value costs that many tabs;
294
+ * every row carries its URL's index as `seq` so the caller can restore order.
295
+ */
296
+ readonly concurrency?: number;
297
+ }
298
+ /** Progress of one scrape batch. The rows themselves live in the file. */
299
+ export interface BrowserScrapeStatus {
300
+ readonly id: string;
301
+ /** `stopped` means it was asked to stop; rows already written are kept. */
302
+ readonly state: 'running' | 'done' | 'stopped';
303
+ readonly total: number;
304
+ readonly done: number;
305
+ /** Pages whose navigation or extraction failed; they still get a row. */
306
+ readonly failed: number;
307
+ readonly path: string;
308
+ /** Set when the whole batch aborted for a reason other than a page error. */
309
+ readonly error?: string;
310
+ }
311
+ /**
312
+ * One interactive element in a snapshot, with a stable reference number the
313
+ * tool layer (and the model) can cite to target the element.
314
+ */
315
+ export interface BrowserSnapshotElement {
316
+ /** 1-based reference number, stable for the lifetime of one snapshot. */
317
+ readonly ref: number;
318
+ /** Element kind: 'input' | 'button' | 'link' | 'textarea' | 'select' | 'checkbox' | 'other'. */
319
+ readonly kind: string;
320
+ /** Text content or placeholder/label, truncated for the snapshot. */
321
+ readonly label: string;
322
+ /** Best-effort CSS selector; may be empty when none is derivable. */
323
+ readonly selector: string;
324
+ /** Best-effort locator for re-targeting (id/name/aria-label/text). */
325
+ readonly loc: string;
326
+ /** Viewport-relative center, for coordinate fallbacks. */
327
+ readonly x: number;
328
+ readonly y: number;
329
+ }
330
+ /**
331
+ * AI-friendly page snapshot: a compact, numbered inventory of interactive
332
+ * elements plus page facts. Not raw HTML — the model dialogues with this.
333
+ */
334
+ export interface BrowserSnapshotResult {
335
+ /** Opaque id that binds the element references to this exact page snapshot. */
336
+ readonly snapshotId: string;
337
+ /** Final page URL. */
338
+ readonly url: string;
339
+ /** Page title, if any. */
340
+ readonly title?: string;
341
+ /** Numbered interactive elements. */
342
+ readonly elements: readonly BrowserSnapshotElement[];
343
+ /** True when the snapshot was truncated (element cap reached). */
344
+ readonly truncated: boolean;
345
+ /** Human-verification challenge blocking the page, when one is detected. */
346
+ readonly challenge?: BrowserChallenge;
347
+ /** True when a human interacted with the page within the last minute. */
348
+ readonly userControlling?: boolean;
349
+ }
350
+ /**
351
+ * A detected human-verification (bot-detection / CAPTCHA) challenge blocking
352
+ * the page. Detection is best-effort and marker-based; a clean result is not a
353
+ * guarantee that no challenge exists.
354
+ */
355
+ export interface BrowserChallenge {
356
+ /** Whether a challenge currently blocks the page. */
357
+ readonly blocked: boolean;
358
+ /** Machine-readable kind: cloudflare | hcaptcha | recaptcha | turnstile | generic. */
359
+ readonly kind?: string;
360
+ /** Short human-readable description, e.g. 'Cloudflare "Just a moment" interstitial'. */
361
+ readonly reason?: string;
362
+ }
363
+ /**
364
+ * Content fetch formats. `html` is the raw DOM; `markdown` is a structured
365
+ * reading view; `txt` is plain text (smallest); `json` is structured data.
366
+ */
367
+ export type BrowserContentFormat = 'html' | 'markdown' | 'txt' | 'json';
368
+ /** Content fetch request: format, optional selector scoping, and caps. */
369
+ export interface BrowserContentRequest {
370
+ /** Desired format. */
371
+ readonly format: BrowserContentFormat;
372
+ /** Optional CSS selector limiting the fetch to one region (e.g. `#main`). */
373
+ readonly selector?: string;
374
+ /** Maximum characters of the returned content. */
375
+ readonly maxChars?: number;
376
+ /** Evaluation timeout in ms; the fetch aborts after this. Default 30000. */
377
+ readonly timeoutMs?: number;
378
+ }
379
+ /** A fetched page content in the requested format. */
380
+ export interface BrowserContentResult {
381
+ /** The content in the requested format, capped at `maxChars`. */
382
+ readonly content: string;
383
+ /** True when the content was truncated. */
384
+ readonly truncated: boolean;
385
+ }
386
+ /**
387
+ * A tab inside a browser session. Tabs share the session's profile/cookies
388
+ * and are switchable without losing state.
389
+ */
390
+ export interface BrowserTab {
391
+ /** Stable tab id within the session. */
392
+ readonly id: string;
393
+ /** Current URL, or empty for a blank tab. */
394
+ readonly url: string;
395
+ /** Page title, if any. */
396
+ readonly title?: string;
397
+ /** Whether this tab is the active one. */
398
+ readonly active: boolean;
399
+ }
400
+ /** Request to open a URL: into the active tab or a new tab. */
401
+ export interface BrowserOpenRequest {
402
+ /** The URL to open. */
403
+ readonly url: string;
404
+ /** Open in a new tab (parallel). Default false (reuse the active tab). */
405
+ readonly newTab?: boolean;
406
+ }
407
+ /** Options for opening or recovering a browser task session. */
408
+ export interface BrowserOpenOptions {
409
+ /** Stable browser-task key; keyed providers may recover the existing session for this task. */
410
+ readonly key?: string;
411
+ /** Optional display name shown in the task manager and active window title. */
412
+ readonly label?: string;
413
+ }
414
+ /** One browser task represented in the shared visible window. */
415
+ export interface BrowserSpaceInfo {
416
+ /** Stable browser-task key. */
417
+ readonly key: string;
418
+ /** Task display label, or '' for unlabeled. */
419
+ readonly label: string;
420
+ }
421
+ /** Visible activity state for a browser task. */
422
+ export type BrowserTaskStatus = 'idle' | 'running' | 'waiting-user' | 'failed';
423
+ /** Which collaborator currently owns page-changing actions. */
424
+ export type BrowserControlOwner = 'agent' | 'human';
425
+ /** Intentional Agent-to-human handoff mode. */
426
+ export type BrowserHandoffState = 'waiting-user' | 'agent';
427
+ /** Compact task information shared by browser tools and the visible workspace. */
428
+ export interface BrowserTaskInfo extends BrowserSpaceInfo {
429
+ /** Whether this task is currently visible in the shared browser window. */
430
+ readonly active: boolean;
431
+ /** Number of tabs in the task's session. */
432
+ readonly tabs: number;
433
+ /** Current visible activity state. */
434
+ readonly status: BrowserTaskStatus;
435
+ /** Who currently owns page-changing actions. */
436
+ readonly control: BrowserControlOwner;
437
+ /** Latest completed or in-progress operation, when known. */
438
+ readonly latestAction?: string;
439
+ /** Epoch milliseconds of the last task-state change. */
440
+ readonly updatedAt: number;
441
+ /** Short display-safe failure detail, when the task failed. */
442
+ readonly error?: string;
443
+ }
444
+ /** Partial task-state update produced by the tool and provider layers. */
445
+ export interface BrowserTaskUpdate {
446
+ readonly status?: BrowserTaskStatus;
447
+ readonly control?: BrowserControlOwner;
448
+ readonly latestAction?: string;
449
+ readonly error?: string;
450
+ }
451
+ /**
452
+ * A browser-capable backend. Registered with `ctx.browser.registerBrowserProvider`.
453
+ * `id` is a stable string, unique across the seam.
454
+ */
455
+ export interface BrowserProvider {
456
+ readonly id: string;
457
+ /** Cheap local usability check; must not make network calls. */
458
+ available(): boolean;
459
+ /**
460
+ * Open or recover a session. The provider mints the session id and prepares its
461
+ * backing surface (a view, a headless page, and so on); keyed providers may return
462
+ * the existing session for the same stable task key. Sessions are isolated from
463
+ * each other; a session must not be visible to any other session's caller.
464
+ */
465
+ open(options?: BrowserOpenOptions): Promise<BrowserSessionId>;
466
+ /** Open a URL, optionally in a new tab. Honor `signal` for cancellation. */
467
+ openUrl(session: BrowserSessionId, request: BrowserOpenRequest, signal?: AbortSignal): Promise<void>;
468
+ /** List the session's tabs. */
469
+ listTabs(session: BrowserSessionId): Promise<readonly BrowserTab[]>;
470
+ /** Switch to a tab by id. Unknown id -> `BROWSER_TAB_UNKNOWN`. */
471
+ switchTab(session: BrowserSessionId, tabId: string): Promise<void>;
472
+ /** Close one tab. Closing the active tab activates the next. */
473
+ closeTab(session: BrowserSessionId, tabId: string): Promise<boolean>;
474
+ /** Close every tab and reset the session's state. */
475
+ reset(session: BrowserSessionId): Promise<void>;
476
+ /** Navigate the active tab. Honor `signal` for cancellation. */
477
+ navigate(session: BrowserSessionId, request: BrowserNavigateRequest, signal?: AbortSignal): Promise<void>;
478
+ /** Navigate to the previous history entry when one exists. */
479
+ back(session: BrowserSessionId, signal?: AbortSignal): Promise<boolean>;
480
+ /** Navigate to the next history entry when one exists. */
481
+ forward(session: BrowserSessionId, signal?: AbortSignal): Promise<boolean>;
482
+ /** Reload the active page. */
483
+ reload(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
484
+ /** Stop loading the active page. */
485
+ stopLoading(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
486
+ /** Execute JS in the active tab's page context. */
487
+ execute(session: BrowserSessionId, request: BrowserExecuteRequest, signal?: AbortSignal): Promise<BrowserExecuteResult>;
488
+ /** Produce an AI-friendly snapshot of the active tab. */
489
+ snapshot(session: BrowserSessionId, options?: {
490
+ query?: string;
491
+ limit?: number;
492
+ }, signal?: AbortSignal): Promise<BrowserSnapshotResult>;
493
+ /** Apply device/viewport/media emulation to the session's active tab. */
494
+ emulate(session: BrowserSessionId, options?: {
495
+ width?: number;
496
+ height?: number;
497
+ deviceScaleFactor?: number;
498
+ mobile?: boolean;
499
+ userAgent?: string;
500
+ colorScheme?: 'light' | 'dark' | 'no-preference';
501
+ clear?: boolean;
502
+ }): Promise<{
503
+ applied: string[];
504
+ }>;
505
+ /** Console messages the host captured for the session's active tab. */
506
+ consoleMessages(session: BrowserSessionId, options?: {
507
+ limit?: number;
508
+ level?: string;
509
+ clear?: boolean;
510
+ }): Promise<{
511
+ messages: Array<{
512
+ level: string;
513
+ text: string;
514
+ at: string;
515
+ }>;
516
+ }>;
517
+ /** Network requests the host captured for the session's active tab. */
518
+ networkRequests(session: BrowserSessionId, options?: {
519
+ limit?: number;
520
+ failedOnly?: boolean;
521
+ urlContains?: string;
522
+ clear?: boolean;
523
+ }): Promise<{
524
+ requests: Array<{
525
+ method: string;
526
+ url: string;
527
+ status?: number;
528
+ mime?: string;
529
+ kind?: string;
530
+ failed?: string;
531
+ ms?: number;
532
+ at: string;
533
+ }>;
534
+ }>;
535
+ /** Set how the host answers the next JS dialog (alert/confirm/prompt). */
536
+ setDialogPolicy(session: BrowserSessionId, policy: {
537
+ behavior: 'accept' | 'dismiss';
538
+ promptText?: string;
539
+ }): Promise<{
540
+ dialog: unknown;
541
+ policy: {
542
+ behavior: 'accept' | 'dismiss';
543
+ promptText?: string;
544
+ };
545
+ }>;
546
+ /** Drain any pending dialog, then report the last one and the current policy. */
547
+ inspectDialog(session: BrowserSessionId): Promise<{
548
+ dialog: unknown;
549
+ policy: {
550
+ behavior: 'accept' | 'dismiss';
551
+ promptText?: string;
552
+ };
553
+ }>;
554
+ /** The last JS dialog the host reported, plus the current policy. */
555
+ dialogState(session: BrowserSessionId): {
556
+ dialog: unknown;
557
+ policy: {
558
+ behavior: 'accept' | 'dismiss';
559
+ promptText?: string;
560
+ };
561
+ };
562
+ /** Click one element referenced by an exact snapshot. */
563
+ clickRef(session: BrowserSessionId, request: BrowserRefRequest, signal?: AbortSignal): Promise<void>;
564
+ /** Scroll one element referenced by an exact snapshot into view. */
565
+ scrollIntoView(session: BrowserSessionId, request: BrowserScrollIntoViewRequest, signal?: AbortSignal): Promise<BrowserScrollResult>;
566
+ /** Check whether a human-verification challenge is blocking the active tab. */
567
+ detectChallenge(session: BrowserSessionId, signal?: AbortSignal): Promise<BrowserChallenge>;
568
+ /** Fetch page content in a requested format. */
569
+ content(session: BrowserSessionId, request: BrowserContentRequest, signal?: AbortSignal): Promise<BrowserContentResult>;
570
+ /** Click at viewport coordinates (fallback path; execute is preferred). Honor `signal` for cancellation. */
571
+ click(session: BrowserSessionId, request: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
572
+ /** Type into the focused element (fallback path). Honor `signal` for cancellation. */
573
+ type(session: BrowserSessionId, request: BrowserTypeRequest, signal?: AbortSignal): Promise<void>;
574
+ /** Press a key (keyDown + keyUp) into the active tab. Honor `signal` for cancellation. */
575
+ pressKey(session: BrowserSessionId, request: BrowserPressKeyRequest, signal?: AbortSignal): Promise<void>;
576
+ /** Double-click (one press/release pair, clickCount 2). Honor `signal` for cancellation. */
577
+ doubleClick(session: BrowserSessionId, request: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
578
+ /** Move the pointer without clicking. Honor `signal` for cancellation. */
579
+ hover(session: BrowserSessionId, request: BrowserPointerTarget, signal?: AbortSignal): Promise<BrowserPointerResult>;
580
+ /**
581
+ * Press on one target, move to another, release. Drives sliders, sortable
582
+ * lists and canvas editors. Honor `signal` for cancellation.
583
+ */
584
+ drag(session: BrowserSessionId, request: BrowserDragRequest, signal?: AbortSignal): Promise<BrowserDragResult>;
585
+ /** Scroll the active page by CSS-pixel deltas. */
586
+ scroll(session: BrowserSessionId, request: BrowserScrollRequest, signal?: AbortSignal): Promise<BrowserScrollResult>;
587
+ /** Attach a local file to a file input. Honor `signal` for cancellation. */
588
+ uploadFile(session: BrowserSessionId, request: BrowserUploadFileRequest, signal?: AbortSignal): Promise<BrowserUploadFileResult>;
589
+ /** Wait until an element matching the selector exists (and optionally is visible). Honor `signal` for cancellation. */
590
+ waitForElement(session: BrowserSessionId, request: BrowserWaitForRequest, signal?: AbortSignal): Promise<BrowserWaitForResult>;
591
+ /** Fill a form's fields in one batch. Honor `signal` for cancellation. */
592
+ fillForm(session: BrowserSessionId, request: BrowserFillRequest, signal?: AbortSignal): Promise<BrowserFillResult>;
593
+ /** Capture the current page. Honor `signal` for cancellation. */
594
+ screenshot(session: BrowserSessionId, request?: BrowserScreenshotRequest, signal?: AbortSignal): Promise<BrowserScreenshotResult>;
595
+ /** Download a URL to a local file. Honor `signal` for cancellation. */
596
+ download(session: BrowserSessionId, request: BrowserDownloadRequest, signal?: AbortSignal): Promise<{
597
+ readonly path: string;
598
+ }>;
599
+ /** Export the session's cookies (login state). */
600
+ flushAuth(session: BrowserSessionId): Promise<readonly ExportedCookie[]>;
601
+ /** Import cookies into the session (restore login state); returns the number of cookies restored. */
602
+ restoreAuth(session: BrowserSessionId, cookies: readonly ExportedCookie[]): Promise<number>;
603
+ /**
604
+ * Import cookies from a JSON export on disk (read-guarded by the provider's
605
+ * read roots). `failed` counts entries the file carried that could not be set.
606
+ */
607
+ importAuth(session: BrowserSessionId, path: string): Promise<{
608
+ readonly restored: number;
609
+ readonly failed: number;
610
+ }>;
611
+ /** Start a background scrape batch; it writes its rows itself. */
612
+ startScrape(session: BrowserSessionId, request: BrowserScrapeRequest): Promise<BrowserScrapeStatus>;
613
+ /** Progress of one batch. */
614
+ scrapeStatus(id: string): Promise<BrowserScrapeStatus>;
615
+ /** Ask a running batch to stop; rows already written are kept. */
616
+ stopScrape(id: string): Promise<BrowserScrapeStatus>;
617
+ /** Every batch this process knows about, oldest first. */
618
+ listScrapes(): Promise<readonly BrowserScrapeStatus[]>;
619
+ /** Remove cookies for a site scope; returns how many went and their names. */
620
+ clearAuth(session: BrowserSessionId, request: BrowserClearAuthRequest): Promise<BrowserClearAuthResult>;
621
+ /** Return the session's chronological operation log. */
622
+ history(session: BrowserSessionId): Promise<readonly BrowserHistoryEntry[]>;
623
+ /** Replay one recorded operation by sequence number. */
624
+ replay(session: BrowserSessionId, seq: number): Promise<void>;
625
+ /** Set the browser task label for a session; selected task controls the shared title. */
626
+ setSpace(session: BrowserSessionId, label: string): Promise<void>;
627
+ /** List browser tasks (legacy spaces) with their labels. */
628
+ listSpaces(): Promise<readonly BrowserSpaceInfo[]>;
629
+ /** List browser tasks with live collaboration status. */
630
+ listTasks(): Promise<readonly BrowserTaskInfo[]>;
631
+ /** Read the collaboration state of this session's task. */
632
+ getTask(session: BrowserSessionId): Promise<BrowserTaskInfo>;
633
+ /** Apply a visible activity/control update to this session's task. */
634
+ updateTask(session: BrowserSessionId, update: BrowserTaskUpdate): Promise<BrowserTaskInfo>;
635
+ /** Mark this task as waiting for the user or returned to Agent control. */
636
+ setHandoff(session: BrowserSessionId, state: BrowserHandoffState): Promise<BrowserTaskInfo>;
637
+ /** Close the session and destroy its backing surface. Idempotent. */
638
+ close(session: BrowserSessionId): Promise<void>;
639
+ }
640
+ /** One recorded browser operation, in chronological order (seq 1, 2, 3…). */
641
+ export interface BrowserHistoryEntry {
642
+ /** Monotonic sequence number within the session. */
643
+ readonly seq: number;
644
+ /** The operation name. Replayable: navigate | execute | click | type |
645
+ * pressKey. Also recorded but not replayable: fill | download |
646
+ * flushAuth | restoreAuth | clearAuth | setSpace | doubleClick | hover |
647
+ * uploadFile | waitForElement. */
648
+ readonly action: string;
649
+ /** The operation's arguments. */
650
+ readonly params: Record<string, unknown>;
651
+ /** Whether the operation succeeded. */
652
+ readonly ok: boolean;
653
+ /** Truncated result text, when the operation returned one. */
654
+ readonly result?: string;
655
+ /** Error text, when the operation failed. */
656
+ readonly error?: string;
657
+ /** Epoch millis of the operation. */
658
+ readonly at: number;
659
+ }
660
+ /**
661
+ * Typed browser error with a machine-routable, open-string `code` and chained
662
+ * `cause`. Consumers must tolerate provider-specific codes. Shared codes cover
663
+ * unavailable, missing, unusable, ambiguous, or duplicate providers, unknown
664
+ * or closed sessions, cancellation, and provider failure; a provider adds its
665
+ * own (e.g. `BROWSER_CDP_ATTACH_FAILED`).
666
+ */
667
+ export declare class BrowserError extends HarnessError {
668
+ }