effect-browserbase 0.1.0-beta.102 → 0.1.0-beta.104

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.
package/README.md CHANGED
@@ -210,6 +210,15 @@ Coordinates are CSS pixels in the main frame's viewport. Each call is one native
210
210
 
211
211
  `hover` places the pointer on one exact element where it is, by selector or by the node an observation named (`session.hoverElement`). It never scrolls to reach it, because that would hide a scripted scroll inside a native-input operation. If the pointer cannot be placed on the element (it is outside the viewport, has no area, or something covers it) the call fails `not-visible` and `undispatched`.
212
212
 
213
+ For a child-frame element, hover checks the commanded point through each ancestor
214
+ frame and then the exact node, including cross-origin documents. The receipt
215
+ still uses main-viewport CSS coordinates. An overlay or clipping ancestor refuses
216
+ input. Frame traversal is bounded at 32 levels, with bounded shadow/DOM ancestry
217
+ checks. Axis-aligned translation and positive scaling are supported; rotation,
218
+ perspective and other unsupported frame mappings are refused rather than guessed.
219
+ These reads and native input are separate operations in the browser: page script
220
+ can still change geometry between validation and dispatch.
221
+
213
222
  An `InputReceipt` carries the target it was sent to, the position this owner commanded (null until it has placed the pointer on that page), and an interval on the same host monotonic clock that stamps `CapturedFrame.receivedMonotonicNanos`. Input and pixels share one timeline, so a compositor can place the pointer on the frame that shows it. A wheel event is dispatched, not awaited: the receipt does not claim the page finished scrolling or that any frame shows it.
214
223
 
215
224
  ### Real key input
@@ -412,6 +421,34 @@ Live capture frames carry owned JPEG bytes, captured target identity, sequence n
412
421
 
413
422
  Closing, navigating, detaching a relevant frame, or resizing the captured page ends its interval explicitly without ending a sibling page's capture. `Capture.start(session, { lifetime: "page" })` instead follows a page's main frame across documents: start it before a navigation and it covers the loading in between. The native screencast is never restarted for a navigation, so a boundary is not a gap this package introduced. Each frame carries the `document` it was received during (0, then one more per navigation), and the summary's bounded `documentBoundaries` give the last sequence before each one and the address it committed, with `initialUrl` for document 0. That is attribution by receipt order, not proof of whose pixels a frame shows: one received just after a navigation can still show the document before it. Selecting another page or frame does not invalidate an unrelated interval. Handoff pause, connection loss, an uncertain owner and session closure still invalidate all child intervals. A confirmed native stop releases only its own reservation; a failed stop on a live page keeps that target quarantined. A definitively closed page releases its capture reservation. Stopping a child capture does not close its browser. The frame seam has **no website-audio source**, so this package does not synthesize silent samples or infer audio support from a video container. Caller encoding is demonstrated in `examples/record-video.ts`; the example decodes every generated frame with the caller's FFmpeg and checks presentation timestamps and pixel checksums. Native acceptance requires changing pixels and source-time agreement rather than accepting container headers as video evidence. Filming across a navigation with one page-lifetime interval, resampled onto a constant-rate reel with the address of each document reported, is demonstrated in `examples/realistic-footage`.
414
423
 
424
+ ### Read metadata while capture is running
425
+
426
+ ```ts
427
+ const interval = yield * Capture.start(session, { lifetime: "page" });
428
+ const current = yield * interval.snapshot;
429
+ // current.phase is "capturing"; initialUrl and recorded documentBoundaries are available now.
430
+ ```
431
+
432
+ `snapshot` copies the bounded metadata already recorded in host memory. It does
433
+ no browser work, consumes no frames, charges no action, and works while the page
434
+ is held. Repeated reads neither stop nor restart capture. `observedMonotonicNanos`
435
+ stamps the read; each boundary retains its own commit observation time. The
436
+ prefix holds at most 64 boundaries; `documentBoundariesTruncated` reports overflow
437
+ and `currentDocument` continues counting. Addresses longer than the existing
438
+ bound remain `null`. The model should not receive these host-only addresses by
439
+ accident merely because it can inspect the page.
440
+
441
+ While capturing, `reason` and `nativeStop` are `null`. `phase: "stopping"` means
442
+ capture has stopped accepting frames but native cleanup is pending. `"stopped"`
443
+ means that cleanup attempt settled; only `nativeStop: "confirmed"` confirms the
444
+ native stop. All snapshots are observations of accounting so far: buffered frames
445
+ can still be drained after stop. For final accounting, stop or await capture
446
+ termination, finish draining `frames`, then read `completed` to obtain the terminal
447
+ summary with final delivery counts. Preserve the terminal error and loss counters
448
+ when reconciling earlier snapshots. `late` remains included in `dropped`, and
449
+ `upstreamDrops` remains `"unknown"`; live metadata adds no stronger pixel or loss
450
+ guarantee.
451
+
415
452
  ### What a compositor is given, and what it owns
416
453
 
417
454
  Footage from `capture` is the page surface. It has no pointer, no tab strip and no address bar, so on its own it reads as the inside of a tab rather than as a browser. Drawing those is the application's, exactly as cursor artwork, easing and encoding are: this package has no window-compositing API and will not grow one. What it owes a compositor is the evidence only it can see, on one timeline:
@@ -436,6 +473,43 @@ Partial native failures fence the session as uncertain; there is no success rece
436
473
 
437
474
  ## Development and evidence
438
475
 
439
- Repository commands use Vite+: `vp run check`, `vp test`, `vp run install:test-browser`, `vp run test:native`, `vp pack`. Native tests use Playwright 1.63.0 Chromium against loopback HTTP fixtures over the real CDP boundary. Unit tests use Effect TestClock and a provider scripted through `fetch` behind the real Client. Those local boundaries remain distinct from hosted Browserbase evidence: a local CDP pass proves native integration, not provider allocation, Live View authorization, provider recording/audio behavior, recording coexistence, or Browserbase network behavior.
476
+ Repository commands use Vite+: `vp run check`, `vp test`, `vp run install:test-browser`, `vp run test:native`, `vp pack`. Native tests use Playwright 1.63.0 Chromium against loopback HTTP fixtures over the real CDP boundary. Hosted-path fixtures script provider allocation/status behind the real Client; the independent local-owner tests use no account service or provider responses. Unit tests use Effect TestClock, with real-process lifecycle tests explicitly using the live clock. These local boundaries remain distinct from hosted Browserbase evidence: a local CDP pass proves native integration, not provider allocation, Live View authorization, provider recording/audio behavior, recording coexistence, or Browserbase network behavior.
440
477
 
441
478
  `tools/packed-consumer.sh` installs the emitted npm tarballs into separate consumers with exact public dependency versions, checks NodeNext declarations, and runs the unchanged native suites without workspace aliases. It also runs real programs directly on Node and Bun: `test/consumer/resources.ts` with no Playwright and no framework installed, and `test/consumer/native.ts` against a real local Chromium.
