@scalebrowser/sdk 0.2.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.
@@ -0,0 +1,1613 @@
1
+ import WebSocket from 'ws';
2
+
3
+ /**
4
+ * Error model — stable numeric API error codes (SPEC §5,
5
+ * `crates/core/src/error.rs`) and the typed exceptions the SDK throws.
6
+ */
7
+ /** Stable numeric API error codes (4001–4010). */
8
+ declare const ErrorCode: {
9
+ readonly NotFound: 4001;
10
+ readonly ProxyGeo: 4002;
11
+ readonly Capacity: 4003;
12
+ readonly EngineMissing: 4004;
13
+ readonly Preflight: 4005;
14
+ readonly AlreadyRunning: 4006;
15
+ /** Retired: the removed Methode-D (VM) tier produced this. Reserved (never reused)
16
+ * to match the server's append-only code contract; never returned now. */
17
+ readonly VmProvisionFailed: 4007;
18
+ /** The subscription's abo-wide concurrent-browser allowance is used up (DEC-012).
19
+ * Distinct from Capacity (4003 = this machine is full, wait): the caller's move
20
+ * is to stop a running browser anywhere or upgrade the plan. */
21
+ readonly ConcurrencyLimit: 4008;
22
+ readonly Unauthorized: 4010;
23
+ };
24
+ type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode];
25
+ /** Error body shape: `{ code, message }` (docs/features/api.md). */
26
+ interface ApiErrorBody {
27
+ code?: number;
28
+ message?: string;
29
+ }
30
+ /** Base class for every error this SDK throws — lets callers `catch` one type. */
31
+ declare class ScalebrowserError extends Error {
32
+ constructor(message: string);
33
+ }
34
+ /**
35
+ * A typed transport/contract error. Carries the HTTP `status` and the daemon's
36
+ * numeric `code` so callers (and the auth layer) can react — e.g. 401 / code
37
+ * 4010 → re-authenticate.
38
+ */
39
+ declare class ApiError extends ScalebrowserError {
40
+ readonly status: number;
41
+ readonly code?: number;
42
+ readonly body?: ApiErrorBody;
43
+ constructor(status: number, body?: ApiErrorBody, fallbackMessage?: string);
44
+ /** True for auth failures that must trigger re-authentication. */
45
+ get isAuthError(): boolean;
46
+ /** Short label for the error code, if any. */
47
+ get codeLabel(): string | undefined;
48
+ }
49
+ /** A network-level failure (daemon unreachable, socket error, fetch rejected). */
50
+ declare class NetworkError extends ScalebrowserError {
51
+ readonly cause?: unknown;
52
+ constructor(message: string, cause?: unknown);
53
+ }
54
+ /** A CDP command that returned an `error` frame, or a closed CDP connection. */
55
+ declare class CdpError extends ScalebrowserError {
56
+ readonly code: number;
57
+ readonly data?: unknown;
58
+ constructor(code: number, message: string, data?: unknown);
59
+ }
60
+ /** Narrow an unknown thrown value to a user-facing message. */
61
+ declare function errorMessage(err: unknown): string;
62
+
63
+ interface Screen {
64
+ width: number;
65
+ height: number;
66
+ avail_width: number;
67
+ avail_height: number;
68
+ color_depth: number;
69
+ pixel_depth: number;
70
+ device_pixel_ratio: number;
71
+ orientation: string;
72
+ }
73
+ interface Locale {
74
+ language: string;
75
+ languages: string[];
76
+ accept_language: string;
77
+ }
78
+ interface WebGl {
79
+ vendor: string;
80
+ renderer: string;
81
+ unmasked_vendor: string;
82
+ unmasked_renderer: string;
83
+ version: string;
84
+ shading_language_version: string;
85
+ }
86
+ interface WebGpu {
87
+ present: boolean;
88
+ adapter: string;
89
+ vendor: string;
90
+ architecture: string;
91
+ device: string;
92
+ description: string;
93
+ }
94
+ interface AudioProps {
95
+ sample_rate: number;
96
+ base_latency: number;
97
+ max_channel_count: number;
98
+ output_channel_count: number;
99
+ }
100
+ interface SpeechVoice {
101
+ name: string;
102
+ lang: string;
103
+ local_service: boolean;
104
+ default: boolean;
105
+ }
106
+ interface GpuPersona {
107
+ backend: string;
108
+ vendor: string;
109
+ model: string;
110
+ }
111
+ interface Brand {
112
+ brand: string;
113
+ version: string;
114
+ }
115
+ interface ClientHints {
116
+ brands: Brand[];
117
+ full_version_list: Brand[];
118
+ platform: string;
119
+ platform_version: string;
120
+ architecture: string;
121
+ bitness: string;
122
+ model: string;
123
+ mobile: boolean;
124
+ ua_full_version: string;
125
+ wow64: boolean;
126
+ }
127
+ interface PermissionDefaults {
128
+ notifications: string;
129
+ geolocation: string;
130
+ camera: string;
131
+ microphone: string;
132
+ }
133
+ interface MediaDevice {
134
+ kind: string;
135
+ label: string;
136
+ device_id: string;
137
+ group_id: string;
138
+ }
139
+ interface PersonaMisc {
140
+ webdriver: boolean;
141
+ get_installed_related_apps: boolean;
142
+ max_touch_points: number;
143
+ pdf_viewer_enabled: boolean;
144
+ headless_guards: boolean;
145
+ }
146
+ interface Persona {
147
+ os: OsFamily;
148
+ user_agent: string;
149
+ platform: string;
150
+ browser_version: string;
151
+ screen: Screen;
152
+ hardware_concurrency: number;
153
+ device_memory: number;
154
+ timezone: string;
155
+ locale: Locale;
156
+ webgl: WebGl;
157
+ webgpu: WebGpu;
158
+ fonts: string[];
159
+ noise_seed: number;
160
+ audio: AudioProps;
161
+ speech_voices: SpeechVoice[];
162
+ gpu_persona: GpuPersona;
163
+ client_hints: ClientHints;
164
+ permissions: PermissionDefaults;
165
+ media_devices: MediaDevice[];
166
+ misc: PersonaMisc;
167
+ }
168
+
169
+ /**
170
+ * Wire types — a 1:1 mirror of the daemon's serialized domain model
171
+ * (`crates/core/src/model.rs`, `persona.rs`, `events.rs`) and the endpoint
172
+ * contract in `docs/features/api.md`. Mirrored against `web-ui/src/api/types/`
173
+ * (a directory of split files behind the `web-ui/src/api/types.ts` barrel), and
174
+ * that mirror is ENFORCED: `web-ui/src/test/sdk-type-mirror.test.ts` compares
175
+ * every shared exported declaration and goes red on drift. The Python SDK
176
+ * (`sdks/python/src/scalebrowser/models*.py`) carries the same shapes by hand,
177
+ * deliberately without a gate — a TS↔pydantic comparator is its own tool.
178
+ *
179
+ * All Rust enums serialize `snake_case` (serde `rename_all`). Timestamps are
180
+ * unix seconds (`number`, Rust `i64`). The truth for any disagreement is the
181
+ * UNION of the Rust route shapes, never one serde struct (create/delete answer
182
+ * the bare `Profile`, list/get the flattened `ProfileView`).
183
+ */
184
+ /** `RuntimeState` (AC-PR-004). */
185
+ type RuntimeState = 'stopped' | 'starting' | 'running' | 'crashed';
186
+ declare const RUNTIME_STATES: readonly RuntimeState[];
187
+ /** `HostMode` (PRD §B, DEC-004). Default `linux_engine_spoof`. */
188
+ type HostMode = 'linux_engine_spoof' | 'local_native_windows';
189
+ declare const HOST_MODES: readonly HostMode[];
190
+ /** `GeoMode` (AC-PX-002). Default `follow_exit`. */
191
+ type GeoMode = 'strict_expected' | 'follow_exit' | 'off';
192
+ declare const GEO_MODES: readonly GeoMode[];
193
+ /** `ProxyKind` (AC-PX-001). */
194
+ type ProxyKind = 'http' | 'socks5' | 'gateway';
195
+ declare const PROXY_KINDS: readonly ProxyKind[];
196
+ /** `Rotation` (AC-PX-002). */
197
+ type Rotation = 'sticky' | 'rotating';
198
+ declare const ROTATIONS: readonly Rotation[];
199
+ /** `SessionKind` (AC-ST-002). */
200
+ type SessionKind = 'cookies' | 'local_storage' | 'indexed_db' | 'service_worker';
201
+ type OsFamily = 'windows' | 'macos' | 'linux';
202
+ /** How protected a profile is right now, decided by the daemon WITHOUT starting a
203
+ * browser — it runs the same gates the launch runs. `blocked` means the launch
204
+ * will refuse this profile; `warn` means it starts but something measurably
205
+ * weakens it (most often a missing proxy). */
206
+ type ProtectionLevel = 'ok' | 'warn' | 'blocked';
207
+ interface ProtectionStatus {
208
+ level: ProtectionLevel;
209
+ /** Plain-language findings, worst first. Empty exactly when `level` is `ok`. */
210
+ reasons: string[];
211
+ }
212
+ /** A browser identity profile (`profiles`). */
213
+ interface Profile {
214
+ id: string;
215
+ name: string;
216
+ runtime_state: RuntimeState;
217
+ enabled: boolean;
218
+ crash_reason: string | null;
219
+ pid: number | null;
220
+ run_lock: string | null;
221
+ seed: string;
222
+ engine_version: string;
223
+ persona: Persona;
224
+ host_mode: HostMode;
225
+ gpu_persona: string | null;
226
+ group_id: string | null;
227
+ proxy_id: string | null;
228
+ geo_mode: GeoMode;
229
+ expected_country: string | null;
230
+ data_dir: string;
231
+ created_at: number;
232
+ last_open_at: number | null;
233
+ /** Additive (a daemon predating it simply omits the field). Present on list/get
234
+ * (`ProfileView`), absent on create/delete/bulk, which answer the bare profile. */
235
+ protection?: ProtectionStatus;
236
+ /**
237
+ * Platform slugs this profile has a stored login for, sorted by the daemon.
238
+ * Additive — a daemon predating the credential vault simply omits it.
239
+ */
240
+ platforms?: string[];
241
+ }
242
+ interface Group {
243
+ id: string;
244
+ name: string;
245
+ created_at: number;
246
+ }
247
+ /**
248
+ * Operating config: what the created profiles DO. Every key mirrors a
249
+ * `POST /v1/profiles` body field. The daemon refuses an unknown key with a 400
250
+ * rather than dropping it — a preset makes up to 1000 profiles, so a swallowed
251
+ * key is a thousand wrong ones.
252
+ */
253
+ interface PresetConfig {
254
+ /** Pin the batch to one engine build. Omit for per-profile version diversity. */
255
+ engine_version?: string | null;
256
+ host_mode?: HostMode | null;
257
+ geo_mode?: GeoMode | null;
258
+ /** The geo GATE's expectation — not the persona's drawn region. */
259
+ expected_country?: string | null;
260
+ group_id?: string | null;
261
+ proxy_id?: string | null;
262
+ /**
263
+ * Library ids every profile of the batch is created with. Empty is normal —
264
+ * about half of real Chrome users carry no extension.
265
+ *
266
+ * An id the library no longer holds refuses the whole batch: a thousand profiles
267
+ * that silently differ from the preset look exactly like correct ones.
268
+ */
269
+ extensions?: string[];
270
+ }
271
+ /**
272
+ * Generator constraints: what the created profiles ARE. Exactly one axis,
273
+ * because an axis is only honest here if the daemon does not overwrite it at
274
+ * launch — the GPU, screen, DPR and Chrome version all are.
275
+ */
276
+ interface PresetConstraints {
277
+ /**
278
+ * ISO-3166 alpha-2 from `GET /v1/persona/constraints`. Masks the persona's
279
+ * locale draw, pinning language, timezone and speech voices together.
280
+ */
281
+ country?: string | null;
282
+ }
283
+ interface Preset {
284
+ id: string;
285
+ name: string;
286
+ constraints: PresetConstraints;
287
+ config: PresetConfig;
288
+ created_at: number;
289
+ }
290
+ /** `GET /v1/persona/constraints` — the values a preset may constrain to. */
291
+ interface PersonaConstraintOptions {
292
+ countries: string[];
293
+ }
294
+ /** Proxy — credentials are write-only (`#[serde(skip_serializing)]`); never returned. */
295
+ interface Proxy {
296
+ id: string;
297
+ kind: ProxyKind;
298
+ host: string;
299
+ port: number;
300
+ rotation: Rotation;
301
+ last_exit_ip: string | null;
302
+ last_country: string | null;
303
+ last_check_at: number | null;
304
+ healthy: boolean | null;
305
+ created_at: number;
306
+ }
307
+ /** Per-detector outcome; `skip` means the page could not be evaluated, NOT a pass. */
308
+ type AuditVerdict = 'pass' | 'warn' | 'fail' | 'skip' | 'error';
309
+ interface AuditDetectorReport {
310
+ id: string;
311
+ verdict: AuditVerdict;
312
+ detail: string;
313
+ }
314
+ interface AuditReport {
315
+ schema_version: number;
316
+ timestamp: string;
317
+ overall: AuditVerdict;
318
+ detectors: AuditDetectorReport[];
319
+ }
320
+ interface AuditStatus {
321
+ profile_id: string;
322
+ /** `idle` (never run since the daemon started), `running`, or `finished`. */
323
+ state: 'idle' | 'running' | 'finished';
324
+ started_at?: number;
325
+ finished_at?: number;
326
+ /** Why the RUN failed — distinct from a detector's own `skip`. */
327
+ error?: string;
328
+ report?: AuditReport;
329
+ }
330
+ interface CreateGroupBody {
331
+ name: string;
332
+ }
333
+ type UpdateGroupBody = Partial<CreateGroupBody>;
334
+ interface CreatePresetBody {
335
+ name: string;
336
+ constraints?: PresetConstraints;
337
+ config?: PresetConfig;
338
+ }
339
+ type UpdatePresetBody = Partial<CreatePresetBody>;
340
+ interface SessionExportBody {
341
+ password: string;
342
+ kinds?: SessionKind[];
343
+ }
344
+ interface SessionExportResult {
345
+ bundle: string;
346
+ /**
347
+ * Whether the cookies came out of the RUNNING browser for this export.
348
+ *
349
+ * `false` means the bundle carries only what was stored earlier, which for a
350
+ * stopped profile is all there can be: a browser holds its jar in its own
351
+ * process. Export while the profile runs for a current session.
352
+ */
353
+ harvested?: boolean;
354
+ /** Why the bundle is thinner than it could be, when it is. */
355
+ note?: string;
356
+ }
357
+ interface SessionImportBody {
358
+ password: string;
359
+ bundle: string;
360
+ }
361
+ /**
362
+ * Trusted-input payload (`POST /v1/profiles/:id/input`, G8, AC-API-003). Only
363
+ * `action` and `humanize` are fixed by `docs/features/api.md`; the per-action coordinate
364
+ * fields below are the SDK's contract assumption for the daemon's input crate.
365
+ */
366
+ type TrustedInputBody = {
367
+ action: 'move';
368
+ x: number;
369
+ y: number;
370
+ width?: number;
371
+ humanize?: boolean;
372
+ } | {
373
+ action: 'click';
374
+ x: number;
375
+ y: number;
376
+ button?: string;
377
+ click_count?: number;
378
+ width?: number;
379
+ humanize?: boolean;
380
+ } | {
381
+ action: 'type';
382
+ text: string;
383
+ humanize?: boolean;
384
+ } | {
385
+ action: 'scroll';
386
+ x: number;
387
+ y: number;
388
+ delta_x?: number;
389
+ delta_y?: number;
390
+ humanize?: boolean;
391
+ };
392
+ /** Who is driving: an agent holding a reservation, or a person at a window. */
393
+ type RunActor = 'agent' | 'human';
394
+ /** How a run ended. `reached`/`partial` appear only when a source claimed it. */
395
+ type RunOutcome = 'running' | 'reached' | 'partial' | 'blocked' | 'interrupted' | 'unknown';
396
+ /** What kind of thing a trail entry is. */
397
+ type StepKind = 'page' | 'action' | 'interruption';
398
+ /** The element a step acted on, as it can still be recognised later. */
399
+ interface StepTarget {
400
+ role: string;
401
+ name: string;
402
+ }
403
+ /** One tool call, as the operator gets to read it back. */
404
+ interface RunStep {
405
+ /** Position within the run, starting at 1. */
406
+ seq: number;
407
+ at: number;
408
+ duration_ms: number | null;
409
+ kind: StepKind;
410
+ /** The tool name; `null` for a synthetic `page` step. */
411
+ tool: string | null;
412
+ target: StepTarget | null;
413
+ /** A short human line. Carries typed text unless `redacted`. */
414
+ summary: string | null;
415
+ redacted: boolean;
416
+ /** Set on `page` steps. */
417
+ url: string | null;
418
+ /** Path of the still under the runs API; the image is fetched separately. */
419
+ shot_path: string | null;
420
+ /**
421
+ * What the call produced, in one line — "3 blocks, 122 characters",
422
+ * "no visible change · 14 elements".
423
+ *
424
+ * `summary` says what the agent asked for, this says what came back. A step
425
+ * recorded before this field existed has `null`.
426
+ */
427
+ result: string | null;
428
+ error: string | null;
429
+ }
430
+ /** What one running profile is showing. */
431
+ interface ProfileActivity {
432
+ profile_id: string;
433
+ profile_name: string;
434
+ url: string;
435
+ title: string;
436
+ actor: RunActor;
437
+ /** Unix seconds this address was first seen. */
438
+ since: number;
439
+ /** The browser did not answer the last sweep; this is the last known value. */
440
+ stale: boolean;
441
+ }
442
+
443
+ /**
444
+ * A daemon lifecycle event. Named `LifecycleEvent` (not `Event`) to avoid
445
+ * clashing with the global `Event`; also re-exported as `Event` for parity with
446
+ * the web-ui contract.
447
+ *
448
+ * Each event carries the whole state of the thing it is about, so a reader never
449
+ * has to fetch to act on one. The exception is an image: `RunStep.shot_path` is
450
+ * a reference, and the picture is already on disk when the event arrives.
451
+ */
452
+ type LifecycleEvent = {
453
+ type: 'profile_state_changed';
454
+ profile_id: string;
455
+ from: RuntimeState;
456
+ to: RuntimeState;
457
+ at: number;
458
+ } | {
459
+ type: 'profile_started';
460
+ profile_id: string;
461
+ cdp_ws: string;
462
+ headless: boolean;
463
+ at: number;
464
+ } | {
465
+ type: 'profile_stopped';
466
+ profile_id: string;
467
+ at: number;
468
+ } | {
469
+ type: 'profile_crashed';
470
+ profile_id: string;
471
+ reason: string;
472
+ at: number;
473
+ } | {
474
+ type: 'preflight_failed';
475
+ profile_id: string;
476
+ detail: string;
477
+ at: number;
478
+ } | {
479
+ type: 'proxy_checked';
480
+ proxy_id: string;
481
+ healthy: boolean;
482
+ country: string | null;
483
+ at: number;
484
+ } | {
485
+ type: 'capacity_rejected';
486
+ profile_id: string;
487
+ at: number;
488
+ }
489
+ /** An agent declared a task on a profile, or one was opened for undeclared work. */
490
+ | {
491
+ type: 'run_started';
492
+ run_id: string;
493
+ profile_id: string;
494
+ actor: RunActor;
495
+ goal: string;
496
+ at: number;
497
+ }
498
+ /** One tool call was appended to a run's trail — the whole stored step. */
499
+ | {
500
+ type: 'run_step';
501
+ run_id: string;
502
+ profile_id: string;
503
+ step: RunStep;
504
+ at: number;
505
+ } | {
506
+ type: 'run_finished';
507
+ run_id: string;
508
+ profile_id: string;
509
+ outcome: RunOutcome;
510
+ at: number;
511
+ }
512
+ /** A running profile's row appeared, or something about it is different. */
513
+ | {
514
+ type: 'profile_activity_changed';
515
+ activity: ProfileActivity;
516
+ at: number;
517
+ }
518
+ /** A profile is no longer running: its row goes away. */
519
+ | {
520
+ type: 'profile_activity_gone';
521
+ profile_id: string;
522
+ at: number;
523
+ }
524
+ /**
525
+ * Frames were lost; re-read rather than carry on. Raised when the daemon
526
+ * notices this connection fell behind. `dropped` counts what it skipped.
527
+ */
528
+ | {
529
+ type: 'resync';
530
+ dropped: number;
531
+ at: number;
532
+ };
533
+ /**
534
+ * An event as it arrives, with its position in this connection's stream.
535
+ *
536
+ * The daemon numbers every frame, starting at 1. A jump means frames were lost —
537
+ * the only way to tell "nothing happened" from "something happened and I did not
538
+ * get it". Optional so a daemon predating the numbering still type-checks.
539
+ */
540
+ type StreamedEvent = LifecycleEvent & {
541
+ seq?: number;
542
+ };
543
+
544
+ /**
545
+ * Live lifecycle event stream (`GET /v1/events`, AC-API-003).
546
+ *
547
+ * Read as `text/event-stream` over `fetch` (so the Bearer header is attached —
548
+ * the browser `EventSource` cannot do that) and reconnect with backoff. A
549
+ * `/v1/ws` websocket carries the same events; SSE is sufficient for one-way
550
+ * fan-out. Mirrors `web-ui/src/api/events.ts`.
551
+ */
552
+
553
+ /** Injectable `fetch` — defaults to the global at call time. */
554
+ type FetchLike = typeof globalThis.fetch;
555
+ type StreamStatus = 'connecting' | 'open' | 'closed';
556
+ interface StreamParams {
557
+ baseUrl: string;
558
+ token?: string | null;
559
+ fetch?: FetchLike;
560
+ /** When unauthorized (401 / code 4010), stop and notify instead of retrying. */
561
+ onUnauthorized?: () => void;
562
+ }
563
+ interface StreamHandlers {
564
+ onEvent: (event: LifecycleEvent) => void;
565
+ onStatus?: (status: StreamStatus) => void;
566
+ }
567
+ /**
568
+ * Subscribe to the lifecycle event stream. Returns an unsubscribe function that
569
+ * aborts the connection and stops reconnection.
570
+ *
571
+ * **Loss is reported, never swallowed.** Two things can cost frames, and both
572
+ * end in the same synthetic `resync` event so a reader has one case to handle:
573
+ * a jump in the numbering (a frame the daemon sent and this side did not get),
574
+ * and a reconnect (a gap of unknown length). A third, the daemon noticing that
575
+ * this connection fell behind, arrives as a real `resync` from the other side.
576
+ *
577
+ * The FIRST connection is deliberately not a reconnect: nothing was subscribed
578
+ * before it, so there is nothing a gap could have cost.
579
+ */
580
+ declare function subscribeEvents(params: StreamParams, handlers: StreamHandlers): () => void;
581
+ /** Extract and JSON-parse the `data:` payload of a single SSE frame. */
582
+ declare function parseSseFrame(frame: string): StreamedEvent | null;
583
+ /**
584
+ * `for await (const event of eventStream({...}))` ergonomics over
585
+ * {@link subscribeEvents}. Break out of the loop (or call `.return()`) to
586
+ * unsubscribe.
587
+ */
588
+ declare function eventStream(params: StreamParams): AsyncIterableIterator<LifecycleEvent>;
589
+
590
+ /**
591
+ * What the browser asks (interruptions), the bytes that go in and out
592
+ * (artifacts), and what the licence says (account).
593
+ *
594
+ * Interruption shapes mirror `web-ui/src/api/types/credentials.ts`, the shared
595
+ * source of contract truth.
596
+ */
597
+ /** What an operator has taken out of an agent's hands. */
598
+ type InterruptionLock = 'always_block' | 'always_allow' | 'agent_decides';
599
+ /**
600
+ * One answer, in the browser's OWN vocabulary — hence `allowOnce` in camelCase
601
+ * while everything else in this API is snake_case. The daemon's enum is
602
+ * `#[serde(rename_all = "camelCase")]`; sending `allow_once` is a 400.
603
+ */
604
+ type InterruptionChoice = 'allow' | 'allowOnce' | 'block' | 'dismiss';
605
+ interface InterruptionLockRow {
606
+ kind: string;
607
+ /** Null for the account-wide row. */
608
+ profile_id: string | null;
609
+ decision: InterruptionLock;
610
+ }
611
+ /** What applies right now for one kind, account-wide. */
612
+ interface EffectiveInterruptionLock {
613
+ kind: string;
614
+ /** Null means the agent decides. */
615
+ decision: InterruptionLock | null;
616
+ }
617
+ /**
618
+ * Everything about locks in one call, `effective` included.
619
+ *
620
+ * The daemon computes `effective` with the same function the launch path uses.
621
+ * Recomputing it yourself would be a second opinion, and code that disagrees
622
+ * with the browser is worse than code that shows nothing.
623
+ */
624
+ interface InterruptionLockView {
625
+ kinds: string[];
626
+ locked_by_default: string[];
627
+ rows: InterruptionLockRow[];
628
+ effective: EffectiveInterruptionLock[];
629
+ }
630
+ interface SetInterruptionLockBody {
631
+ kind: string;
632
+ profile_id?: string | null;
633
+ decision: InterruptionLock;
634
+ }
635
+ interface InterruptionRuleRow {
636
+ origin: string;
637
+ kind: string;
638
+ choice: InterruptionChoice;
639
+ /** Who wrote it — an agent must not be able to widen what the operator allowed. */
640
+ author: 'operator' | 'agent';
641
+ profile_id: string | null;
642
+ }
643
+ interface SetInterruptionRuleBody {
644
+ origin: string;
645
+ kind: string;
646
+ choice: InterruptionChoice;
647
+ profile_id?: string | null;
648
+ }
649
+ interface DeleteInterruptionRuleParams {
650
+ origin: string;
651
+ kind: string;
652
+ profile_id?: string | null;
653
+ }
654
+ /** The reply to a file handed in for a later upload. */
655
+ interface ArtifactPutResult {
656
+ artifact_id: string;
657
+ size_bytes: number;
658
+ name: string | null;
659
+ }
660
+ /** Bytes plus what the daemon said they are. */
661
+ interface ArtifactBytes {
662
+ bytes: Uint8Array;
663
+ content_type: string | null;
664
+ /** Parsed out of `Content-Disposition`, when the daemon sent one. */
665
+ filename: string | null;
666
+ }
667
+ /**
668
+ * Licence state, for display.
669
+ *
670
+ * The daemon decides on every launch whether a start is allowed; this never
671
+ * gets a say in it. Never gate your own code on it — a green light on a profile
672
+ * the launch gate refuses is worse than no light.
673
+ */
674
+ interface Account {
675
+ /** `false` on a self-hosted build with no control plane. */
676
+ licensed: boolean;
677
+ plan?: string;
678
+ status?: string;
679
+ /**
680
+ * Browsers the subscription may run AT ONCE. Replaced the profile allowance
681
+ * on 2026-08-18: profiles are unlimited on every plan now.
682
+ */
683
+ concurrency?: number;
684
+ /** Browsers running on THIS machine, not the abo-wide count. */
685
+ running_local: number;
686
+ /** Profiles stored on THIS machine. */
687
+ profiles_local: number;
688
+ features?: string[];
689
+ /** Unix seconds; when the cached entitlement stops being valid. */
690
+ expires_at?: number;
691
+ permits_launch: boolean;
692
+ }
693
+ interface HealthStatus {
694
+ status: string;
695
+ service?: string;
696
+ }
697
+ interface ReadyStatus {
698
+ status: string;
699
+ /** Where the daemon looks for engines. Whether one is INSTALLED is a
700
+ * launch-time question the preflight answers properly. */
701
+ engines_dir: string;
702
+ }
703
+
704
+ interface ScalebrowserClientOptions {
705
+ /** Daemon base URL. Default `http://127.0.0.1:8787`. */
706
+ baseUrl?: string;
707
+ /** Bearer token (AC-API-002). */
708
+ token?: string | null;
709
+ /** Injectable `fetch` (for testing / custom agents). Default `globalThis.fetch`. */
710
+ fetch?: FetchLike;
711
+ }
712
+ type QueryValue = string | number | boolean | null | undefined;
713
+ type Query = Record<string, QueryValue>;
714
+ interface RequestOptions {
715
+ query?: Query;
716
+ signal?: AbortSignal;
717
+ }
718
+ declare class ClientCore {
719
+ #private;
720
+ readonly baseUrl: string;
721
+ constructor(options?: ScalebrowserClientOptions);
722
+ /** For subclasses that hand the token to a sibling transport (SSE, CDP). */
723
+ protected get token(): string | null;
724
+ protected get fetchImpl(): FetchLike;
725
+ /** Rotate the Bearer token at runtime. */
726
+ setToken(token: string | null): void;
727
+ protected request<T>(method: string, path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
728
+ /**
729
+ * A route that answers with bytes, not JSON — artifacts and run stills.
730
+ *
731
+ * Separate from {@link ClientCore.request} rather than a flag on it, because the two
732
+ * differ in the error path as well: a failed byte fetch still answers JSON,
733
+ * so the mapping to {@link ApiError} has to happen before the body is read as
734
+ * binary.
735
+ */
736
+ protected requestBytes(method: string, path: string, opts?: RequestOptions): Promise<ArtifactBytes>;
737
+ }
738
+
739
+ declare class ControlApi extends ClientCore {
740
+ /** Which browser surfaces are locked shut, and what actually applies. */
741
+ listInterruptionLocks(signal?: AbortSignal): Promise<InterruptionLockView>;
742
+ /** Omit `profile_id` to set the fleet-wide default. */
743
+ setInterruptionLock(body: SetInterruptionLockBody): Promise<unknown>;
744
+ /** Standing answers per origin and kind. */
745
+ listInterruptionRules(signal?: AbortSignal): Promise<InterruptionRuleRow[]>;
746
+ setInterruptionRule(body: SetInterruptionRuleBody): Promise<unknown>;
747
+ deleteInterruptionRule(params: DeleteInterruptionRuleParams): Promise<unknown>;
748
+ /**
749
+ * Hand the daemon a file so `interact action=upload` has something to attach.
750
+ *
751
+ * The bytes are bound to the PROFILE, not to a reservation: set a file up
752
+ * before leasing, and a lease released mid-task does not destroy it.
753
+ */
754
+ putArtifact(profileId: string, bytes: Uint8Array | ArrayBuffer, name?: string): Promise<ArtifactPutResult>;
755
+ /**
756
+ * Fetch one artifact's bytes — a screenshot, a download, a saved PDF.
757
+ *
758
+ * A miss is a plain 404 whether the id never existed, expired, or belonged to
759
+ * a released lease: the caller's next move is the same in all three cases.
760
+ */
761
+ getArtifact(artifactId: string, signal?: AbortSignal): Promise<ArtifactBytes>;
762
+ /**
763
+ * Licence state for display. The launch gate stays the authority on starts.
764
+ *
765
+ * A daemon predating the route answers 404, and that is not an error worth
766
+ * throwing: it means "self-hosted, no control plane", which is exactly what
767
+ * `licensed: false` says. Same shape of tolerance as {@link getMetrics}.
768
+ */
769
+ getAccount(signal?: AbortSignal): Promise<Account>;
770
+ /** "Is the daemon alive?" Public, needs no token. */
771
+ health(signal?: AbortSignal): Promise<HealthStatus>;
772
+ /** "Can it serve?" Local facts only — the control plane is deliberately not consulted. */
773
+ ready(signal?: AbortSignal): Promise<ReadyStatus>;
774
+ }
775
+
776
+ /** Column the DAEMON orders by. Sorting a fetched page would order a minority of
777
+ * a paged fleet while looking authoritative, so ordering is a server parameter. */
778
+ type ProfileSort = 'created_at' | 'name' | 'runtime_state' | 'last_open_at';
779
+ type SortOrder = 'asc' | 'desc';
780
+ /** `GET /v1/profiles/ids` — every id matching the same filters, unpaged. */
781
+ interface ProfileIdsResult {
782
+ count: number;
783
+ ids: string[];
784
+ }
785
+ /** `POST /v1/bulk/start` — per-id results plus what the batch as a whole did. */
786
+ interface BulkStartResult {
787
+ results: {
788
+ id: string;
789
+ ok: boolean;
790
+ cdp_ws?: string;
791
+ error?: string;
792
+ }[];
793
+ started: number;
794
+ remaining: string[];
795
+ stopped_reason?: string;
796
+ }
797
+ interface ListProfilesParams {
798
+ group?: string;
799
+ state?: RuntimeState;
800
+ q?: string;
801
+ limit?: number;
802
+ offset?: number;
803
+ sort?: ProfileSort;
804
+ order?: SortOrder;
805
+ }
806
+ interface CreateProfileBody {
807
+ name: string;
808
+ seed?: string;
809
+ host_mode?: HostMode;
810
+ geo_mode?: GeoMode;
811
+ expected_country?: string | null;
812
+ group_id?: string | null;
813
+ proxy_id?: string | null;
814
+ }
815
+ /**
816
+ * PATCH — all mutable fields optional. `host_mode` and `seed` are fixed at create
817
+ * time (C4/C5): a DIFFERING `host_mode` is refused with 400, an unchanged echo is
818
+ * accepted. Changing a tier requires delete + recreate.
819
+ */
820
+ type UpdateProfileBody = Omit<Partial<CreateProfileBody>, "host_mode" | "seed">;
821
+ interface StartProfileBody {
822
+ headless?: boolean;
823
+ }
824
+ interface StartProfileResult {
825
+ profile_id: string;
826
+ cdp_ws: string;
827
+ /** DevTools TCP port, parsed from `cdp_ws` (`null` if the URL has none). */
828
+ debug_port: number | null;
829
+ /** Effective headless state of this launch (AC-OR-005). */
830
+ headless: boolean;
831
+ pid: number | null;
832
+ started_at: number;
833
+ }
834
+ interface StopProfileResult {
835
+ stopped: boolean;
836
+ }
837
+ interface BulkCreateBody {
838
+ preset_id: string;
839
+ count: number;
840
+ /** Base name for the batch; the daemon appends an index per profile. */
841
+ name_prefix?: string;
842
+ /** Drop the whole batch into this group at create time. */
843
+ group_id?: string;
844
+ }
845
+ interface CreateProxyBody {
846
+ kind: ProxyKind;
847
+ host: string;
848
+ port: number;
849
+ username?: string | null;
850
+ password?: string | null;
851
+ rotation?: Rotation;
852
+ }
853
+ type UpdateProxyBody = Partial<CreateProxyBody>;
854
+ /**
855
+ * Result of `POST /v1/proxies/:id/check` (health + geo).
856
+ *
857
+ * CONTRACT ASSUMPTION: the daemon returns the freshly-checked exit identity plus
858
+ * the JA4↔JA4T coherence verdict (AC-FP-011). Fields beyond the persisted `Proxy`
859
+ * columns (`ja4`, `ja4t`, `ja4t_mismatch`, `warnings`) are optional; if the
860
+ * daemon only echoes the updated `Proxy`, the JA4 verdict is simply absent.
861
+ */
862
+ interface ProxyCheckResult {
863
+ healthy: boolean;
864
+ exit_ip: string | null;
865
+ country: string | null;
866
+ /** Carrier/mobile-exit hint from the exit lookup (a mobile exit is the best case). */
867
+ is_mobile?: boolean | null;
868
+ ja4?: string | null;
869
+ ja4t?: string | null;
870
+ ja4t_mismatch?: boolean;
871
+ warnings?: string[];
872
+ checked_at?: number;
873
+ /** Round-trip RTT — `null` on an unreachable exit (a timeout is not a latency). */
874
+ latency_ms?: number | null;
875
+ }
876
+ /**
877
+ * Body of `POST /v1/proxies/check` — probe a proxy config that is NOT persisted
878
+ * (health + geo before you commit it).
879
+ *
880
+ * Edit-mode: pass the `id` of an existing proxy and omit `username`/`password`
881
+ * to probe with its stored (write-only) credentials. A proxy whose credentials
882
+ * cannot be decrypted fails closed rather than probing unauthenticated.
883
+ */
884
+ interface CheckProxyConfigBody {
885
+ kind: ProxyKind;
886
+ host: string;
887
+ port: number;
888
+ username?: string | null;
889
+ password?: string | null;
890
+ rotation?: Rotation;
891
+ /** Existing proxy id — enables the stored-credential fallback. */
892
+ id?: string | null;
893
+ }
894
+ /**
895
+ * One package in the daemon-wide extension library, keyed by id AND version: the
896
+ * library stocks several versions of the same extension and each profile draws one.
897
+ */
898
+ interface Extension {
899
+ /** Canonical Web-Store id, derived from the package's own key. Repeats across versions. */
900
+ id: string;
901
+ name: string;
902
+ version: string;
903
+ /** Base64 DER public key lifted from the CRX header. */
904
+ public_key: string;
905
+ /**
906
+ * Unpacked library directory (the profile launches from its own copy).
907
+ * Derived by the daemon from its data dir rather than stored, so a moved data
908
+ * dir does not leave every row pointing at nothing.
909
+ */
910
+ dir: string;
911
+ /** SHA-256 of the unpacked tree — names the CONTENT of this version. */
912
+ content_hash: string;
913
+ added_at: number;
914
+ }
915
+ /**
916
+ * How a profile's extension set reaches the browser. Returned with every
917
+ * extensions response; `engine_switches` is what the launch actually emits.
918
+ *
919
+ * `id_model` is `"canonical"`: the id comes from the package's key and is the same
920
+ * for every profile — deliberately, since it is the id millions of real users
921
+ * report. Only the FILES are per-profile (`delivery: "per_profile_copy"`).
922
+ */
923
+ interface ExtensionPolicy {
924
+ id_model: string;
925
+ delivery: string;
926
+ /** Exact, ordered engine switches emitted for this profile's set; empty when none. */
927
+ engine_switches: string[];
928
+ }
929
+ /** Response of every `/v1/profiles/:id/extensions` call: the set + the policy. */
930
+ interface ExtensionsResult {
931
+ profile_id: string;
932
+ /** Assigned library ids, as an idempotent set. */
933
+ extensions: string[];
934
+ /**
935
+ * The set this profile actually loads — ONE version per extension, drawn from the
936
+ * profile's seed over what the library stocks. Shorter than `extensions` when an id
937
+ * no longer resolves.
938
+ */
939
+ packages: Extension[];
940
+ policy: ExtensionPolicy;
941
+ /**
942
+ * What is worth knowing about THIS set, judged by the daemon with the same
943
+ * function the launch gate uses. Empty for an unremarkable set — including an
944
+ * empty one, since about half of real Chrome users carry no extension.
945
+ *
946
+ * Never recompute it client-side: a second opinion could contradict the gate.
947
+ */
948
+ advice: ExtensionAdvice[];
949
+ }
950
+ /** One finding about a profile's extension set. */
951
+ interface ExtensionAdvice {
952
+ /** `warn` — a launch is never blocked by an extension. */
953
+ level: string;
954
+ message: string;
955
+ }
956
+
957
+ declare class ExtensionsApi extends ClientCore {
958
+ /** The library, newest first. */
959
+ listLibraryExtensions(signal?: AbortSignal): Promise<Extension[]>;
960
+ /**
961
+ * Add a package. `crx` is the raw `.crx` file. A new VERSION is stocked alongside the
962
+ * existing ones (profiles draw their own); re-uploading the same version replaces it.
963
+ */
964
+ uploadExtension(crx: Uint8Array | ArrayBuffer): Promise<Extension>;
965
+ /** One library entry — the most recently added version. */
966
+ getLibraryExtension(id: string, signal?: AbortSignal): Promise<Extension>;
967
+ /** Remove EVERY version of a package, its files and every profile's assignment of it. */
968
+ deleteLibraryExtension(id: string): Promise<unknown>;
969
+ /**
970
+ * Remove ONE stocked version.
971
+ *
972
+ * Assignments survive — they name the extension, not the version — unless this
973
+ * was the last version, in which case they go with it.
974
+ */
975
+ deleteLibraryExtensionVersion(id: string, version: string): Promise<unknown>;
976
+ /** The profile's assigned set, resolved, + the launch policy. */
977
+ listExtensions(profileId: string, signal?: AbortSignal): Promise<ExtensionsResult>;
978
+ /** Assign a library id (idempotent) → the updated set + policy. 404 if unknown. */
979
+ attachExtension(profileId: string, extRef: string): Promise<ExtensionsResult>;
980
+ /** Unassign a library id (idempotent) → the updated set + policy. */
981
+ detachExtension(profileId: string, extRef: string): Promise<ExtensionsResult>;
982
+ }
983
+
984
+ /**
985
+ * One stored platform login — everything about it EXCEPT the secrets.
986
+ *
987
+ * The username IS returned: it is the account's display identity, not a secret on
988
+ * any platform, and without it two stored accounts are indistinguishable. The
989
+ * password and two-factor key travel only through `revealCredential`, which needs
990
+ * the vault password.
991
+ */
992
+ interface CredentialMeta {
993
+ platform: string;
994
+ username: string | null;
995
+ login_url: string | null;
996
+ has_password: boolean;
997
+ has_totp: boolean;
998
+ /** Typed into a sign-up form but never confirmed — the account may not exist. */
999
+ pending?: boolean;
1000
+ created_at: number;
1001
+ updated_at: number;
1002
+ last_used_at: number | null;
1003
+ /** No longer opens with the current master key: listable, deletable, unusable. */
1004
+ secrets_unreadable?: boolean;
1005
+ }
1006
+ /** An omitted secret means "leave it unchanged", not "delete it". */
1007
+ interface PutCredentialBody {
1008
+ username?: string | null;
1009
+ password?: string | null;
1010
+ /** Base32 setup key. Grouped, lower-case input is normalised by the daemon. */
1011
+ totp_secret?: string | null;
1012
+ login_url?: string | null;
1013
+ }
1014
+ /** The only shape in this SDK that carries a password. */
1015
+ interface RevealedCredential {
1016
+ platform: string;
1017
+ username: string | null;
1018
+ password: string | null;
1019
+ totp_secret: string | null;
1020
+ }
1021
+ /** A passphrase-sealed backup of one profile's logins. */
1022
+ interface CredentialBundle {
1023
+ bundle: string;
1024
+ count: number;
1025
+ }
1026
+ interface CredentialImportResult {
1027
+ /** Boolean, like the session import — the number is in `count`. */
1028
+ imported: boolean;
1029
+ count: number;
1030
+ platforms: string[];
1031
+ }
1032
+ interface VaultStatus {
1033
+ configured: boolean;
1034
+ }
1035
+
1036
+ /**
1037
+ * The account layer: mailboxes, passkeys and the one door a cookie value leaves
1038
+ * through.
1039
+ *
1040
+ * Mailbox and passkey shapes mirror `web-ui/src/api/types/{inboxes,credentials}.ts`,
1041
+ * the shared source of contract truth. The cookie reveal has no UI counterpart:
1042
+ * it is a REST-only route deliberately kept off every screen.
1043
+ */
1044
+ /** One configured mailbox. The password is never sent back. */
1045
+ interface Inbox {
1046
+ id: string;
1047
+ name: string;
1048
+ host: string;
1049
+ port: number;
1050
+ /** The mailbox login. Not a secret, and it is how two rows are told apart. */
1051
+ user: string;
1052
+ /** TLS from the first byte. `false` is only accepted towards this machine. */
1053
+ tls: boolean;
1054
+ folder: string;
1055
+ /**
1056
+ * The domain new addresses are minted under, e.g. `my-domain.tld`. Absent for
1057
+ * a mailbox with exactly one address.
1058
+ */
1059
+ address_domain: string | null;
1060
+ created_at: number;
1061
+ /**
1062
+ * The stored password could not be decrypted — a restored database without
1063
+ * its key. The row stays visible and deletable; every use of it fails closed.
1064
+ */
1065
+ credentials_unreadable?: boolean;
1066
+ }
1067
+ interface CreateInboxBody {
1068
+ name: string;
1069
+ host: string;
1070
+ port: number;
1071
+ user: string;
1072
+ /** Required when creating; leave it out on an edit to keep the stored one. */
1073
+ password?: string;
1074
+ tls?: boolean;
1075
+ folder?: string;
1076
+ address_domain?: string | null;
1077
+ }
1078
+ type UpdateInboxBody = CreateInboxBody;
1079
+ /** Which channel a code arrives on. */
1080
+ type InboxChannel = 'email' | 'sms';
1081
+ /** What one profile is bound to, per channel. */
1082
+ interface InboxBinding {
1083
+ profile_id: string;
1084
+ channel: InboxChannel;
1085
+ /** The address or number this profile is known by. */
1086
+ address: string;
1087
+ /**
1088
+ * The mailbox that answers. `null` means the operator's configured command
1089
+ * does, which is the only shape an SMS binding can have.
1090
+ */
1091
+ inbox_id: string | null;
1092
+ created_at: number;
1093
+ }
1094
+ interface BindInboxBody {
1095
+ channel: InboxChannel;
1096
+ inbox_id?: string | null;
1097
+ /** Absent mints one under the mailbox's catch-all domain. */
1098
+ address?: string;
1099
+ }
1100
+ interface InboxBindings {
1101
+ bindings: InboxBinding[];
1102
+ }
1103
+ /**
1104
+ * One passkey of a profile — a login that needs no password.
1105
+ *
1106
+ * `credential_id` names the row for deletion and nothing else. The private key
1107
+ * has no field here and no endpoint: it IS the account, and it could not be
1108
+ * typed anywhere useful, so returning it would be risk without a purpose.
1109
+ */
1110
+ interface PasskeyRow {
1111
+ credential_id: string;
1112
+ /** The site it belongs to, e.g. `github.com`. */
1113
+ rp_id: string;
1114
+ user_name: string | null;
1115
+ user_display: string | null;
1116
+ created_at: number;
1117
+ last_used_at: number | null;
1118
+ /** True when a stored secret cannot be decrypted — the sign-in WILL fail. */
1119
+ secrets_unreadable: boolean;
1120
+ /**
1121
+ * When the key stopped being usable, if it has. A retired key is never loaded
1122
+ * into the browser and never offered to an agent, but it stays visible until
1123
+ * it is cleared: an account that quietly stops working is the first thing
1124
+ * anyone asks about.
1125
+ */
1126
+ retired_at: number | null;
1127
+ /**
1128
+ * `unknown` is a reason this build cannot name, and it deliberately claims
1129
+ * nothing — reading it as `operator` would tell you that you had deleted
1130
+ * something you never touched.
1131
+ */
1132
+ retired_reason: 'operator' | 'agent' | 'site_revoked' | 'unknown' | null;
1133
+ }
1134
+ /** A cookie VALUE skips both the password and the second factor beside it. */
1135
+ interface RevealedCookie {
1136
+ name: string;
1137
+ domain: string;
1138
+ path: string;
1139
+ value: string;
1140
+ }
1141
+ interface RevealCookiesBody {
1142
+ /** The same gate a far weaker password sits behind. */
1143
+ vault_password: string;
1144
+ domain: string;
1145
+ /** One cookie by name; omit for every cookie on the domain. */
1146
+ name?: string | null;
1147
+ }
1148
+ interface RevealCookiesResult {
1149
+ domain: string;
1150
+ cookies: RevealedCookie[];
1151
+ }
1152
+
1153
+ declare class IdentityApi extends ClientCore {
1154
+ /** Which platforms this profile can log into. Never carries a secret. */
1155
+ listCredentials(profileId: string, signal?: AbortSignal): Promise<CredentialMeta[]>;
1156
+ /**
1157
+ * Store or update one login.
1158
+ *
1159
+ * An omitted secret keeps its stored value rather than clearing it — an edit form
1160
+ * never saw the password in the clear, so it cannot resend one; delete the row to
1161
+ * remove a credential. `totp_secret` may be pasted exactly as a site presents it
1162
+ * (grouped, lower case): it is normalised and test-decoded by the daemon, so a
1163
+ * broken key is refused here rather than half-way through a login.
1164
+ */
1165
+ putCredential(profileId: string, platform: string, body: PutCredentialBody): Promise<CredentialMeta>;
1166
+ /** Forget one login. The account itself is untouched. */
1167
+ deleteCredential(profileId: string, platform: string): Promise<void>;
1168
+ /** Read one login back in the clear. Needs the vault password. */
1169
+ revealCredential(profileId: string, platform: string, vaultPassword: string): Promise<RevealedCredential>;
1170
+ /**
1171
+ * Pack this profile's logins into a bundle sealed with `password`.
1172
+ *
1173
+ * `password` is deliberately separate from the vault password: a backup gets
1174
+ * handed to another machine, and that must not also hand over daemon access. This
1175
+ * is the answer to a lost master key — a daemon-generated password exists nowhere
1176
+ * else.
1177
+ */
1178
+ exportCredentials(profileId: string, vaultPassword: string, password: string): Promise<CredentialBundle>;
1179
+ /** Restore a bundle into this profile. */
1180
+ importCredentials(profileId: string, vaultPassword: string, password: string, bundle: string): Promise<CredentialImportResult>;
1181
+ /** Has a vault password been set on this daemon? */
1182
+ vaultStatus(signal?: AbortSignal): Promise<VaultStatus>;
1183
+ /** Set the vault password, or change it (then `currentPassword` is required). */
1184
+ setVaultPassword(newPassword: string, currentPassword?: string): Promise<void>;
1185
+ /** Mailboxes, passwords never returned. */
1186
+ listInboxes(signal?: AbortSignal): Promise<Inbox[]>;
1187
+ createInbox(body: CreateInboxBody): Promise<Inbox>;
1188
+ /** An omitted `password` leaves the stored one unchanged. */
1189
+ updateInbox(inboxId: string, body: UpdateInboxBody): Promise<Inbox>;
1190
+ deleteInbox(inboxId: string): Promise<unknown>;
1191
+ /** Which mailbox a profile's confirmation codes arrive in, per channel. */
1192
+ getInboxBindings(profileId: string, signal?: AbortSignal): Promise<InboxBindings>;
1193
+ bindInbox(profileId: string, body: BindInboxBody): Promise<InboxBindings>;
1194
+ unbindInbox(profileId: string, channel: string): Promise<unknown>;
1195
+ /** What this profile can sign in to without a password. Metadata only. */
1196
+ listPasskeys(profileId: string, signal?: AbortSignal): Promise<PasskeyRow[]>;
1197
+ /**
1198
+ * Retire a passkey.
1199
+ *
1200
+ * Calling it twice is meaningful: the first call retires a live key and leaves
1201
+ * a headstone, the second clears the headstone for good.
1202
+ */
1203
+ deletePasskey(profileId: string, credentialId: string): Promise<unknown>;
1204
+ /**
1205
+ * The one door a cookie VALUE leaves through, and it needs the vault password.
1206
+ *
1207
+ * A session cookie skips both the password and the second factor, which is why
1208
+ * every other cookie surface answers with metadata only.
1209
+ */
1210
+ revealCookies(profileId: string, body: RevealCookiesBody): Promise<RevealCookiesResult>;
1211
+ }
1212
+
1213
+ /**
1214
+ * Agent runs and live activity (`/v1/runs`, `/v1/activity`).
1215
+ *
1216
+ * All of it is display: nothing here starts, stops or changes anything.
1217
+ *
1218
+ * `RunActor`, `RunOutcome`, `StepKind`, `StepTarget`, `RunStep` and
1219
+ * `ProfileActivity` are NOT redeclared here — they live in `types.ts` because
1220
+ * the live event stream carries them, and the run list describes the same rows.
1221
+ * A second copy would let the two drift apart while both looked right.
1222
+ *
1223
+ * Mirrors `web-ui/src/api/types/runs.ts`, the shared source of contract truth.
1224
+ */
1225
+
1226
+ /** Who made the outcome claim. `derived` means nobody did. */
1227
+ type OutcomeSource = 'agent' | 'check' | 'human' | 'derived';
1228
+ /** Whether the goal text was stated or merely describes the session. */
1229
+ type GoalSource = 'agent' | 'derived';
1230
+ interface AgentRun {
1231
+ id: string;
1232
+ /** Groups runs that shared one profile reservation. */
1233
+ session_id: string;
1234
+ profile_id: string;
1235
+ profile_name: string;
1236
+ actor: RunActor;
1237
+ /** Never empty — described when it was not declared (see `goal_source`). */
1238
+ goal: string;
1239
+ goal_source: GoalSource;
1240
+ /**
1241
+ * `reached` and `partial` are CLAIMS: the daemon can tell that a run ended,
1242
+ * never that it worked, so those two only ever come from an agent, a check or
1243
+ * a person.
1244
+ */
1245
+ outcome: RunOutcome;
1246
+ outcome_source: OutcomeSource | null;
1247
+ note: string | null;
1248
+ started_at: number;
1249
+ ended_at: number | null;
1250
+ step_count: number;
1251
+ /**
1252
+ * Steps that never reached the trail because the writer queue was full. Zero
1253
+ * on a healthy run, and worth reading: a trail missing entries looks exactly
1254
+ * like an agent that did less work.
1255
+ */
1256
+ steps_lost: number;
1257
+ first_domain: string | null;
1258
+ }
1259
+ interface ActivitySnapshot {
1260
+ sampled_at: number | null;
1261
+ profiles: ProfileActivity[];
1262
+ }
1263
+ interface ListRunsParams {
1264
+ profile_id?: string;
1265
+ limit?: number;
1266
+ offset?: number;
1267
+ }
1268
+ interface ListStepsParams {
1269
+ limit?: number;
1270
+ offset?: number;
1271
+ }
1272
+
1273
+ declare class RunsApi extends ClientCore {
1274
+ /** Runs, newest first. Read-only, all of it. */
1275
+ listRuns(params?: ListRunsParams, signal?: AbortSignal): Promise<AgentRun[]>;
1276
+ /** One run. */
1277
+ getRun(runId: string, signal?: AbortSignal): Promise<AgentRun>;
1278
+ /** A run's steps, oldest first — the order they happened in. */
1279
+ listRunSteps(runId: string, params?: ListStepsParams, signal?: AbortSignal): Promise<RunStep[]>;
1280
+ /**
1281
+ * The still taken at one step, as JPEG bytes.
1282
+ *
1283
+ * The picture shows a customer's account, so it is decrypted only on the way
1284
+ * out and never cached anywhere shared.
1285
+ */
1286
+ getRunShot(runId: string, seq: number, signal?: AbortSignal): Promise<ArtifactBytes>;
1287
+ /** What every running profile currently shows. */
1288
+ getActivity(signal?: AbortSignal): Promise<ActivitySnapshot>;
1289
+ }
1290
+
1291
+ /**
1292
+ * Direct-CDP driver (DEC-006, AC-FP-005) — a minimal, nodriver-style Chrome
1293
+ * DevTools Protocol client over a raw websocket. This is the recommended SDK
1294
+ * driver plane: it is NOT the Playwright/Puppeteer control plane (which is a
1295
+ * detection vector independent of patch quality). We never call `Runtime.enable`
1296
+ * (a known leak surface); `Runtime.evaluate` works without it, and isolated
1297
+ * worlds are created explicitly via `Page.createIsolatedWorld`.
1298
+ */
1299
+
1300
+ /** The slice of the REST client the CDP driver needs for `humanize*` helpers. */
1301
+ interface InputClient {
1302
+ sendInput(profileId: string, body: TrustedInputBody): Promise<unknown>;
1303
+ }
1304
+ /** A CDP event frame (no `id`): `{ method, params, sessionId? }`. */
1305
+ interface CdpEvent {
1306
+ method: string;
1307
+ params: Record<string, unknown>;
1308
+ sessionId?: string;
1309
+ }
1310
+ interface NavigateResult {
1311
+ frameId: string;
1312
+ loaderId?: string;
1313
+ errorText?: string;
1314
+ }
1315
+ /**
1316
+ * The websocket multiplexer: assigns command ids, resolves responses, and fans
1317
+ * CDP events out to subscribers. One connection backs the root session and every
1318
+ * attached page session.
1319
+ */
1320
+ declare class CdpConnection {
1321
+ #private;
1322
+ constructor(ws: WebSocket);
1323
+ request<T>(method: string, params?: Record<string, unknown>, sessionId?: string): Promise<T>;
1324
+ subscribe(cb: (event: CdpEvent) => void): () => void;
1325
+ /** Register a callback fired once when the connection closes (immediately if
1326
+ * it already has) — event streams use this to end instead of hanging. */
1327
+ onClose(cb: () => void): () => void;
1328
+ close(): void;
1329
+ get closed(): boolean;
1330
+ }
1331
+ interface AttachOptions {
1332
+ /** Attach to this exact target; otherwise the first `page` target is used. */
1333
+ targetId?: string;
1334
+ /** Create a fresh target (tab) instead of reusing an existing one. */
1335
+ create?: boolean;
1336
+ /** Initial URL when creating a target (default `about:blank`). */
1337
+ url?: string;
1338
+ }
1339
+ interface NavigateOptions {
1340
+ /** Wait until `document.readyState === 'complete'` (default true). */
1341
+ wait?: boolean;
1342
+ /** Wait timeout in ms (default 30000). */
1343
+ timeout?: number;
1344
+ }
1345
+ interface EvaluateOptions {
1346
+ awaitPromise?: boolean;
1347
+ returnByValue?: boolean;
1348
+ /** Run in a fresh isolated world rather than the page's main world. */
1349
+ isolated?: boolean;
1350
+ }
1351
+ interface ClickOptions {
1352
+ button?: string;
1353
+ clickCount?: number;
1354
+ /**
1355
+ * B5: element-relative targeting — the effective target width (an element's
1356
+ * bounding-box size). The daemon's Fitts-law approach lands within the element
1357
+ * instead of on a fixed corner. Prefer {@link CdpSession.humanizeClickElement}.
1358
+ */
1359
+ width?: number;
1360
+ }
1361
+ interface MoveOptions {
1362
+ /** B5: element-relative target width (see {@link ClickOptions.width}). */
1363
+ width?: number;
1364
+ }
1365
+ interface ScrollOptions {
1366
+ deltaX?: number;
1367
+ deltaY?: number;
1368
+ }
1369
+ /** A DOM element's bounding box (e.g. from `getBoundingClientRect()`). */
1370
+ interface ElementBox {
1371
+ x: number;
1372
+ y: number;
1373
+ width: number;
1374
+ height: number;
1375
+ }
1376
+ /**
1377
+ * A CDP session. The root session (no `sessionId`) speaks browser-level CDP;
1378
+ * `attachToPage()` returns a page-bound session whose commands carry its
1379
+ * `sessionId` (flatten mode). `navigate`/`evaluate`/`createIsolatedWorld` and
1380
+ * the `humanize*` helpers operate on a page-bound session.
1381
+ */
1382
+ declare class CdpSession {
1383
+ #private;
1384
+ readonly sessionId: string | undefined;
1385
+ constructor(conn: CdpConnection, sessionId?: string, client?: InputClient, profileId?: string);
1386
+ /** Send a raw CDP command and await its result (by id). */
1387
+ send<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
1388
+ /** Register a callback for a CDP event scoped to this session. Returns unsubscribe. */
1389
+ on(method: string, callback: (params: Record<string, unknown>) => void): () => void;
1390
+ /** `for await (const event of session.events())` over this session's CDP events. */
1391
+ events(): AsyncIterableIterator<CdpEvent>;
1392
+ /** Attach to a page target, returning a page-bound child session. */
1393
+ attachToPage(opts?: AttachOptions): Promise<CdpSession>;
1394
+ /** Navigate the page; optionally wait for load. Never calls `Runtime.enable`. */
1395
+ navigate(url: string, opts?: NavigateOptions): Promise<NavigateResult>;
1396
+ /** Evaluate a JS expression in the page (or an isolated world) and return its value. */
1397
+ evaluate<T = unknown>(expression: string, opts?: EvaluateOptions): Promise<T>;
1398
+ /** Create an isolated world for the (main) frame and return its execution context id. */
1399
+ createIsolatedWorld(frameId?: string, worldName?: string): Promise<number>;
1400
+ humanizeMove(x: number, y: number, opts?: MoveOptions): Promise<unknown>;
1401
+ humanizeClick(x: number, y: number, opts?: ClickOptions): Promise<unknown>;
1402
+ /**
1403
+ * B5: humanized move to the CENTRE of an element box, with the box width fed to the
1404
+ * daemon's Fitts-law approach so the pointer lands *within* the element (not on a
1405
+ * fixed corner). Pass a `getBoundingClientRect()`-shaped box.
1406
+ */
1407
+ humanizeMoveToElement(box: ElementBox): Promise<unknown>;
1408
+ /** B5: humanized click at the centre of an element box (see {@link humanizeMoveToElement}). */
1409
+ humanizeClickElement(box: ElementBox, opts?: ClickOptions): Promise<unknown>;
1410
+ humanizeType(text: string): Promise<unknown>;
1411
+ humanizeScroll(x: number, y: number, opts?: ScrollOptions): Promise<unknown>;
1412
+ /** Close the underlying CDP connection. */
1413
+ close(): void;
1414
+ }
1415
+ interface CdpConnectOptions {
1416
+ token?: string | null;
1417
+ client?: InputClient;
1418
+ profileId?: string;
1419
+ /** Auto-attach to a page target so navigate/evaluate work (default true). */
1420
+ autoAttach?: boolean;
1421
+ /** Create a fresh tab on attach instead of reusing an existing page (default false). */
1422
+ createTarget?: boolean;
1423
+ /** Initial URL when creating a target. */
1424
+ targetUrl?: string;
1425
+ /** Websocket handshake timeout in ms (default 30000). */
1426
+ openTimeout?: number;
1427
+ }
1428
+ /** Open a CDP websocket and return a (page-bound, unless `autoAttach:false`) session. */
1429
+ declare function openCdpSession(url: string, options?: CdpConnectOptions): Promise<CdpSession>;
1430
+
1431
+ /**
1432
+ * Dashboard/SDK metrics (AC-UI-002, M3). The daemon serves `GET /v1/metrics`
1433
+ * from a background sampler; each null-able figure is disambiguated by
1434
+ * {@link Metrics.availability} (`measured` vs `unsupported` vs `unavailable`).
1435
+ * Against an old daemon without the endpoint (404) the client falls back to
1436
+ * deriving running counts from profile states and stamps `source: "derived"`
1437
+ * (see {@link deriveMetricsFromProfiles}).
1438
+ */
1439
+ type ProbeState = 'measured' | 'unsupported' | 'unavailable';
1440
+ type MetricsSource = 'live' | 'derived';
1441
+ interface MetricsAvailability {
1442
+ ram: ProbeState;
1443
+ cpu: ProbeState;
1444
+ gpu: ProbeState;
1445
+ vram: ProbeState;
1446
+ per_profile: ProbeState;
1447
+ }
1448
+ interface ProfileResource {
1449
+ profile_id: string;
1450
+ name: string;
1451
+ pid: number | null;
1452
+ ram_mb: number | null;
1453
+ cpu_pct: number | null;
1454
+ vram_mb: number | null;
1455
+ }
1456
+ interface Metrics {
1457
+ running: number;
1458
+ capacity_max_concurrent: number;
1459
+ ram_used_mb: number | null;
1460
+ ram_budget_mb: number;
1461
+ vram_used_mb: number | null;
1462
+ vram_budget_mb: number | null;
1463
+ cpu_pct: number | null;
1464
+ gpu_pct: number | null;
1465
+ source?: MetricsSource;
1466
+ sampled_at?: number | null;
1467
+ availability?: MetricsAvailability;
1468
+ profiles?: ProfileResource[];
1469
+ /**
1470
+ * Launchable host memory (MiB) — the capacity gate's own reading. "How many
1471
+ * more browsers fit" derives from THIS; `ram_budget_mb − ram_used_mb` does
1472
+ * not subtract (used is host-wide, the budget is the daemon's own ceiling).
1473
+ */
1474
+ ram_available_mb?: number | null;
1475
+ }
1476
+
1477
+ /**
1478
+ * `ScalebrowserClient` — the one typed HTTP surface every REST call goes
1479
+ * through. It centralises the base URL, Bearer auth, query-string building, JSON
1480
+ * (de)serialisation and error mapping (`{ code, message }` → {@link ApiError}).
1481
+ * Backend endpoint drift is a change in THIS file (paths) plus `types.ts`
1482
+ * (shapes) — nowhere else.
1483
+ */
1484
+
1485
+ /** Mirrors `CapacityConfig::default()` (`crates/core/src/config.rs`). */
1486
+ declare const DEFAULT_CAPACITY: {
1487
+ max_concurrent: number;
1488
+ ram_budget_mb: number;
1489
+ vram_budget_mb: number | null;
1490
+ };
1491
+ /** A live, CDP-driven browser launched via {@link ScalebrowserClient.launch}. */
1492
+ interface LaunchHandle {
1493
+ /** The page-bound CDP session. */
1494
+ readonly cdp: CdpSession;
1495
+ /** The `start` result (`cdp_ws`, `debug_port`, `headless`). */
1496
+ readonly result: StartProfileResult;
1497
+ /** Close the CDP connection and stop the profile. */
1498
+ stop(): Promise<void>;
1499
+ /** `await using` support — calls {@link stop}. */
1500
+ [Symbol.asyncDispose](): Promise<void>;
1501
+ }
1502
+ /** Fallback: compute what we can purely from profile runtime states. */
1503
+ declare function deriveMetricsFromProfiles(profiles: Profile[]): Metrics;
1504
+ declare class ScalebrowserClient extends ClientCore {
1505
+ listProfiles(params?: ListProfilesParams, signal?: AbortSignal): Promise<Profile[]>;
1506
+ /**
1507
+ * Every id matching `params`, unpaged — what "act on all matches" needs.
1508
+ *
1509
+ * `listProfiles` is paged, so acting on "everything" from it only ever covers the
1510
+ * page you fetched. Asking for ids costs one request and no persona payloads.
1511
+ */
1512
+ listProfileIds(params?: ListProfilesParams, signal?: AbortSignal): Promise<ProfileIdsResult>;
1513
+ getProfile(id: string, signal?: AbortSignal): Promise<Profile>;
1514
+ createProfile(body: CreateProfileBody): Promise<Profile>;
1515
+ updateProfile(id: string, body: UpdateProfileBody): Promise<Profile>;
1516
+ deleteProfile(id: string): Promise<void>;
1517
+ /** Start a profile → CDP ws endpoint. Errors: 4002/4003/4005/4006/4008. */
1518
+ startProfile(id: string, body?: StartProfileBody): Promise<StartProfileResult>;
1519
+ stopProfile(id: string): Promise<StopProfileResult>;
1520
+ bulkCreateProfiles(body: BulkCreateBody): Promise<Profile[]>;
1521
+ /**
1522
+ * Start a batch. `headless` is the ONE visibility decision for the run; omitted
1523
+ * means headless, the right default for an unattended client. The daemon stops at
1524
+ * the machine's capacity limit and reports how far it got (`started` /
1525
+ * `remaining` / `stopped_reason`) instead of failing every remaining id.
1526
+ */
1527
+ bulkStart(ids: string[], headless?: boolean): Promise<BulkStartResult>;
1528
+ bulkStop(ids: string[]): Promise<unknown>;
1529
+ bulkDelete(ids: string[]): Promise<unknown>;
1530
+ bulkAssignProxy(ids: string[], proxyId: string): Promise<unknown>;
1531
+ /**
1532
+ * Give every id exactly this set of extensions.
1533
+ *
1534
+ * It REPLACES rather than adds, the same shape as assigning a proxy: picking a
1535
+ * set for a batch states what those profiles carry. An empty list is a
1536
+ * legitimate "none". An id the library does not hold refuses the whole call.
1537
+ */
1538
+ bulkAssignExtensions(ids: string[], extRefs: string[]): Promise<unknown>;
1539
+ listGroups(signal?: AbortSignal): Promise<Group[]>;
1540
+ getGroup(id: string, signal?: AbortSignal): Promise<Group>;
1541
+ createGroup(body: CreateGroupBody): Promise<Group>;
1542
+ updateGroup(id: string, body: UpdateGroupBody): Promise<Group>;
1543
+ deleteGroup(id: string): Promise<void>;
1544
+ listPresets(signal?: AbortSignal): Promise<Preset[]>;
1545
+ getPreset(id: string, signal?: AbortSignal): Promise<Preset>;
1546
+ createPreset(body: CreatePresetBody): Promise<Preset>;
1547
+ updatePreset(id: string, body: UpdatePresetBody): Promise<Preset>;
1548
+ deletePreset(id: string): Promise<void>;
1549
+ /**
1550
+ * The values a preset's `constraints` may take. Ask rather than hardcode: the
1551
+ * region list comes from the daemon's embedded persona model, so a pinned copy
1552
+ * drifts the moment that model changes and every create 400s.
1553
+ */
1554
+ getPersonaConstraints(signal?: AbortSignal): Promise<PersonaConstraintOptions>;
1555
+ listProxies(signal?: AbortSignal): Promise<Proxy[]>;
1556
+ getProxy(id: string, signal?: AbortSignal): Promise<Proxy>;
1557
+ createProxy(body: CreateProxyBody): Promise<Proxy>;
1558
+ updateProxy(id: string, body: UpdateProxyBody): Promise<Proxy>;
1559
+ deleteProxy(id: string): Promise<void>;
1560
+ /** Health + geo check (exit-ip/country/healthy + JA4↔JA4T verdict). */
1561
+ checkProxy(id: string): Promise<ProxyCheckResult>;
1562
+ /**
1563
+ * Probe a proxy config WITHOUT persisting it — validate before you commit.
1564
+ * Pass `id` (and omit the credentials) to probe an existing proxy's stored,
1565
+ * write-only credentials in an edit form.
1566
+ */
1567
+ checkProxyConfig(body: CheckProxyConfigBody): Promise<ProxyCheckResult>;
1568
+ /** Queue a run → the status right after queueing. `409` if not running / already running. */
1569
+ startAudit(profileId: string): Promise<AuditStatus>;
1570
+ /** State plus the last stored report. */
1571
+ getAudit(profileId: string, signal?: AbortSignal): Promise<AuditStatus>;
1572
+ exportSession(profileId: string, body: SessionExportBody): Promise<SessionExportResult>;
1573
+ importSession(profileId: string, body: SessionImportBody): Promise<unknown>;
1574
+ sendInput(profileId: string, body: TrustedInputBody): Promise<unknown>;
1575
+ getMetrics(signal?: AbortSignal): Promise<Metrics>;
1576
+ /** Subscribe to lifecycle events. Returns an unsubscribe function. */
1577
+ subscribeEvents(handlers: StreamHandlers): () => void;
1578
+ /** `for await (const event of client.events())` over lifecycle events. */
1579
+ events(): AsyncIterableIterator<LifecycleEvent>;
1580
+ /**
1581
+ * Open a direct-CDP session against a started profile's `cdp_ws`. Pass the
1582
+ * `profileId` so `humanize*` helpers can route trusted input through the daemon.
1583
+ */
1584
+ connectCdp(target: StartProfileResult | string, profileId?: string): Promise<CdpSession>;
1585
+ /**
1586
+ * Start a profile, connect direct-CDP, and return a {@link LaunchHandle} whose
1587
+ * `stop()` (or `await using`) closes CDP and stops the profile.
1588
+ */
1589
+ launch(profileId: string, body?: StartProfileBody): Promise<LaunchHandle>;
1590
+ }
1591
+ interface ScalebrowserClient extends IdentityApi, RunsApi, ControlApi, ExtensionsApi {
1592
+ }
1593
+
1594
+ /**
1595
+ * `@scalebrowser/sdk` — the official Node/TypeScript SDK for the Scalebrowser
1596
+ * daemon. A typed REST client plus a direct-CDP driver plane (nodriver-style,
1597
+ * NOT Playwright — DEC-006).
1598
+ *
1599
+ * ```ts
1600
+ * import { ScalebrowserClient } from '@scalebrowser/sdk';
1601
+ *
1602
+ * const sb = new ScalebrowserClient({ baseUrl: 'http://127.0.0.1:8787', token });
1603
+ * const profile = await sb.createProfile({ name: 'acct-01' });
1604
+ * await using session = await sb.launch(profile.id, { headless: true });
1605
+ * await session.cdp.navigate('https://example.com');
1606
+ * const title = await session.cdp.evaluate<string>('document.title');
1607
+ * await session.cdp.humanizeClick(120, 240);
1608
+ * ```
1609
+ */
1610
+
1611
+ declare const VERSION = "0.2.0";
1612
+
1613
+ export { type Account, type ActivitySnapshot, type AgentRun, ApiError, type ApiErrorBody, type ArtifactBytes, type ArtifactPutResult, type AttachOptions, type AuditDetectorReport, type AuditReport, type AuditStatus, type AuditVerdict, type BindInboxBody, type CdpConnectOptions, CdpError, type CdpEvent, CdpSession, type ClickOptions, type CreateGroupBody, type CreateInboxBody, type CreatePresetBody, DEFAULT_CAPACITY, type DeleteInterruptionRuleParams, type EffectiveInterruptionLock, ErrorCode, type ErrorCodeValue, type EvaluateOptions, type FetchLike, GEO_MODES, type GeoMode, type GoalSource, type Group, HOST_MODES, type HealthStatus, type HostMode, type Inbox, type InboxBinding, type InboxBindings, type InboxChannel, type InputClient, type InterruptionChoice, type InterruptionLock, type InterruptionLockRow, type InterruptionLockView, type InterruptionRuleRow, type LaunchHandle, type ListRunsParams, type ListStepsParams, type NavigateOptions, NetworkError, type OsFamily, type OutcomeSource, PROXY_KINDS, type PasskeyRow, type PersonaConstraintOptions, type Preset, type PresetConfig, type PresetConstraints, type Profile, type ProfileActivity, type ProtectionLevel, type ProtectionStatus, type Proxy, type ProxyKind, ROTATIONS, RUNTIME_STATES, type ReadyStatus, type RevealCookiesBody, type RevealCookiesResult, type RevealedCookie, type Rotation, type RunActor, type RunOutcome, type RunStep, type RuntimeState, ScalebrowserClient, type ScalebrowserClientOptions, ScalebrowserError, type ScrollOptions, type SessionExportBody, type SessionExportResult, type SessionImportBody, type SessionKind, type SetInterruptionLockBody, type SetInterruptionRuleBody, type StepKind, type StepTarget, type StreamHandlers, type StreamParams, type StreamStatus, type TrustedInputBody, type UpdateGroupBody, type UpdateInboxBody, type UpdatePresetBody, VERSION, deriveMetricsFromProfiles, errorMessage, eventStream, openCdpSession, parseSseFrame, subscribeEvents };