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