479
+
480
+ ## Independent local Chromium
481
+
482
+ `effect-browserbase/local-browser` provides `LocalBrowser`. It uses the same modeled browser owner, native driver, exact-node observations, bootstrap bindings, capture and page control as a hosted session. It requires no Browserbase client, project, key, allocation response or provider endpoint. `BrowserSession<E>` is their shared modeled capability; `BrowserbaseSession<E>` retains the separate provider reference, artifacts, Live View, handoff and release contracts.
483
+
484
+ ```ts
485
+ import { Effect } from "effect";
486
+ import { BrowserPolicy, NavigateRequest } from "effect-browserbase/browser-data";
487
+ import { LocalBrowser } from "effect-browserbase/local-browser";
488
+
489
+ const program = Effect.scoped(
490
+ Effect.gen(function* () {
491
+ const browser = yield* LocalBrowser;
492
+ const session = yield* browser.open(BrowserPolicy.unrestricted());
493
+ yield* session.bind().navigate(NavigateRequest.make({ url: "https://example.com" }));
494
+ return yield* session.observe({ scope: "viewport" });
495
+ }),
496
+ ).pipe(
497
+ Effect.provide(
498
+ LocalBrowser.layer({
499
+ viewport: { width: 1280, height: 720 },
500
+ pageControl: true,
501
+ launch: { headless: true, chromiumSandbox: true },
502
+ }),
503
+ ),
504
+ );
505
+ ```
506
+
507
+ Layer construction validates configuration and starts nothing. The optional `playwright-core` peer is loaded only when needed. `acquire(policy, { bootstrap })` starts one owned Chromium process and registers cleanup before waiting for a connection; its cached `connect` yields one `LocalSession<E>`. `open` combines those steps. Bootstrap consumer errors and services remain in the acquisition signatures, just as for hosted sessions. `withBrowser(policy, request, use)` supervises callback failure and checks local cleanup before returning a normal result.
508
+
509
+ Owned launch currently supports POSIX hosts (Linux and macOS). It uses a fresh temporary profile, an ephemeral loopback debugger port and one maintained CDP connection. The launcher owns those arguments; callers cannot replace them through `args`. `executablePath` selects an installed Chromium explicitly; otherwise the pinned Playwright executable is used. Headless mode and Chromium sandboxing default to enabled. `chromiumSandbox: false` is an explicit host exception, never inferred from `CI`, root execution or a connection failure. Setting the option alone does not prove the operating system's sandbox configuration. Startup waiting is bounded by `startupTimeoutMillis` (15 seconds by default, at most 60 seconds) and the owner's remaining lifetime. Acquisition does not retry a failed launch.
510
+
511
+ For an externally owned Chromium, use `browser.attach(endpoint, { policy, target?, bootstrap? })`. `endpoint` is a `Redacted<string>` containing the exact `ws://127.0.0.1:PORT/devtools/browser/ID` or IPv6-loopback equivalent advertised by that browser. HTTP discovery URLs, non-loopback hosts, credentials, queries and fragments are refused. This validates the control endpoint's shape; it does not authenticate the host running it. A supplied `target.targetId` must identify an existing page. Without one, several candidate pages are an explicit ambiguity error. Attachment preserves the existing viewport and does not replay the layer's launch arguments or create a replacement browser. Coordinate any independent controllers yourself, especially when enabling page control.
512
+
513
+ Local identity is `{ provider: "local", id }`, identifying this ownership lifetime. It is not a Browserbase session reference, a PID or an attachment credential. `close` and `cleanupResult` retain `connection` and `process` facts separately. Owned cleanup fences operations, stops capture, disposes initialization, disconnects and terminates its process group; only observed termination permits removing the temporary profile. Failure or timeout remains in `issues` and leaves `process: "unknown"` when exit was not established. Borrowed cleanup disconnects its own client and reports `process: "not-owned"`; it never terminates the external process. Repeated close calls share one result. No local result has a provider `remote: "confirmed"` field. `onCleanup` receives these bounded, host-only facts even when connection setup fails after launch.
514
+
515
+ `launch.proxy: { server, bypass? }` forwards an existing host-operated proxy to Chromium. With a proxy, the default bypass value is `<-loopback>` so Chromium does not silently exclude loopback destinations; a different bypass is an explicit host choice. Additional reviewed native flags, such as disabling QUIC and non-proxied WebRTC UDP, can be supplied through `launch.args`. This module does not implement a proxy or qualify its transport/DNS coverage. Browser policy remains `Unrestricted`; a local endpoint, URL admission or successful local test never establishes whole-browser egress containment. Preserve and test the selected enforcing proxy independently. Hosted endpoint validation and network policy are unchanged.
@@ -10,7 +10,7 @@ import { A as ReadTextRequest, B as TypeRequest, E as PageInfo, G as WheelReques
10
10
  import { t as LaunchRecipe } from "./Launch-C8CfaYxb.mjs";
11
11
  import { Context, Effect, Layer, Option, Redacted, Scope } from "effect";
12
12
  declare namespace Browser_d_exports {
13
- export { AttachRequest, BoundTarget, BrowserAcquisition, BrowserOptions, BrowserbaseBrowser, BrowserbaseSession, ElementAdmission, Handoff, LiveView, NavigationOperation, OpenOptions };
13
+ export { AttachRequest, BoundTarget, BrowserAcquisition, BrowserOptions, BrowserSession, BrowserbaseBrowser, BrowserbaseSession, ElementAdmission, Handoff, LiveView, NavigationOperation, OpenOptions };
14
14
  }
15
15
  /** Host configuration. Credentials live in the Client; a model never selects these values. */
16
16
  export interface BrowserOptions extends AutomationOptions {
@@ -83,8 +83,7 @@ export interface BoundTarget {
83
83
  * Host control over one owned browser. This is not a serializable model value: copying a
84
84
  * session object cannot copy its capture, page-control or connection authority.
85
85
  */
86
- export interface BrowserbaseSession<E = never> {
87
- readonly reference: SessionReference;
86
+ export interface BrowserSession<E = never> {
88
87
  /** First fail-session callback cause, preserving the consumer's error type on the host. */
89
88
  readonly failure: Effect.Effect<never, E | InitializationError>;
90
89
  /** Bounded host-only evidence; consumer causes are never projected into a page reply. */
@@ -133,17 +132,21 @@ export interface BrowserbaseSession<E = never> {
133
132
  }) => Effect.Effect<void, BrowserError>;
134
133
  /** Navigation observation is registered before the single click dispatch. */
135
134
  readonly clickAndWait: (request: ClickRequest) => Effect.Effect<ActionResult, BrowserError>;
136
- readonly clickForDownload: (request: ClickRequest) => Effect.Effect<DownloadObservation, BrowserError>;
137
- /** Attaches to an existing file input; an uploaded branch needs a receipt for this session. */
138
- readonly selectFiles: (request: SelectFilesRequest) => Effect.Effect<ActionResult, BrowserError>;
139
- /** Registers the chooser observation before the single click that opens it. */
140
- readonly clickForFileSelection: (request: SelectFilesRequest) => Effect.Effect<ActionResult, BrowserError>;
141
135
  /**
142
136
  * Readiness of the current document only. Dependent operations wait for it themselves;
143
137
  * this reports it without charging an action, so a caller can decide what to do about a
144
138
  * document that predates the registrations.
145
139
  */
146
140
  readonly ready: Effect.Effect<ReadinessOutcome, InitializationError>;
141
+ }
142
+ /** Browserbase identity, remote artifacts and provider cleanup remain hosted capabilities. */
143
+ export interface BrowserbaseSession<E = never> extends BrowserSession<E> {
144
+ readonly reference: SessionReference;
145
+ readonly clickForDownload: (request: ClickRequest) => Effect.Effect<DownloadObservation, BrowserError>;
146
+ /** Attaches to an existing file input; an uploaded branch needs a receipt for this session. */
147
+ readonly selectFiles: (request: SelectFilesRequest) => Effect.Effect<ActionResult, BrowserError>;
148
+ /** Registers the chooser observation before the single click that opens it. */
149
+ readonly clickForFileSelection: (request: SelectFilesRequest) => Effect.Effect<ActionResult, BrowserError>;
147
150
  readonly liveView: (expiresInSeconds?: number) => Effect.Effect<LiveView, BrowserError>;
148
151
  readonly beginHandoff: (expiresInSeconds?: number) => Effect.Effect<Handoff, BrowserError>;
149
152
  readonly resume: (token: Redacted.Redacted<string>, operatorReleasedControl: boolean) => Effect.Effect<Observation, BrowserError>;
package/dist/Browser.mjs CHANGED
@@ -1,2 +1,223 @@
1
- import { n as BrowserbaseBrowser } from "./Browser-joE4Zgys.mjs";
2
- export { BrowserbaseBrowser };
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Identifier } from "./References.mjs";
3
+ import { BrowserError } from "./Errors.mjs";
4
+ import { DownloadObservation } from "./Transfers.mjs";
5
+ import { t as BrowserbaseClient } from "./Client-D1_xYM9o.mjs";
6
+ import { ActionResult, AutomationOptions, BrowserPolicy, ClickRequest, InlineFiles, Selector, Viewport } from "./BrowserData.mjs";
7
+ import { t as BrowserbaseSessions } from "./Sessions-CtbseWEh.mjs";
8
+ import { t as issuedUpload } from "./Issued-DejqoMYL.mjs";
9
+ import { d as empty } from "./Bootstrap-BXuzZojd.mjs";
10
+ import { t as BrowserbaseBrowserBinding } from "./BrowserBinding-DlJTGYZL.mjs";
11
+ import { a as decoded, c as preparePlan, i as checked, l as compileBootstrap, n as borrowedRemote, o as makeSession$1, r as ownedRemote, s as makeBindings, t as acquireSession } from "./Session-D4nT9hOO.mjs";
12
+ import { Context, Effect, Layer, Scope } from "effect";
13
+ //#region src/Browser.ts
14
+ var Browser_exports = /* @__PURE__ */ __exportAll({ BrowserbaseBrowser: () => BrowserbaseBrowser });
15
+ /**
16
+ * Uploaded selection is admitted by the identity of a receipt this package issued for this
17
+ * exact session, never by a path read off the caller's value. A fabricated receipt has no
18
+ * issuance record, so an arbitrary server pathname can never become an attached file.
19
+ */
20
+ const selection = (request, reference, operation) => Effect.suspend(() => {
21
+ const invalid = BrowserError.make({
22
+ operation,
23
+ reason: "configuration",
24
+ outcome: "undispatched"
25
+ });
26
+ if (request.selection._tag === "Inline") return checked(InlineFiles, request.selection.files, operation).pipe(Effect.map((files) => files.map((file) => ({
27
+ _tag: "Inline",
28
+ name: file.name,
29
+ mediaType: file.mediaType,
30
+ bytes: new Uint8Array(file.bytes)
31
+ }))));
32
+ const uploads = request.selection.uploads;
33
+ if (!Array.isArray(uploads) || uploads.length < 1 || uploads.length > 8) return Effect.fail(invalid);
34
+ const files = [];
35
+ for (const receipt of uploads) {
36
+ const issued = issuedUpload(receipt);
37
+ if (issued === void 0) return Effect.fail(BrowserError.make({
38
+ operation,
39
+ reason: "authorization",
40
+ outcome: "undispatched"
41
+ }));
42
+ if (issued.reference.projectId !== reference.projectId || issued.reference.sessionId !== reference.sessionId) return Effect.fail(BrowserError.make({
43
+ operation,
44
+ reason: "authorization",
45
+ outcome: "undispatched"
46
+ }));
47
+ files.push({
48
+ _tag: "Remote",
49
+ path: issued.remotePath
50
+ });
51
+ }
52
+ return Effect.succeed(files);
53
+ });
54
+ const makeSession = (controls, bindings) => {
55
+ const session = makeSession$1(controls, bindings);
56
+ const navigate = (url) => decoded(ActionResult, "action-result")({ url });
57
+ return Object.assign(session, {
58
+ reference: controls.reference,
59
+ clickForDownload: (request) => checked(ClickRequest, request, "download-action").pipe(Effect.flatMap((value) => controls.clickForDownload(value.selector)), Effect.flatMap((raw) => decoded(DownloadObservation, "download-action")({
60
+ ...raw,
61
+ reference: controls.reference
62
+ }))),
63
+ selectFiles: (request) => checked(Selector, request.selector, "select-files").pipe(Effect.flatMap((selector) => selection(request, controls.reference, "select-files").pipe(Effect.flatMap((files) => controls.selectFiles(selector, files)))), Effect.flatMap(navigate)),
64
+ clickForFileSelection: (request) => checked(Selector, request.selector, "file-chooser").pipe(Effect.flatMap((selector) => selection(request, controls.reference, "file-chooser").pipe(Effect.flatMap((files) => controls.clickForFileSelection(selector, files)))), Effect.flatMap(navigate)),
65
+ liveView: (ttl = 60) => controls.liveView(ttl),
66
+ beginHandoff: (ttl = 60) => controls.beginHandoff(ttl),
67
+ resume: (token, released) => controls.resume(token, released),
68
+ detach: controls.detach,
69
+ reconnect: (released) => controls.reconnect(released),
70
+ close: controls.close,
71
+ cleanupResult: controls.cleanupResult
72
+ });
73
+ };
74
+ /**
75
+ * Credentials, budgets and connection lifetime are fixed when this Layer is built. Building it
76
+ * allocates nothing and loads no native peer; every acquisition is scoped by its caller.
77
+ */
78
+ var BrowserbaseBrowser = class BrowserbaseBrowser extends Context.Service()("effect-browserbase/Browser") {
79
+ static layer(options) {
80
+ return Layer.effect(BrowserbaseBrowser, Effect.gen(function* () {
81
+ const client = yield* BrowserbaseClient;
82
+ const sessions = yield* BrowserbaseSessions;
83
+ const binding = yield* BrowserbaseBrowserBinding;
84
+ const automation = yield* checked(AutomationOptions, projected(options), "configure");
85
+ const viewport = yield* checked(Viewport, options.launch.viewport._tag === "Fixed" ? {
86
+ width: options.launch.viewport.width,
87
+ height: options.launch.viewport.height
88
+ } : {
89
+ width: 1280,
90
+ height: 720
91
+ }, "configure");
92
+ const actionTimeoutMillis = automation.actionTimeoutMillis ?? 1e4;
93
+ const maxPages = automation.maxPages ?? 10;
94
+ const pageControl = automation.pageControl ?? false;
95
+ const popupPolicy = automation.popupPolicy ?? "retain";
96
+ const dialogPolicy = automation.dialogPolicy ?? "dismiss";
97
+ if (pageControl && (options.launch.keepAlive === true || popupPolicy === "pause" || dialogPolicy === "pause")) return yield* BrowserError.make({
98
+ operation: "configure",
99
+ reason: "unsupported",
100
+ outcome: "undispatched"
101
+ });
102
+ if (options.launch.context?.persist === true && options.contextWriter === void 0) return yield* BrowserError.make({
103
+ operation: "configure",
104
+ reason: "context-lease"
105
+ });
106
+ if (Object.prototype.hasOwnProperty.call(options, "bootstrap")) return yield* BrowserError.make({
107
+ operation: "configure",
108
+ reason: "configuration",
109
+ outcome: "undispatched"
110
+ });
111
+ const driver = {
112
+ pageControl,
113
+ viewport,
114
+ maxPages,
115
+ popupPolicy,
116
+ dialogPolicy,
117
+ preserveViewport: options.launch.viewport._tag !== "Fixed",
118
+ ...automation.initialPage === void 0 ? {} : "targetId" in automation.initialPage ? { initialTargetId: automation.initialPage.targetId } : { newPage: true }
119
+ };
120
+ const acquire = Effect.fnUntraced(function* (policy, request = {}) {
121
+ const fixed = yield* checked(BrowserPolicy, policy, "configure");
122
+ const plan = yield* preparePlan(request.bootstrap ?? empty);
123
+ const scope = yield* Scope.fork(yield* Scope.Scope, "sequential");
124
+ const bindings = yield* makeBindings(plan).pipe(Scope.provide(scope));
125
+ const bootstrap = compileBootstrap(plan);
126
+ const acquired = yield* acquireSession({
127
+ maxActions: fixed.maxActions,
128
+ maxElapsedMillis: fixed.maxElapsedMillis,
129
+ actionTimeoutMillis
130
+ }, {
131
+ remote: ownedRemote({
132
+ launch: options.launch,
133
+ ...options.contextWriter === void 0 ? {} : { contextWriter: options.contextWriter },
134
+ ...options.onCleanup === void 0 ? {} : { onCleanup: options.onCleanup },
135
+ ...options.onAllocationUncertain === void 0 ? {} : { onAllocationUncertain: options.onAllocationUncertain }
136
+ }),
137
+ keepAlive: options.launch.keepAlive === true,
138
+ driver: {
139
+ ...driver,
140
+ ...bootstrap === void 0 ? {} : { bootstrap }
141
+ },
142
+ connectBindings: bindings.connect,
143
+ maxReturnedBytes: fixed.maxReturnedBytes
144
+ }).pipe(Effect.provideService(BrowserbaseClient, client), Effect.provideService(BrowserbaseSessions, sessions), Effect.provideService(BrowserbaseBrowserBinding, binding), Scope.provide(scope));
145
+ const connected = yield* Effect.cached(acquired.connect.pipe(Effect.map((controls) => makeSession(controls, bindings))));
146
+ return {
147
+ reference: acquired.reference,
148
+ attempt: acquired.lease.attempt,
149
+ failure: bindings.failure,
150
+ close: acquired.close,
151
+ connect: Effect.raceFirst(bindings.failure, acquired.connect.pipe(Effect.andThen(connected))).pipe(Effect.onError(() => acquired.close.pipe(Effect.asVoid)))
152
+ };
153
+ });
154
+ /**
155
+ * Borrowing a session another process allocated. The launch recipe is not replayed and
156
+ * no allocation is attempted: the requested target is resolved explicitly, credentials
157
+ * are fetched fresh because expiry races the caller's own status read, and this scope
158
+ * disconnects locally without ever requesting release. Detaching and reattaching inside
159
+ * a borrowed scope is deliberately absent; attaching again is the cross-process path,
160
+ * and it revalidates the session instead of assuming it is still there.
161
+ */
162
+ const attach = Effect.fnUntraced(function* (reference, request) {
163
+ const fixed = yield* checked(BrowserPolicy, request.policy, "configure");
164
+ const plan = yield* preparePlan(request.bootstrap ?? empty);
165
+ const scope = yield* Scope.fork(yield* Scope.Scope, "sequential");
166
+ const bindings = yield* makeBindings(plan).pipe(Scope.provide(scope));
167
+ const bootstrap = compileBootstrap(plan);
168
+ const targetId = request.target === void 0 ? void 0 : yield* checked(Identifier, request.target.targetId, "configure");
169
+ const acquired = yield* acquireSession({
170
+ maxActions: fixed.maxActions,
171
+ maxElapsedMillis: fixed.maxElapsedMillis,
172
+ actionTimeoutMillis
173
+ }, {
174
+ remote: borrowedRemote({
175
+ reference,
176
+ ...request.pendingWaitMillis === void 0 ? {} : { pendingWaitMillis: request.pendingWaitMillis },
177
+ ...options.onCleanup === void 0 ? {} : { onCleanup: options.onCleanup }
178
+ }),
179
+ keepAlive: false,
180
+ driver: {
181
+ ...driver,
182
+ ...bootstrap === void 0 ? {} : { bootstrap },
183
+ newPage: false,
184
+ preserveViewport: true,
185
+ ...targetId === void 0 ? {} : { initialTargetId: targetId }
186
+ },
187
+ maxReturnedBytes: fixed.maxReturnedBytes,
188
+ connectBindings: bindings.connect
189
+ }).pipe(Effect.provideService(BrowserbaseClient, client), Effect.provideService(BrowserbaseSessions, sessions), Effect.provideService(BrowserbaseBrowserBinding, binding), Scope.provide(scope));
190
+ return yield* Effect.raceFirst(bindings.failure, acquired.connect.pipe(Effect.map((controls) => makeSession(controls, bindings)))).pipe(Effect.onError(() => acquired.close.pipe(Effect.asVoid)));
191
+ });
192
+ return BrowserbaseBrowser.of({
193
+ acquire,
194
+ attach,
195
+ open: (policy, request) => acquire(policy, request).pipe(Effect.flatMap((acquired) => acquired.connect)),
196
+ withBrowser: (policy, request, use) => Effect.scoped(Effect.gen(function* () {
197
+ const session = yield* (yield* acquire(policy, request)).connect;
198
+ const result = yield* Effect.raceFirst(session.failure, Effect.suspend(() => use(session)));
199
+ const cleanup = yield* session.close;
200
+ if (cleanup.remote !== "confirmed" || cleanup.local !== "closed" || cleanup.issues.length > 0) return yield* BrowserError.make({
201
+ operation: "close",
202
+ reason: "provider",
203
+ outcome: "unknown"
204
+ });
205
+ return result;
206
+ }))
207
+ });
208
+ }));
209
+ }
210
+ };
211
+ /** Only the automation fields cross the schema boundary; launch and callbacks are separate. */
212
+ const projected = (options) => ({
213
+ ...options.actionTimeoutMillis === void 0 ? {} : { actionTimeoutMillis: options.actionTimeoutMillis },
214
+ ...options.maxPages === void 0 ? {} : { maxPages: options.maxPages },
215
+ ...options.initialPage === void 0 ? {} : { initialPage: options.initialPage },
216
+ ...options.popupPolicy === void 0 ? {} : { popupPolicy: options.popupPolicy },
217
+ ...options.dialogPolicy === void 0 ? {} : { dialogPolicy: options.dialogPolicy },
218
+ ...options.pageControl === void 0 ? {} : { pageControl: options.pageControl }
219
+ });
220
+ //#endregion
221
+ export { BrowserbaseBrowser, Browser_exports as t };
222
+
223
+ //# sourceMappingURL=Browser.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Browser.mjs","names":["makeBrowserSession","Bootstrap.empty"],"sources":["../src/Browser.ts"],"sourcesContent":["import { Context, Effect, Layer, type Option, type Redacted, Scope } from \"effect\";\n\nimport * as Bootstrap from \"./Bootstrap.ts\";\nimport { BrowserbaseBrowserBinding } from \"./BrowserBinding.ts\";\nimport type {\n Checkpoint,\n CheckpointOptions,\n FillRequest,\n HoverRequest,\n InputReceipt,\n KeyStroke,\n NavigateRequest,\n NavigationResult,\n ObservationOptions,\n ObservedElement,\n PointerMoveRequest,\n PressRequest,\n ReadTextRequest,\n ScreenshotRequest,\n ScreenshotResult,\n ScrollRequest,\n StartNavigationRequest,\n TextResult,\n TypeRequest,\n WheelRequest,\n} from \"./BrowserData.ts\";\nimport {\n type ControlFacts,\n ActionResult,\n AutomationOptions,\n BrowserPolicy,\n ClickRequest,\n type FrameInfo,\n InlineFiles,\n type Observation,\n type PageInfo,\n type SelectFilesRequest,\n Selector,\n type Target,\n Viewport,\n} from \"./BrowserData.ts\";\nimport type { CleanupResult } from \"./Cleanup.ts\";\nimport { BrowserbaseClient } from \"./Client.ts\";\nimport type { InitializationError } from \"./Errors.ts\";\nimport {\n type AllocationError,\n BrowserError,\n type BrowserOperation,\n type ContextError,\n type SessionError,\n} from \"./Errors.ts\";\nimport { makeBindings, preparePlan, type Bindings } from \"./internal/browser/Bindings.ts\";\nimport { compileBootstrap } from \"./internal/browser/Bootstrap.ts\";\nimport type { NativeFileSelection } from \"./internal/browser/Driver.ts\";\nimport type { LiveView } from \"./internal/browser/LiveView.ts\";\nimport {\n checked,\n decoded,\n makeSession as makeBrowserSession,\n} from \"./internal/browser/PublicSession.ts\";\nimport {\n acquireSession,\n borrowedRemote,\n ownedRemote,\n type SessionControls,\n} from \"./internal/browser/Session.ts\";\nimport type { ContextWriterPermit } from \"./internal/session/WriterFacts.ts\";\nimport { issuedUpload } from \"./internal/upload/Issued.ts\";\nimport type { LaunchRecipe } from \"./Launch.ts\";\nimport { Identifier, type AllocationAttempt, type SessionReference } from \"./References.ts\";\nimport { BrowserbaseSessions } from \"./Sessions.ts\";\nimport { DownloadObservation } from \"./Transfers.ts\";\n\nexport type { LiveView } from \"./internal/browser/LiveView.ts\";\n\n/** Host configuration. Credentials live in the Client; a model never selects these values. */\nexport interface BrowserOptions extends AutomationOptions {\n readonly launch: LaunchRecipe;\n /** Required whenever the launch recipe persists a context. */\n readonly contextWriter?: ContextWriterPermit;\n readonly onCleanup?: (result: CleanupResult) => Effect.Effect<void>;\n readonly onAllocationUncertain?: (attempt: AllocationAttempt) => Effect.Effect<void>;\n}\n\nexport interface Handoff {\n readonly token: Redacted.Redacted<string>;\n readonly view: LiveView;\n}\n\n/**\n * A host's decision about one control, made on facts read from the exact node immediately\n * before input. Returning anything but `true`, or throwing, sends nothing and fails `denied`.\n * It is a plain synchronous function on purpose: it runs while the owner's permit is held, where\n * waiting on a model or a network call would stall every other operation. It is not an atomic\n * check-and-input transaction, because page script can still run before the native input lands.\n */\nexport interface ElementAdmission {\n readonly admit: (facts: ControlFacts) => boolean;\n}\n\n/**\n * A navigation the browser is still performing. The owner's permit was released when it was\n * dispatched, so while it loads a host may read, `checkpoint`, hold and resume this page, and\n * use any other page. Anything that would change this page fails `busy` until it settles.\n *\n * Leaving its scope unsettled fences the session, exactly as an interrupted mutation does,\n * because nothing then knows what the browser did. It is never replayed.\n */\nexport interface NavigationOperation {\n /** What was navigated, read before dispatch. */\n readonly target: Target;\n /**\n * The document reached DOMContentLoaded. It belongs to this one navigation: a successor that\n * reaches the same URL fails it instead. Interrupting a waiter stops nothing in the browser.\n */\n readonly completed: Effect.Effect<NavigationResult, BrowserError>;\n /**\n * Asks the browser to stop loading and waits for this navigation to settle, after which\n * `completed` fails `interrupted`. Success is a known outcome and the session stays usable:\n * the page holds whatever had loaded. It does not undo anything the page already did.\n */\n readonly stop: Effect.Effect<void, BrowserError>;\n}\n\n/** One selected page and frame at one connection generation, never the current DOM. */\nexport interface BoundTarget {\n readonly navigate: (request: NavigateRequest) => Effect.Effect<NavigationResult, BrowserError>;\n /** `navigate`, left in flight: the same single dispatch, completed by the caller. */\n readonly startNavigation: (\n request: StartNavigationRequest,\n ) => Effect.Effect<NavigationOperation, BrowserError, Scope.Scope>;\n readonly readText: (request: ReadTextRequest) => Effect.Effect<TextResult, BrowserError>;\n readonly click: (request: ClickRequest) => Effect.Effect<ActionResult, BrowserError>;\n readonly fill: (request: FillRequest) => Effect.Effect<ActionResult, BrowserError>;\n /** Script in the page: instantaneous, and it raises no wheel event. */\n readonly scroll: (request: ScrollRequest) => Effect.Effect<ActionResult, BrowserError>;\n /** One real pointer move, in main-frame viewport pixels. */\n readonly pointerMove: (request: PointerMoveRequest) => Effect.Effect<InputReceipt, BrowserError>;\n /** Places the pointer on one exact element where it is, or fails `not-visible` unsent. */\n readonly hover: (request: HoverRequest) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real wheel event; the browser decides what under the pointer scrolls. */\n readonly wheel: (request: WheelRequest) => Effect.Effect<InputReceipt, BrowserError>;\n /** One real key stroke to whatever has focus, or fails `not-focused` unsent if `into` lacks it. */\n readonly press: (request: PressRequest) => Effect.Effect<InputReceipt, BrowserError>;\n /** Text as the real key strokes that produce it, under the same focus rule as `press`. */\n readonly type: (request: TypeRequest) => Effect.Effect<InputReceipt, BrowserError>;\n readonly screenshot: (\n request: ScreenshotRequest,\n ) => Effect.Effect<ScreenshotResult, BrowserError>;\n}\n\n/**\n * Host control over one owned browser. This is not a serializable model value: copying a\n * session object cannot copy its capture, page-control or connection authority.\n */\nexport interface BrowserSession<E = never> {\n /** First fail-session callback cause, preserving the consumer's error type on the host. */\n readonly failure: Effect.Effect<never, E | InitializationError>;\n /** Bounded host-only evidence; consumer causes are never projected into a page reply. */\n readonly bindingDiagnostics: Effect.Effect<Bootstrap.BindingDiagnostics<E>>;\n readonly bind: () => BoundTarget;\n /** Re-reads the live selection first, so a stale generation fails before any dispatch. */\n readonly currentTarget: Effect.Effect<BoundTarget, BrowserError>;\n readonly target: Effect.Effect<Target, BrowserError>;\n /**\n * The one observation whose nodes later actions may name. `scope: \"viewport\"` keeps only text\n * and controls that are on screen and reachable; the default reads the whole document. It is\n * safe to show a model: it carries no destination, form or field value.\n */\n readonly observe: (options?: ObservationOptions) => Effect.Effect<Observation, BrowserError>;\n /**\n * Passive evidence for a recorder, with a picture when asked. It issues no references and\n * leaves the observation above exactly as it was. Host-only: it carries control facts.\n */\n readonly checkpoint: (options?: CheckpointOptions) => Effect.Effect<Checkpoint, BrowserError>;\n /** Host-only facts about one observed control, read from that exact node just now. */\n readonly controlFacts: (reference: ObservedElement) => Effect.Effect<ControlFacts, BrowserError>;\n /**\n * After a page hold, nothing observed on that page may be acted on unchecked. This checks one\n * reference: still attached, and still the control that was inspected. It never searches by\n * selector or label for a substitute, and it sends nothing.\n */\n readonly revalidateElement: (\n reference: ObservedElement,\n ) => Effect.Effect<ObservedElement, BrowserError>;\n readonly clickElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly fillElement: (\n reference: ObservedElement,\n value: string,\n admission?: ElementAdmission,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly hoverElement: (\n reference: ObservedElement,\n admission?: ElementAdmission,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** A key stroke sent only if the exact node an observation named already has focus. */\n readonly pressElement: (\n reference: ObservedElement,\n stroke: KeyStroke,\n admission?: ElementAdmission,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n /** Real typing with `fillElement`'s exactness: that node must already have focus. */\n readonly typeElement: (\n reference: ObservedElement,\n text: string,\n admission?: ElementAdmission,\n ) => Effect.Effect<InputReceipt, BrowserError>;\n readonly pages: Effect.Effect<ReadonlyArray<PageInfo>, BrowserError>;\n readonly frames: Effect.Effect<ReadonlyArray<FrameInfo>, BrowserError>;\n readonly selectPage: (pageId: string) => Effect.Effect<BoundTarget, BrowserError>;\n readonly selectFrame: (frameId: string) => Effect.Effect<BoundTarget, BrowserError>;\n /** Create a tab without selecting it. */\n readonly createPage: Effect.Effect<string, BrowserError>;\n readonly closePage: (pageId: string) => Effect.Effect<void, BrowserError>;\n readonly resizeViewport: (viewport: Viewport) => Effect.Effect<void, BrowserError>;\n readonly waitFor: (request: {\n readonly selector: string;\n readonly state: \"visible\" | \"hidden\" | \"attached\" | \"detached\";\n }) => Effect.Effect<void, BrowserError>;\n /** Navigation observation is registered before the single click dispatch. */\n readonly clickAndWait: (request: ClickRequest) => Effect.Effect<ActionResult, BrowserError>;\n /**\n * Readiness of the current document only. Dependent operations wait for it themselves;\n * this reports it without charging an action, so a caller can decide what to do about a\n * document that predates the registrations.\n */\n readonly ready: Effect.Effect<Bootstrap.ReadinessOutcome, InitializationError>;\n}\n\n/** Browserbase identity, remote artifacts and provider cleanup remain hosted capabilities. */\nexport interface BrowserbaseSession<E = never> extends BrowserSession<E> {\n readonly reference: SessionReference;\n readonly clickForDownload: (\n request: ClickRequest,\n ) => Effect.Effect<DownloadObservation, BrowserError>;\n /** Attaches to an existing file input; an uploaded branch needs a receipt for this session. */\n readonly selectFiles: (request: SelectFilesRequest) => Effect.Effect<ActionResult, BrowserError>;\n /** Registers the chooser observation before the single click that opens it. */\n readonly clickForFileSelection: (\n request: SelectFilesRequest,\n ) => Effect.Effect<ActionResult, BrowserError>;\n readonly liveView: (expiresInSeconds?: number) => Effect.Effect<LiveView, BrowserError>;\n readonly beginHandoff: (expiresInSeconds?: number) => Effect.Effect<Handoff, BrowserError>;\n readonly resume: (\n token: Redacted.Redacted<string>,\n operatorReleasedControl: boolean,\n ) => Effect.Effect<Observation, BrowserError>;\n readonly detach: Effect.Effect<\n { readonly reference: SessionReference; readonly targetId: string },\n BrowserError\n >;\n readonly reconnect: (\n operatorReleasedControl: boolean,\n ) => Effect.Effect<Observation, BrowserError | InitializationError>;\n readonly close: Effect.Effect<CleanupResult, BrowserError>;\n readonly cleanupResult: Effect.Effect<Option.Option<CleanupResult>>;\n}\n\n/** Dependencies are captured at acquisition, not erased into an environment-free service Layer. */\nexport interface OpenOptions<E = never, R = never> {\n readonly bootstrap?: Bootstrap.Plan<E, R>;\n}\n\nexport interface AttachRequest<E = never, R = never> extends OpenOptions<E, R> {\n readonly policy: BrowserPolicy;\n /** The exact page to resume; without it the session must have exactly one page. */\n readonly target?: { readonly targetId: string };\n /** Bounded admission of a session the provider has not finished starting. */\n readonly pendingWaitMillis?: number;\n}\n\nexport interface BrowserAcquisition<E = never> {\n readonly reference: SessionReference;\n readonly attempt: AllocationAttempt;\n readonly failure: Effect.Effect<never, E | InitializationError>;\n readonly connect: Effect.Effect<BrowserbaseSession<E>, BrowserError | E | InitializationError>;\n readonly close: Effect.Effect<CleanupResult, BrowserError>;\n}\n\n/**\n * Uploaded selection is admitted by the identity of a receipt this package issued for this\n * exact session, never by a path read off the caller's value. A fabricated receipt has no\n * issuance record, so an arbitrary server pathname can never become an attached file.\n */\nconst selection = (\n request: SelectFilesRequest,\n reference: SessionReference,\n operation: BrowserOperation,\n): Effect.Effect<ReadonlyArray<NativeFileSelection>, BrowserError> =>\n Effect.suspend(() => {\n const invalid = BrowserError.make({\n operation,\n reason: \"configuration\",\n outcome: \"undispatched\",\n });\n\n if (request.selection._tag === \"Inline\") {\n return checked(InlineFiles, request.selection.files, operation).pipe(\n Effect.map((files) =>\n files.map((file) => ({\n _tag: \"Inline\" as const,\n name: file.name,\n mediaType: file.mediaType,\n // The dispatched bytes cannot change after they were validated.\n bytes: new Uint8Array(file.bytes),\n })),\n ),\n );\n }\n\n const uploads = request.selection.uploads;\n\n if (!Array.isArray(uploads) || uploads.length < 1 || uploads.length > 8)\n return Effect.fail(invalid);\n const files: NativeFileSelection[] = [];\n\n for (const receipt of uploads) {\n const issued = issuedUpload(receipt);\n\n if (issued === undefined)\n return Effect.fail(\n BrowserError.make({ operation, reason: \"authorization\", outcome: \"undispatched\" }),\n );\n if (\n issued.reference.projectId !== reference.projectId ||\n issued.reference.sessionId !== reference.sessionId\n )\n return Effect.fail(\n BrowserError.make({ operation, reason: \"authorization\", outcome: \"undispatched\" }),\n );\n files.push({ _tag: \"Remote\", path: issued.remotePath });\n }\n\n return Effect.succeed(files);\n });\n\nconst makeSession = <E>(\n controls: SessionControls,\n bindings: Bindings<E>,\n): BrowserbaseSession<E> => {\n const session = makeBrowserSession(controls, bindings);\n const navigate = (url: string) => decoded(ActionResult, \"action-result\")({ url });\n\n return Object.assign(session, {\n reference: controls.reference,\n clickForDownload: (request) =>\n checked(ClickRequest, request, \"download-action\").pipe(\n Effect.flatMap((value) => controls.clickForDownload(value.selector)),\n Effect.flatMap((raw) =>\n decoded(\n DownloadObservation,\n \"download-action\",\n )({ ...raw, reference: controls.reference }),\n ),\n ),\n selectFiles: (request) =>\n checked(Selector, request.selector, \"select-files\").pipe(\n Effect.flatMap((selector) =>\n selection(request, controls.reference, \"select-files\").pipe(\n Effect.flatMap((files) => controls.selectFiles(selector, files)),\n ),\n ),\n Effect.flatMap(navigate),\n ),\n clickForFileSelection: (request) =>\n checked(Selector, request.selector, \"file-chooser\").pipe(\n Effect.flatMap((selector) =>\n selection(request, controls.reference, \"file-chooser\").pipe(\n Effect.flatMap((files) => controls.clickForFileSelection(selector, files)),\n ),\n ),\n Effect.flatMap(navigate),\n ),\n liveView: (ttl = 60) => controls.liveView(ttl),\n beginHandoff: (ttl = 60) => controls.beginHandoff(ttl),\n resume: (token, released) => controls.resume(token, released),\n detach: controls.detach,\n reconnect: (released) => controls.reconnect(released),\n close: controls.close,\n cleanupResult: controls.cleanupResult,\n } satisfies Omit<BrowserbaseSession<E>, keyof BrowserSession<E>>);\n};\n\n/**\n * Credentials, budgets and connection lifetime are fixed when this Layer is built. Building it\n * allocates nothing and loads no native peer; every acquisition is scoped by its caller.\n */\nexport class BrowserbaseBrowser extends Context.Service<\n BrowserbaseBrowser,\n {\n readonly acquire: <E = never, R = never>(\n policy: BrowserPolicy,\n request?: OpenOptions<E, R>,\n ) => Effect.Effect<\n BrowserAcquisition<E>,\n AllocationError | BrowserError | ContextError,\n Scope.Scope | Exclude<R, Scope.Scope>\n >;\n /** Borrowed control of a session this process did not allocate and will not release. */\n readonly attach: <E = never, R = never>(\n reference: SessionReference,\n request: AttachRequest<E, R>,\n ) => Effect.Effect<\n BrowserbaseSession<E>,\n BrowserError | SessionError | E | InitializationError,\n Scope.Scope | Exclude<R, Scope.Scope>\n >;\n readonly open: <E = never, R = never>(\n policy: BrowserPolicy,\n request?: OpenOptions<E, R>,\n ) => Effect.Effect<\n BrowserbaseSession<E>,\n AllocationError | BrowserError | ContextError | E | InitializationError,\n Scope.Scope | Exclude<R, Scope.Scope>\n >;\n /** Race the consumer with typed callback failure and confirm cleanup before normal success. */\n readonly withBrowser: <E, R, A, E2, R2>(\n policy: BrowserPolicy,\n request: OpenOptions<E, R>,\n use: (session: BrowserbaseSession<E>) => Effect.Effect<A, E2, R2>,\n ) => Effect.Effect<\n A,\n AllocationError | BrowserError | ContextError | InitializationError | E | E2,\n Exclude<R | R2, Scope.Scope>\n >;\n }\n>()(\"effect-browserbase/Browser\") {\n static layer(\n options: BrowserOptions,\n ): Layer.Layer<BrowserbaseBrowser, BrowserError, BrowserbaseClient | BrowserbaseSessions> {\n return Layer.effect(\n BrowserbaseBrowser,\n Effect.gen(function* () {\n const client = yield* BrowserbaseClient;\n const sessions = yield* BrowserbaseSessions;\n // The engine is fixed where the Layer is built, like the account it connects for.\n const binding = yield* BrowserbaseBrowserBinding;\n const automation = yield* checked(AutomationOptions, projected(options), \"configure\");\n\n const viewport = yield* checked(\n Viewport,\n options.launch.viewport._tag === \"Fixed\"\n ? { width: options.launch.viewport.width, height: options.launch.viewport.height }\n : { width: 1280, height: 720 },\n \"configure\",\n );\n\n const actionTimeoutMillis = automation.actionTimeoutMillis ?? 10_000;\n const maxPages = automation.maxPages ?? 10;\n const pageControl = automation.pageControl ?? false;\n const popupPolicy = automation.popupPolicy ?? \"retain\";\n const dialogPolicy = automation.dialogPolicy ?? \"dismiss\";\n\n if (\n pageControl &&\n (options.launch.keepAlive === true || popupPolicy === \"pause\" || dialogPolicy === \"pause\")\n )\n return yield* BrowserError.make({\n operation: \"configure\",\n reason: \"unsupported\",\n outcome: \"undispatched\",\n });\n\n if (options.launch.context?.persist === true && options.contextWriter === undefined)\n return yield* BrowserError.make({ operation: \"configure\", reason: \"context-lease\" });\n\n // A retired layer-level bootstrap must not be silently discarded for JavaScript callers.\n if (Object.prototype.hasOwnProperty.call(options, \"bootstrap\"))\n return yield* BrowserError.make({\n operation: \"configure\",\n reason: \"configuration\",\n outcome: \"undispatched\",\n });\n\n const driver = {\n pageControl,\n viewport,\n maxPages,\n popupPolicy,\n dialogPolicy,\n preserveViewport: options.launch.viewport._tag !== \"Fixed\",\n ...(automation.initialPage === undefined\n ? {}\n : \"targetId\" in automation.initialPage\n ? { initialTargetId: automation.initialPage.targetId }\n : { newPage: true }),\n };\n\n const acquire = Effect.fnUntraced(function* <E = never, R = never>(\n policy: BrowserPolicy,\n request: OpenOptions<E, R> = {},\n ) {\n const fixed = yield* checked(BrowserPolicy, policy, \"configure\");\n const plan = yield* preparePlan<E, R>(request.bootstrap ?? Bootstrap.empty);\n // The sequential child keeps remote cleanup ahead of callback-scope finalization even\n // when a consumer deliberately gives its outer Scope parallel finalizers.\n const scope = yield* Scope.fork(yield* Scope.Scope, \"sequential\");\n const bindings = yield* makeBindings(plan).pipe(Scope.provide(scope));\n const bootstrap = compileBootstrap(plan);\n\n const acquired = yield* acquireSession(\n {\n maxActions: fixed.maxActions,\n maxElapsedMillis: fixed.maxElapsedMillis,\n actionTimeoutMillis,\n },\n {\n remote: ownedRemote({\n launch: options.launch,\n ...(options.contextWriter === undefined\n ? {}\n : { contextWriter: options.contextWriter }),\n ...(options.onCleanup === undefined ? {} : { onCleanup: options.onCleanup }),\n ...(options.onAllocationUncertain === undefined\n ? {}\n : { onAllocationUncertain: options.onAllocationUncertain }),\n }),\n keepAlive: options.launch.keepAlive === true,\n driver: { ...driver, ...(bootstrap === undefined ? {} : { bootstrap }) },\n connectBindings: bindings.connect,\n maxReturnedBytes: fixed.maxReturnedBytes,\n },\n ).pipe(\n // The Layer owns the one account and resource service; acquisition never re-resolves them.\n Effect.provideService(BrowserbaseClient, client),\n Effect.provideService(BrowserbaseSessions, sessions),\n Effect.provideService(BrowserbaseBrowserBinding, binding),\n Scope.provide(scope),\n );\n\n // One public session object is shared by repeated connect calls in this scope.\n const connected = yield* Effect.cached(\n acquired.connect.pipe(Effect.map((controls) => makeSession(controls, bindings))),\n );\n\n return {\n reference: acquired.reference,\n attempt: acquired.lease.attempt,\n failure: bindings.failure,\n close: acquired.close,\n connect: Effect.raceFirst(\n bindings.failure,\n acquired.connect.pipe(Effect.andThen(connected)),\n ).pipe(Effect.onError(() => acquired.close.pipe(Effect.asVoid))),\n } satisfies BrowserAcquisition<E>;\n });\n\n /**\n * Borrowing a session another process allocated. The launch recipe is not replayed and\n * no allocation is attempted: the requested target is resolved explicitly, credentials\n * are fetched fresh because expiry races the caller's own status read, and this scope\n * disconnects locally without ever requesting release. Detaching and reattaching inside\n * a borrowed scope is deliberately absent; attaching again is the cross-process path,\n * and it revalidates the session instead of assuming it is still there.\n */\n const attach = Effect.fnUntraced(function* <E = never, R = never>(\n reference: SessionReference,\n request: AttachRequest<E, R>,\n ) {\n const fixed = yield* checked(BrowserPolicy, request.policy, \"configure\");\n const plan = yield* preparePlan<E, R>(request.bootstrap ?? Bootstrap.empty);\n const scope = yield* Scope.fork(yield* Scope.Scope, \"sequential\");\n const bindings = yield* makeBindings(plan).pipe(Scope.provide(scope));\n const bootstrap = compileBootstrap(plan);\n\n const targetId =\n request.target === undefined\n ? undefined\n : yield* checked(Identifier, request.target.targetId, \"configure\");\n\n const acquired = yield* acquireSession(\n {\n maxActions: fixed.maxActions,\n maxElapsedMillis: fixed.maxElapsedMillis,\n actionTimeoutMillis,\n },\n {\n remote: borrowedRemote({\n reference,\n ...(request.pendingWaitMillis === undefined\n ? {}\n : { pendingWaitMillis: request.pendingWaitMillis }),\n ...(options.onCleanup === undefined ? {} : { onCleanup: options.onCleanup }),\n }),\n keepAlive: false,\n driver: {\n ...driver,\n ...(bootstrap === undefined ? {} : { bootstrap }),\n newPage: false,\n preserveViewport: true,\n ...(targetId === undefined ? {} : { initialTargetId: targetId }),\n },\n maxReturnedBytes: fixed.maxReturnedBytes,\n connectBindings: bindings.connect,\n },\n ).pipe(\n Effect.provideService(BrowserbaseClient, client),\n Effect.provideService(BrowserbaseSessions, sessions),\n Effect.provideService(BrowserbaseBrowserBinding, binding),\n Scope.provide(scope),\n );\n\n return yield* Effect.raceFirst(\n bindings.failure,\n acquired.connect.pipe(Effect.map((controls) => makeSession(controls, bindings))),\n ).pipe(Effect.onError(() => acquired.close.pipe(Effect.asVoid)));\n });\n\n return BrowserbaseBrowser.of({\n acquire,\n attach,\n open: (policy, request) =>\n acquire(policy, request).pipe(Effect.flatMap((acquired) => acquired.connect)),\n withBrowser: (policy, request, use) =>\n Effect.scoped(\n Effect.gen(function* () {\n const acquired = yield* acquire(policy, request);\n const session = yield* acquired.connect;\n\n const result = yield* Effect.raceFirst(\n session.failure,\n Effect.suspend(() => use(session)),\n );\n\n const cleanup = yield* session.close;\n\n if (\n cleanup.remote !== \"confirmed\" ||\n cleanup.local !== \"closed\" ||\n cleanup.issues.length > 0\n )\n return yield* BrowserError.make({\n operation: \"close\",\n reason: \"provider\",\n outcome: \"unknown\",\n });\n\n return result;\n }),\n ),\n });\n }),\n );\n }\n}\n\n/** Only the automation fields cross the schema boundary; launch and callbacks are separate. */\nconst projected = (options: BrowserOptions) => ({\n ...(options.actionTimeoutMillis === undefined\n ? {}\n : { actionTimeoutMillis: options.actionTimeoutMillis }),\n ...(options.maxPages === undefined ? {} : { maxPages: options.maxPages }),\n ...(options.initialPage === undefined ? {} : { initialPage: options.initialPage }),\n ...(options.popupPolicy === undefined ? {} : { popupPolicy: options.popupPolicy }),\n ...(options.dialogPolicy === undefined ? {} : { dialogPolicy: options.dialogPolicy }),\n ...(options.pageControl === undefined ? {} : { pageControl: options.pageControl }),\n});\n"],"mappings":"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"}
@@ -1878,10 +1878,64 @@ const makeObservation = (targets, events) => {
1878
1878
  //#endregion
1879
1879
  //#region src/internal/browser/Pointer.ts
1880
1880
  const Reach = Schema.Struct({
1881
- viewportWidth: Schema.Finite,
1882
- viewportHeight: Schema.Finite,
1881
+ x: Schema.Finite,
1882
+ y: Schema.Finite,
1883
1883
  reachable: Schema.Boolean
1884
1884
  });
1885
+ /** Runs in each document on the path to the exact node, including cross-origin frames. */
1886
+ const hitPoint = (node, input) => {
1887
+ const refused = {
1888
+ x: input.x,
1889
+ y: input.y,
1890
+ reachable: false
1891
+ };
1892
+ if (!(node instanceof Element) || !node.isConnected || input.x < 0 || input.y < 0 || input.x >= innerWidth || input.y >= innerHeight) return refused;
1893
+ let hit = node.ownerDocument.elementFromPoint(input.x, input.y);
1894
+ for (let depth = 0; hit?.shadowRoot; depth++) {
1895
+ if (depth >= 32) return refused;
1896
+ const inner = hit.shadowRoot.elementFromPoint(input.x, input.y);
1897
+ if (inner === null || inner === hit) break;
1898
+ hit = inner;
1899
+ }
1900
+ if (hit === null || hit !== node && !node.contains(hit)) return refused;
1901
+ if (!input.child) return {
1902
+ x: input.x,
1903
+ y: input.y,
1904
+ reachable: true
1905
+ };
1906
+ if (!(node instanceof HTMLElement)) return refused;
1907
+ let ancestor = node;
1908
+ for (let depth = 0; ancestor !== null; depth++) {
1909
+ if (depth >= 128) return refused;
1910
+ const style = getComputedStyle(ancestor);
1911
+ const rotation = style.getPropertyValue("rotate");
1912
+ const scale = style.getPropertyValue("scale");
1913
+ if (rotation !== "" && rotation !== "none" && rotation !== "0deg" || scale !== "" && scale !== "none" && scale.split(/\s+/).some((axis) => !(Number.parseFloat(axis) > 0)) || style.perspective !== "none" || style.offsetPath !== "" && style.offsetPath !== "none") return refused;
1914
+ if (style.transform !== "none") {
1915
+ const matrix = new DOMMatrixReadOnly(style.transform);
1916
+ if (!matrix.is2D || matrix.b !== 0 || matrix.c !== 0 || matrix.a <= 0 || matrix.d <= 0) return refused;
1917
+ }
1918
+ const root = ancestor.getRootNode();
1919
+ ancestor = ancestor.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
1920
+ }
1921
+ const rect = node.getBoundingClientRect();
1922
+ const scaleX = rect.width / node.offsetWidth;
1923
+ const scaleY = rect.height / node.offsetHeight;
1924
+ if (!Number.isFinite(scaleX) || !Number.isFinite(scaleY) || scaleX <= 0 || scaleY <= 0) return refused;
1925
+ const style = getComputedStyle(node);
1926
+ const left = Number.parseFloat(style.paddingLeft);
1927
+ const top = Number.parseFloat(style.paddingTop);
1928
+ const right = Number.parseFloat(style.paddingRight);
1929
+ const bottom = Number.parseFloat(style.paddingBottom);
1930
+ const x = (input.x - rect.left) / scaleX - node.clientLeft - left;
1931
+ const y = (input.y - rect.top) / scaleY - node.clientTop - top;
1932
+ if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0 || x >= node.clientWidth - left - right || y >= node.clientHeight - top - bottom) return refused;
1933
+ return {
1934
+ x,
1935
+ y,
1936
+ reachable: true
1937
+ };
1938
+ };
1885
1939
  /**
1886
1940
  * Real pointer input to the selected page. Every command is one the browser would receive from
1887
1941
  * a person, so handlers see trusted events and the browser decides what is under the pointer.
@@ -1915,33 +1969,35 @@ const makePointer = (targets, actions) => {
1915
1969
  x: box.x + box.width / 2,
1916
1970
  y: box.y + box.height / 2
1917
1971
  };
1918
- const reach = safeDecode(Reach, await element.evaluate((node) => {
1919
- const rect = node.getBoundingClientRect();
1920
- const x = rect.left + rect.width / 2;
1921
- const y = rect.top + rect.height / 2;
1922
- let hit = node.ownerDocument.elementFromPoint(x, y);
1923
- while (hit?.shadowRoot) {
1924
- const inner = hit.shadowRoot.elementFromPoint(x, y);
1925
- if (inner === null || inner === hit) break;
1926
- hit = inner;
1972
+ const ancestors = [];
1973
+ try {
1974
+ let frame = await element.ownerFrame();
1975
+ if (frame === null || frame.page() !== page) throw failure("stale", "undispatched");
1976
+ while (frame !== page.mainFrame()) {
1977
+ if (ancestors.length >= 32) throw failure("limit", "undispatched");
1978
+ ancestors.push(await frame.frameElement());
1979
+ frame = frame.parentFrame();
1980
+ if (frame === null) throw failure("stale", "undispatched");
1927
1981
  }
1928
- return {
1929
- viewportWidth: window.top === window ? window.innerWidth : Number.POSITIVE_INFINITY,
1930
- viewportHeight: window.top === window ? window.innerHeight : Number.POSITIVE_INFINITY,
1931
- reachable: hit !== null && (hit === node || node.contains(hit))
1932
- };
1933
- }));
1934
- const viewport = Number.isFinite(reach.viewportWidth) ? {
1935
- width: reach.viewportWidth,
1936
- height: reach.viewportHeight
1937
- } : safeDecode(Schema.Struct({
1938
- width: Schema.Finite,
1939
- height: Schema.Finite
1940
- }), await page.evaluate(() => ({
1941
- width: window.innerWidth,
1942
- height: window.innerHeight
1943
- })));
1944
- if (!reach.reachable || point.x < 0 || point.y < 0 || point.x >= viewport.width || point.y >= viewport.height) throw failure("not-visible", "undispatched");
1982
+ let localPoint = point;
1983
+ for (const ancestor of ancestors.reverse()) {
1984
+ const reached = safeDecode(Reach, await ancestor.evaluate(hitPoint, {
1985
+ ...localPoint,
1986
+ child: true
1987
+ }));
1988
+ if (!reached.reachable) throw failure("not-visible", "undispatched");
1989
+ localPoint = {
1990
+ x: reached.x,
1991
+ y: reached.y
1992
+ };
1993
+ }
1994
+ if (!safeDecode(Reach, await element.evaluate(hitPoint, {
1995
+ ...localPoint,
1996
+ child: false
1997
+ })).reachable) throw failure("not-visible", "undispatched");
1998
+ } finally {
1999
+ await Promise.all(ancestors.map((ancestor) => ancestor.dispose()));
2000
+ }
1945
2001
  return point;
1946
2002
  };
1947
2003
  const hover = (target, ticket, policy) => sanitize(async () => {
@@ -2447,6 +2503,6 @@ const BrowserbaseBrowserBinding = Context.Reference("effect-browserbase/BrowserB
2447
2503
  const layer = (binding) => Layer.succeed(BrowserbaseBrowserBinding, binding);
2448
2504
  const layerPlaywright = layer(defaultBinding);
2449
2505
  //#endregion
2450
- export { bindingImplementation as a, playwright as i, layer as n, layerPlaywright as r, BrowserbaseBrowserBinding as t };
2506
+ export { connectPlaywrightEndpoint as a, playwright as i, layer as n, bindingImplementation as o, layerPlaywright as r, fromNativeAttempt as s, BrowserbaseBrowserBinding as t };
2451
2507
 
2452
- //# sourceMappingURL=BrowserBinding-qbQA6JhH.mjs.map
2508
+ //# sourceMappingURL=BrowserBinding-DlJTGYZL.mjs.map