dsh-browser-plus 0.0.0-stage → 0.5.1

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