@effect-agent/platform-cloudflare 0.1.0-beta.107 → 0.1.0-beta.108

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.
@@ -1,6 +1,6 @@
1
- import { h as BrowserRunSessionLifecycle, o as BrowserRunLiveViewRequest, r as BrowserRunHandoffState, s as BrowserRunLiveViewResult, t as BrowserRunHandoffRequest } from "./InteractiveBrowser-xVNX7ARl.mjs";
2
- import { Clock, Context, Crypto, Effect, Layer, Redacted, Schema, Semaphore } from "effect";
3
- import puppeteer from "@cloudflare/puppeteer";
1
+ import { _ as BrowserRunFailure, b as reportBrowserCause, g as BrowserRunBinding, h as BrowserRunSessionLifecycle, o as BrowserRunLiveViewRequest, r as BrowserRunHandoffState, s as BrowserRunLiveViewResult, t as BrowserRunHandoffRequest, v as browserFailure, x as reportedBrowserError, y as inheritBrowserReport } from "./InteractiveBrowser-l6J4eP6T.mjs";
2
+ import { Cause, Clock, Context, Crypto, Effect, Layer, Redacted, Schema, Semaphore } from "effect";
3
+ import "@cloudflare/puppeteer";
4
4
  import { InteractiveBrowserPolicy } from "effect-agent/interactive-browser";
5
5
  import { BrowserCredentialAccess, CardCredential, CredentialObservationDecision, CredentialOffer, CredentialOfferMetadata, CredentialOrigin, CredentialTarget, CredentialUseResult, ListCredentialOffers, LoginCredential, ProtectedBrowser, ProtectedBrowserCheckpoint, ProtectedBrowserClick, ProtectedBrowserControl, ProtectedBrowserError, ProtectedBrowserFill, ProtectedBrowserNavigate, ProtectedBrowserObservation, UseCredential } from "effect-agent/protected-browser";
6
6
  //#region src/protected-browser/policy.ts
@@ -91,15 +91,18 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
91
91
  driver.invalidate();
92
92
  cleanup = yield* driver.close.pipe(Effect.interruptible, Effect.timeoutOrElse({
93
93
  duration: "10 seconds",
94
- orElse: () => Effect.succeed("unconfirmed")
95
- }), Effect.catchCause(() => Effect.succeed("unconfirmed")));
94
+ orElse: () => Effect.fail(new BrowserRunFailure({
95
+ operation: "protected.close",
96
+ reason: "timeout"
97
+ }))
98
+ }), Effect.catchCause((cause) => reportBrowserCause("protected.close", cause).pipe(Effect.as("unconfirmed"))));
96
99
  return cleanup;
97
100
  })));
98
- yield* Effect.addFinalizer(() => detached ? Effect.void : close.pipe(Effect.flatMap((result) => result === "unconfirmed" ? Effect.logWarning("Protected browser exact-session closure unconfirmed") : Effect.void)));
99
- const remote = (effect) => effect.pipe(Effect.mapError((error) => fail(error.reason)));
100
- const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => fail("provider")));
101
- const caller = access.caller.pipe(Effect.mapError((error) => fail(error.reason)));
102
- const pageContext = remote(driver.context);
101
+ yield* Effect.addFinalizer(() => detached ? Effect.void : close);
102
+ const publicFailure = (effect) => effect.pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, (error) => inheritBrowserReport(error, fail(error.reason))))));
103
+ const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.catch((error) => reportBrowserCause("protected.policy.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(fail("provider")))))));
104
+ const caller = access.caller.pipe(publicFailure);
105
+ const pageContext = publicFailure(driver.context);
103
106
  const permitObservation = Effect.gen(function* () {
104
107
  const context = yield* pageContext;
105
108
  let origins;
@@ -112,7 +115,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
112
115
  exposures: [...exposures],
113
116
  humanExposure,
114
117
  humanOrigins
115
- }).pipe(Effect.mapError((error) => fail(error.reason)));
118
+ }).pipe(publicFailure);
116
119
  if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
117
120
  const decision = yield* Schema.decodeEffect(CredentialObservationDecision)(rawDecision).pipe(Effect.mapError(() => fail("observation-blocked")));
118
121
  if (decision === "deny") return yield* fail("observation-blocked");
@@ -122,13 +125,13 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
122
125
  }
123
126
  observation = "approved-after-exposure";
124
127
  }
125
- yield* remote(driver.restrictObservation(origins));
128
+ yield* publicFailure(driver.restrictObservation(origins));
126
129
  return {
127
130
  ...context,
128
131
  frameOrigins: context.frameOrigins.filter((origin) => origins === void 0 || origins.includes(origin))
129
132
  };
130
133
  });
131
- const target = (ref) => remote(driver.target(ref));
134
+ const target = (ref) => publicFailure(driver.target(ref));
132
135
  const authorizeAction = Effect.fn("ProtectedBrowser.authorizeAction")(function* (action) {
133
136
  if (access.authorizeAction === void 0) {
134
137
  if (action._tag === "Submit") return yield* fail("unsupported");
@@ -139,7 +142,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
139
142
  caller: principal,
140
143
  action,
141
144
  exposures: [...exposures]
142
- }).pipe(Effect.mapError((error) => fail(error.reason)));
145
+ }).pipe(publicFailure);
143
146
  if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
144
147
  if (action._tag !== "Navigate") {
145
148
  const current = yield* target(action.ref);
@@ -165,12 +168,15 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
165
168
  duration: remaining,
166
169
  orElse: () => Effect.fail(fail("timeout"))
167
170
  }), Effect.catchCause((cause) => Effect.gen(function* () {
171
+ const unexpected = cause.reasons.filter((reason) => reason._tag === "Die" || reason._tag === "Fail" && !Schema.is(ProtectedBrowserError)(reason.error));
172
+ if (unexpected.length > 0) yield* reportBrowserCause("protected.operation", Cause.fromReasons(unexpected));
168
173
  const error = cause.reasons.find((reason) => reason._tag === "Fail" && Schema.is(ProtectedBrowserError)(reason.error));
169
- const reason = error?._tag === "Fail" && Schema.is(ProtectedBrowserError)(error.error) ? error.error.reason : "provider";
174
+ const reason = unexpected.length === 0 && error?._tag === "Fail" && Schema.is(ProtectedBrowserError)(error.error) ? error.error.reason : "provider";
170
175
  const interrupt = cause.reasons.some((reason) => reason._tag === "Interrupt");
171
176
  if (dispatch !== "not-dispatched" || interrupt || reason === "provider" || reason === "timeout") yield* close;
172
177
  if (interrupt) return yield* Effect.interrupt;
173
- return yield* fail(dispatch === "possibly-dispatched" && reason === "provider" ? "outcome-unknown" : reason);
178
+ const outcome = fail(dispatch === "possibly-dispatched" && reason === "provider" ? "outcome-unknown" : reason);
179
+ return yield* unexpected.length > 0 ? reportedBrowserError(outcome) : inheritBrowserReport(error?._tag === "Fail" ? error.error : void 0, outcome);
174
180
  })), Effect.onInterrupt(() => close), Effect.withTracerEnabled(false));
175
181
  })).pipe(Effect.flatMap(Effect.fromOption(() => new ProtectedBrowserError({
176
182
  reason: "busy",
@@ -197,13 +203,13 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
197
203
  });
198
204
  offers.clear();
199
205
  dispatch = "possibly-dispatched";
200
- yield* remote(driver.navigate(decoded.url));
206
+ yield* publicFailure(driver.navigate(decoded.url));
201
207
  dispatch = "dispatched";
202
208
  yield* permitObservation;
203
209
  })),
204
210
  observe: run(Effect.gen(function* () {
205
211
  const before = yield* permitObservation;
206
- const result = yield* remote(driver.discover);
212
+ const result = yield* publicFailure(driver.discover);
207
213
  const after = yield* permitObservation;
208
214
  if (before.document !== after.document || result.document !== after.document || result.topOrigin !== after.topOrigin) return yield* fail("stale-reference");
209
215
  if (result.frameOrigins.some((origin) => !after.frameOrigins.includes(origin))) return yield* fail("observation-blocked");
@@ -242,7 +248,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
242
248
  };
243
249
  yield* authorizeAction(action);
244
250
  dispatch = "possibly-dispatched";
245
- yield* remote(driver.click(decoded.ref)).pipe(Effect.catch((error) => {
251
+ yield* publicFailure(driver.click(decoded.ref)).pipe(Effect.catch((error) => {
246
252
  if (error.reason === "needs-attention") dispatch = "not-dispatched";
247
253
  return Effect.fail(error);
248
254
  }));
@@ -261,7 +267,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
261
267
  target: control.target,
262
268
  role: control.role
263
269
  });
264
- yield* remote(driver.fill(decoded.ref, control.role, Redacted.make(decoded.value))).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
270
+ yield* publicFailure(driver.fill(decoded.ref, control.role, Redacted.make(decoded.value))).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
265
271
  dispatch = "possibly-dispatched";
266
272
  }) }));
267
273
  dispatch = "dispatched";
@@ -278,7 +284,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
278
284
  caller: principal,
279
285
  kind: decoded.kind,
280
286
  target: control.target
281
- }).pipe(Effect.mapError((error) => fail(error.reason)));
287
+ }).pipe(publicFailure);
282
288
  const now = yield* Clock.currentTimeMillis;
283
289
  for (const [ref, offer] of offers) if (offer.expires <= now) offers.delete(ref);
284
290
  if (candidates.length > 16 || offers.size + candidates.length > 64) return yield* fail("limit");
@@ -288,7 +294,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
288
294
  const pending = /* @__PURE__ */ new Map();
289
295
  for (const candidate of candidates) {
290
296
  const metadata = yield* decode(CredentialOfferMetadata, candidate.metadata);
291
- const ref = yield* crypto.randomUUIDv4.pipe(Effect.mapError(() => fail("provider")));
297
+ const ref = yield* crypto.randomUUIDv4.pipe(Effect.catch((error) => reportBrowserCause("protected.identity", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(fail("provider")))))));
292
298
  pending.set(ref, {
293
299
  caller: principal,
294
300
  key: candidate.key,
@@ -336,12 +342,12 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
336
342
  };
337
343
  const authorize = Effect.gen(function* () {
338
344
  if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
339
- yield* access.authorize(authorization).pipe(Effect.mapError((error) => fail(error.reason)));
345
+ yield* access.authorize(authorization).pipe(publicFailure);
340
346
  if (Redacted.value(yield* caller) !== Redacted.value(principal)) return yield* fail("denied");
341
347
  });
342
348
  yield* authorize;
343
349
  yield* validate;
344
- const raw = yield* access.resolve(authorization).pipe(Effect.mapError((error) => fail(error.reason)));
350
+ const raw = yield* access.resolve(authorization).pipe(publicFailure);
345
351
  const material = yield* Schema.decodeEffect(offer.kind === "login" ? LoginCredential : CardCredential)(raw).pipe(Effect.mapError(() => fail("resolver")));
346
352
  for (const field of decoded.fields) if (secretFor(material, field.role) === void 0) return yield* fail("missing-credential");
347
353
  offers.delete(decoded.offer);
@@ -351,7 +357,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
351
357
  const value = secretFor(material, field.role);
352
358
  if (value === void 0) return yield* fail("missing-credential");
353
359
  observation = "protected";
354
- yield* remote(driver.fill(field.ref, field.role, value)).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
360
+ yield* publicFailure(driver.fill(field.ref, field.role, value)).pipe(Effect.provideService(ProtectedBrowserDispatch, { mark: Effect.sync(() => {
355
361
  dispatch = "possibly-dispatched";
356
362
  if (!exposures.some((target) => sameTarget(target, offer.target))) exposures.push(offer.target);
357
363
  }) }));
@@ -364,7 +370,7 @@ const makeProtectedBrowserPolicy = Effect.fn("ProtectedBrowser.open")(function*
364
370
  yield* authorize;
365
371
  const ref = decoded.submit;
366
372
  dispatch = "possibly-dispatched";
367
- yield* remote(driver.click(ref)).pipe(Effect.catch((error) => {
373
+ yield* publicFailure(driver.click(ref)).pipe(Effect.catch((error) => {
368
374
  if (error.reason === "needs-attention") dispatch = "dispatched";
369
375
  return Effect.fail(error);
370
376
  }));
@@ -575,27 +581,30 @@ const Descriptions = Schema.Array(Schema.NullOr(Description)).check(Schema.isMax
575
581
  /** One host-private acquired session. The SDK handles and exact-session cleanup have one owner. */
576
582
  var ProtectedNativeSession = class extends Context.Service()("@effect-agent/platform-cloudflare/ProtectedNativeSession") {};
577
583
  const transportError = (reason) => new ProtectedTransportError({ reason });
578
- const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => transportError("provider")));
579
- const remote = (run) => Effect.tryPromise({
584
+ const decode = (schema, value) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.catch((error) => reportBrowserCause("protected.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
585
+ const remote = (operation, run) => Effect.tryPromise({
580
586
  try: async (signal) => {
581
587
  try {
582
588
  return {
583
589
  ok: true,
584
590
  value: await run(signal)
585
591
  };
586
- } catch {
587
- return { ok: false };
592
+ } catch (cause) {
593
+ return {
594
+ ok: false,
595
+ failure: browserFailure(operation, cause)
596
+ };
588
597
  }
589
598
  },
590
- catch: () => transportError("provider")
591
- }).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(transportError("provider"))));
599
+ catch: (cause) => browserFailure(operation, cause)
600
+ }).pipe(Effect.flatMap((result) => result.ok ? Effect.succeed(result.value) : Effect.fail(result.failure)), Effect.catch((failure) => reportBrowserCause(operation, Cause.fail(failure)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
592
601
  /** Host-private scoped transport. Session capabilities are requirements; policy is per-pass data. */
593
602
  const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(function* (policy) {
594
603
  const session = yield* ProtectedNativeSession;
595
604
  const { browser, page } = session;
596
605
  const crypto = yield* Crypto.Crypto;
597
606
  const clock = yield* Clock.Clock;
598
- const uuid = crypto.randomUUIDv4.pipe(Effect.mapError(() => transportError("provider")));
607
+ const uuid = crypto.randomUUIDv4.pipe(Effect.catch((error) => reportBrowserCause("protected.identity", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(transportError("provider")))))));
599
608
  let closed = false;
600
609
  let violation = false;
601
610
  let documentRef;
@@ -652,7 +661,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
652
661
  });
653
662
  const isCurrent = Effect.fn("ProtectedNativeTransport.isCurrent")(function* (state) {
654
663
  if (state.frame.detached) return false;
655
- return yield* remote(() => state.handle.evaluate((held) => {
664
+ return yield* remote("protected.validate-document", () => state.handle.evaluate((held) => {
656
665
  if (typeof held !== "object" || held === null) return false;
657
666
  return Reflect.get(held, "doc") === Reflect.get(globalThis, "document");
658
667
  }));
@@ -661,7 +670,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
661
670
  yield* check;
662
671
  const state = controls.get(ref);
663
672
  if (!state || observationOrigins !== void 0 && !observationOrigins.has(state.control.target.frameOrigin) || state.expires <= (yield* clock.currentTimeMillis) || !(yield* isCurrent(state.frame))) return yield* transportError("stale-reference");
664
- const current = yield* remote(() => state.frame.handle.evaluate((held, index) => {
673
+ const current = yield* remote("protected.validate-control", () => state.frame.handle.evaluate((held, index) => {
665
674
  if (typeof held !== "object" || held === null) return null;
666
675
  return Reflect.apply(Reflect.get(held, "validate"), held, [index]);
667
676
  }, state.index));
@@ -704,14 +713,14 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
704
713
  yield* check;
705
714
  yield* origin(url);
706
715
  clear();
707
- yield* remote(() => page.goto(url, { waitUntil: "load" }));
716
+ yield* remote("protected.navigate", () => page.goto(url, { waitUntil: "load" }));
708
717
  yield* context;
709
718
  }),
710
719
  target: (ref) => get(ref).pipe(Effect.map((state) => state.control)),
711
720
  discover: Effect.gen(function* () {
712
721
  const before = yield* context;
713
722
  controls.clear();
714
- for (const state of frames.values()) yield* remote(() => state.handle.dispose());
723
+ for (const state of frames.values()) yield* remote("protected.dispose", () => state.handle.dispose());
715
724
  frames.clear();
716
725
  const discovered = [];
717
726
  let text = "";
@@ -722,7 +731,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
722
731
  if (observationOrigins !== void 0 && !observationOrigins.has(frameOrigin)) continue;
723
732
  frameOrigins.add(frameOrigin);
724
733
  if (typeof frame.isolatedRealm !== "function") return yield* transportError("unsupported");
725
- const handle = yield* remote(() => frame.isolatedRealm().evaluateHandle(inspectFrame));
734
+ const handle = yield* remote("protected.discover", () => frame.isolatedRealm().evaluateHandle(inspectFrame));
726
735
  const state = {
727
736
  frame,
728
737
  handle,
@@ -731,7 +740,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
731
740
  forms: /* @__PURE__ */ new Map()
732
741
  };
733
742
  frames.set(frame, state);
734
- const raw = yield* remote(() => handle.evaluate((held) => {
743
+ const raw = yield* remote("protected.describe-controls", () => handle.evaluate((held) => {
735
744
  if (typeof held !== "object" || held === null) return null;
736
745
  return Reflect.get(held, "original");
737
746
  }));
@@ -773,7 +782,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
773
782
  });
774
783
  discovered.push(control);
775
784
  }
776
- const rawText = yield* remote(() => handle.evaluate((held) => {
785
+ const rawText = yield* remote("protected.read-text", () => handle.evaluate((held) => {
777
786
  if (typeof held !== "object" || held === null) return null;
778
787
  return Reflect.apply(Reflect.get(held, "text"), held, []);
779
788
  }));
@@ -796,7 +805,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
796
805
  const dispatch = yield* ProtectedBrowserDispatch;
797
806
  const state = yield* get(ref);
798
807
  yield* dispatch.mark;
799
- const filled = yield* remote(() => state.frame.handle.evaluate((held, index, expectedRole, secret) => {
808
+ const filled = yield* remote("protected.fill", () => state.frame.handle.evaluate((held, index, expectedRole, secret) => {
800
809
  if (typeof held !== "object" || held === null) return false;
801
810
  return Reflect.apply(Reflect.get(held, "fill"), held, [
802
811
  index,
@@ -809,7 +818,7 @@ const makeProtectedNativeTransport = Effect.fn("ProtectedNativeTransport.make")(
809
818
  }),
810
819
  click: Effect.fn("ProtectedNativeTransport.click")(function* (ref) {
811
820
  const state = yield* get(ref);
812
- const clicked = yield* remote(() => state.frame.handle.evaluate((held, index) => {
821
+ const clicked = yield* remote("protected.click", () => state.frame.handle.evaluate((held, index) => {
813
822
  if (typeof held !== "object" || held === null) return false;
814
823
  return Reflect.apply(Reflect.get(held, "click"), held, [index]);
815
824
  }, state.index));
@@ -834,6 +843,7 @@ var BrowserRunProtectedBinding = class extends Context.Service()("@effect-agent/
834
843
  */
835
844
  const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBinding)(Effect.gen(function* () {
836
845
  const lifecycle = yield* BrowserRunSessionLifecycle;
846
+ const binding = yield* BrowserRunBinding;
837
847
  const crypto = yield* Crypto.Crypto;
838
848
  return { open: Effect.fn("BrowserRunProtectedTransport.open")(function* (policy, identity) {
839
849
  const failure = () => new ProtectedBrowserError({
@@ -850,108 +860,117 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
850
860
  let browser;
851
861
  let driver;
852
862
  let invalid = false;
853
- const runCleanup = Effect.runPromiseWith(Context.make(Clock.Clock, yield* Clock.Clock));
863
+ const runCleanup = Effect.runPromiseWith(yield* Effect.context());
854
864
  const terminate = Effect.gen(function* () {
855
865
  invalid = true;
856
866
  driver?.invalidate();
857
867
  if (sessionId === void 0) return "unconfirmed";
858
- const cleanup = yield* lifecycle.close(sessionId).pipe(Effect.as("confirmed"), Effect.catchCause(() => Effect.succeed("unconfirmed")), Effect.interruptible, Effect.timeoutOrElse({
868
+ const cleanup = yield* lifecycle.close(sessionId).pipe(Effect.as("confirmed"), Effect.catchCause((cause) => reportBrowserCause("protected.close", cause).pipe(Effect.as("unconfirmed"))), Effect.interruptible, Effect.timeoutOrElse({
859
869
  duration: "10 seconds",
860
- orElse: () => Effect.succeed("unconfirmed")
870
+ orElse: () => reportBrowserCause("protected.close", Cause.fail(new BrowserRunFailure({
871
+ operation: "protected.close",
872
+ reason: "timeout"
873
+ }))).pipe(Effect.as("unconfirmed"))
861
874
  }));
862
875
  const connected = browser;
863
- if (connected !== void 0) yield* Effect.promise(() => connected.disconnect()).pipe(Effect.catchCause(() => Effect.void), Effect.interruptible, Effect.timeoutOrElse({
876
+ if (connected !== void 0) yield* Effect.promise(() => connected.disconnect()).pipe(Effect.catchCause((cause) => reportBrowserCause("protected.disconnect", cause)), Effect.interruptible, Effect.timeoutOrElse({
864
877
  duration: "1 second",
865
- orElse: () => Effect.void
878
+ orElse: () => reportBrowserCause("protected.disconnect", Cause.fail(new BrowserRunFailure({
879
+ operation: "protected.disconnect",
880
+ reason: "timeout"
881
+ })))
866
882
  }));
867
883
  return cleanup;
868
884
  });
869
885
  const close = yield* Effect.cached(terminate);
870
- yield* Effect.addFinalizer(() => detached ? Effect.void : close.pipe(Effect.flatMap((state) => state === "confirmed" ? Effect.void : Effect.logWarning("Protected browser exact-session closure unconfirmed"))));
886
+ yield* Effect.addFinalizer(() => detached ? Effect.void : close);
887
+ let stage = identity === void 0 ? "protected.acquire" : "protected.connect";
871
888
  const acquired = yield* Effect.tryPromise({
872
889
  try: async (signal) => {
873
- let recordingDisabled = false;
874
- const acquired = identity === void 0 ? await puppeteer.acquire({ fetch: async (input, init) => {
875
- const request = new Request(input, init);
876
- const url = new URL(request.url);
877
- if (url.pathname === "/v1/devtools/browser" && request.method === "POST") {
878
- url.searchParams.set("recording", "false");
879
- recordingDisabled = url.searchParams.get("recording") === "false";
880
- return options.browser.fetch(new Request(url, request));
890
+ try {
891
+ sessionId = identity === void 0 ? Redacted.make(await binding.acquire(Math.min(6e5, Math.max(1e4, policy.maxElapsedMillis)), "protected.acquire")) : identity.sessionId;
892
+ if (signal.aborted || invalid) {
893
+ await runCleanup(terminate);
894
+ throw new ProtectedTransportError({ reason: "stale-reference" });
881
895
  }
882
- return options.browser.fetch(request);
883
- } }, {
884
- recording: false,
885
- keep_alive: Math.min(6e5, Math.max(1e4, policy.maxElapsedMillis))
886
- }) : { sessionId: Redacted.value(identity.sessionId) };
887
- sessionId = Redacted.make(Schema.decodeSync(Schema.String.check(Schema.isUUID()))(acquired.sessionId));
888
- if (identity === void 0 && !recordingDisabled || signal.aborted || invalid) {
889
- await runCleanup(terminate);
890
- throw new ProtectedTransportError({ reason: "stale-reference" });
891
- }
892
- browser = await puppeteer.connect(options.browser, acquired.sessionId);
893
- if (signal.aborted || invalid) {
894
- await runCleanup(terminate);
895
- throw new ProtectedTransportError({ reason: "stale-reference" });
896
- }
897
- let page;
898
- if (identity === void 0) page = await (await browser.createBrowserContext()).newPage();
899
- else {
900
- const context = browser.browserContexts().find((candidate) => candidate.id === Redacted.value(identity.contextId));
901
- if (context === void 0) throw new ProtectedTransportError({ reason: "stale-reference" });
902
- const pages = await context.pages();
903
- let found;
904
- for (const candidate of pages) {
905
- const client = await candidate.createCDPSession();
906
- const info = await client.send("Target.getTargetInfo");
907
- await client.detach();
908
- if (info.targetInfo.targetId === Redacted.value(identity.targetId)) found = candidate;
896
+ stage = "protected.connect";
897
+ browser = await binding.connect(Redacted.value(sessionId), stage);
898
+ if (signal.aborted || invalid) {
899
+ await runCleanup(terminate);
900
+ throw new ProtectedTransportError({ reason: "stale-reference" });
909
901
  }
910
- if (found === void 0 || pages.length !== 1) throw new ProtectedTransportError({ reason: "stale-reference" });
911
- page = found;
912
- }
913
- control = await page.createCDPSession();
914
- const info = await control.send("Target.getTargetInfo");
915
- providerIdentity = Schema.decodeSync(ProtectedProviderIdentity)({
916
- sessionId,
917
- contextId: Redacted.make(page.browserContext().id ?? ""),
918
- targetId: Redacted.make(info.targetInfo.targetId)
919
- });
920
- await page.setBypassServiceWorker(true);
921
- await page.setRequestInterception(true);
922
- page.on("request", (request) => {
923
- let allowed = policy.network._tag === "Unrestricted";
924
- try {
925
- const url = new URL(request.url());
926
- allowed ||= policy.network._tag === "ExactHosts" && url.protocol === "https:" && !url.username && !url.password && policy.network.allowedHosts.includes(url.host);
927
- } catch {}
928
- (allowed && !invalid ? request.continue() : request.abort("blockedbyclient")).catch(() => {
929
- invalid = true;
930
- driver?.invalidate();
902
+ let page;
903
+ if (identity === void 0) {
904
+ stage = "protected.context";
905
+ const context = await browser.createBrowserContext();
906
+ stage = "protected.page";
907
+ page = await context.newPage();
908
+ } else {
909
+ stage = "protected.resume";
910
+ const context = browser.browserContexts().find((candidate) => candidate.id === Redacted.value(identity.contextId));
911
+ if (context === void 0) throw new ProtectedTransportError({ reason: "stale-reference" });
912
+ const pages = await context.pages();
913
+ let found;
914
+ for (const candidate of pages) {
915
+ const client = await candidate.createCDPSession();
916
+ const info = await client.send("Target.getTargetInfo");
917
+ await client.detach();
918
+ if (info.targetInfo.targetId === Redacted.value(identity.targetId)) found = candidate;
919
+ }
920
+ if (found === void 0 || pages.length !== 1) throw new ProtectedTransportError({ reason: "stale-reference" });
921
+ page = found;
922
+ }
923
+ stage = "protected.control";
924
+ control = await page.createCDPSession();
925
+ const info = await control.send("Target.getTargetInfo");
926
+ stage = "protected.identity";
927
+ providerIdentity = Schema.decodeSync(ProtectedProviderIdentity)({
928
+ sessionId,
929
+ contextId: Redacted.make(page.browserContext().id ?? ""),
930
+ targetId: Redacted.make(info.targetInfo.targetId)
931
931
  });
932
- });
933
- if (signal.aborted || invalid) {
934
- await runCleanup(terminate);
935
- throw new ProtectedTransportError({ reason: "stale-reference" });
932
+ stage = "protected.interception";
933
+ await page.setBypassServiceWorker(true);
934
+ await page.setRequestInterception(true);
935
+ page.on("request", (request) => {
936
+ let allowed = policy.network._tag === "Unrestricted";
937
+ try {
938
+ const url = new URL(request.url());
939
+ allowed ||= policy.network._tag === "ExactHosts" && url.protocol === "https:" && !url.username && !url.password && policy.network.allowedHosts.includes(url.host);
940
+ } catch {}
941
+ (allowed && !invalid ? request.continue() : request.abort("blockedbyclient")).catch(async (cause) => {
942
+ invalid = true;
943
+ driver?.invalidate();
944
+ await runCleanup(reportBrowserCause("protected.interception", Cause.fail(cause)));
945
+ });
946
+ });
947
+ if (signal.aborted || invalid) {
948
+ await runCleanup(terminate);
949
+ throw new ProtectedTransportError({ reason: "stale-reference" });
950
+ }
951
+ return ProtectedNativeSession.of({
952
+ browser,
953
+ page,
954
+ close,
955
+ release: Effect.suspend(() => detached ? Effect.void : close.pipe(Effect.asVoid))
956
+ });
957
+ } catch (cause) {
958
+ if (signal.aborted && !(cause instanceof ProtectedTransportError)) await runCleanup(reportBrowserCause(stage, Cause.fail(cause)));
959
+ throw cause;
936
960
  }
937
- return ProtectedNativeSession.of({
938
- browser,
939
- page,
940
- close,
941
- release: Effect.suspend(() => detached ? Effect.void : close.pipe(Effect.asVoid))
942
- });
943
961
  },
944
- catch: failure
962
+ catch: (cause) => browserFailure(stage, cause)
945
963
  }).pipe(Effect.timeoutOrElse({
946
964
  duration: Math.min(policy.maxElapsedMillis, 3e4),
947
- orElse: () => Effect.fail(new ProtectedBrowserError({
948
- ...failure(),
965
+ orElse: () => Effect.fail(new BrowserRunFailure({
966
+ operation: stage,
949
967
  reason: "timeout"
950
968
  }))
951
- }), Effect.catch((error) => close.pipe(Effect.flatMap((cleanup) => Effect.fail(new ProtectedBrowserError({
952
- ...error,
969
+ }), Effect.catch((error) => reportBrowserCause(stage, Cause.fail(error)).pipe(Effect.andThen(close), Effect.flatMap((cleanup) => Effect.fail(reportedBrowserError(new ProtectedBrowserError({
970
+ ...failure(),
971
+ reason: error.reason === "timeout" ? "timeout" : "provider",
953
972
  cleanup
954
- }))))));
973
+ })))))));
955
974
  driver = yield* makeProtectedNativeTransport(policy).pipe(Effect.provideService(ProtectedNativeSession, acquired), Effect.provideService(Crypto.Crypto, crypto));
956
975
  const exact = providerIdentity;
957
976
  if (exact === void 0) return yield* failure();
@@ -964,11 +983,14 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
964
983
  const send = Reflect.get(control, "send");
965
984
  return await Reflect.apply(send, control, [method, parameters]);
966
985
  },
967
- catch: failure
986
+ catch: (cause) => browserFailure("protected.command", cause)
968
987
  }).pipe(Effect.timeoutOrElse({
969
988
  duration: "10 seconds",
970
- orElse: () => Effect.fail(failure())
971
- })),
989
+ orElse: () => Effect.fail(new BrowserRunFailure({
990
+ operation: "protected.command",
991
+ reason: "timeout"
992
+ }))
993
+ }), Effect.catch((error) => reportBrowserCause("protected.command", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure())))))),
972
994
  detach: Effect.tryPromise({
973
995
  try: async () => {
974
996
  if (invalid || detached || browser === void 0) throw void 0;
@@ -976,14 +998,17 @@ const protectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedBindi
976
998
  detached = true;
977
999
  driver?.invalidate();
978
1000
  },
979
- catch: failure
1001
+ catch: (cause) => browserFailure("protected.detach", cause)
980
1002
  }).pipe(Effect.timeoutOrElse({
981
1003
  duration: "10 seconds",
982
- orElse: () => Effect.fail(failure())
983
- }))
1004
+ orElse: () => Effect.fail(new BrowserRunFailure({
1005
+ operation: "protected.detach",
1006
+ reason: "timeout"
1007
+ }))
1008
+ }), Effect.catch((error) => reportBrowserCause("protected.detach", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure()))))))
984
1009
  };
985
1010
  }, Effect.withTracerEnabled(false)) };
986
- }));
1011
+ })).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
987
1012
  /** One binding implementation serves scoped tools and host-managed protected handoffs. */
988
1013
  const browserRunProtectedBindingLayer = (options) => Layer.effect(BrowserRunProtectedTransport)(Effect.gen(function* () {
989
1014
  const binding = yield* BrowserRunProtectedBinding;
@@ -1048,7 +1073,7 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
1048
1073
  if (handoff?.handoffId === void 0) return yield* failure("denied");
1049
1074
  yield* fits(0);
1050
1075
  const raw = yield* provider.command("Cloudflare.getHandoffState", {});
1051
- const result = yield* Schema.decodeUnknownEffect(HandoffState)(raw).pipe(Effect.mapError(() => failure("provider")));
1076
+ const result = yield* Schema.decodeUnknownEffect(HandoffState)(raw).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
1052
1077
  if (result.active && result.handoffId === void 0 || result.handoffId !== void 0 && result.handoffId !== Redacted.value(handoff.handoffId)) return yield* failure("denied");
1053
1078
  return BrowserRunHandoffState.make({
1054
1079
  active: result.active,
@@ -1079,13 +1104,13 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
1079
1104
  const result = yield* provider.command("Cloudflare.handoff", {
1080
1105
  instructions: decoded.instructions,
1081
1106
  timeout: decoded.timeout
1082
- }).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Handoff)), Effect.catch(() => state.handle.close.pipe(Effect.flatMap((cleanup) => Effect.fail(new ProtectedBrowserError({
1107
+ }).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Handoff)), Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.interrupt : reportBrowserCause("protected.handoff", cause).pipe(Effect.andThen(state.handle.close), Effect.flatMap((cleanup) => Effect.fail(reportedBrowserError(new ProtectedBrowserError({
1083
1108
  reason: "outcome-unknown",
1084
1109
  dispatch: "possibly-dispatched",
1085
1110
  milestone: suspended.milestone,
1086
1111
  observation: "closed",
1087
1112
  cleanup
1088
- }))))), Effect.onInterrupt(() => state.handle.close));
1113
+ })))))), Effect.onInterrupt(() => state.handle.close));
1089
1114
  handoff = BrowserRunProtectedCheckpoint.make({
1090
1115
  ...provider.identity,
1091
1116
  protected: suspended,
@@ -1101,14 +1126,14 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
1101
1126
  mode: decoded.mode,
1102
1127
  expiresInMs: decoded.expiresInMs
1103
1128
  });
1104
- const result = yield* Schema.decodeUnknownEffect(LiveView)(raw).pipe(Effect.mapError(() => failure("provider")));
1105
- return yield* Schema.decodeEffect(BrowserRunLiveViewResult)({ devtoolsFrontendUrl: Redacted.make(result.devtoolsFrontendUrl) }).pipe(Effect.mapError(() => failure("provider")));
1129
+ const result = yield* Schema.decodeUnknownEffect(LiveView)(raw).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
1130
+ return yield* Schema.decodeEffect(BrowserRunLiveViewResult)({ devtoolsFrontendUrl: Redacted.make(result.devtoolsFrontendUrl) }).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
1106
1131
  })),
1107
1132
  getHandoffState: control(getHandoffState),
1108
1133
  getReturnOrigin: control(Effect.gen(function* () {
1109
1134
  if ((yield* getHandoffState).active) return yield* failure("busy");
1110
- const context = yield* provider.driver.context.pipe(Effect.mapError((error) => failure(error.reason)));
1111
- return yield* Schema.decodeEffect(CredentialOrigin)(context.topOrigin).pipe(Effect.mapError(() => failure("provider")));
1135
+ const context = yield* provider.driver.context.pipe(Effect.mapError((error) => inheritBrowserReport(error, failure(error.reason))));
1136
+ return yield* Schema.decodeEffect(CredentialOrigin)(context.topOrigin).pipe(Effect.catch((error) => reportBrowserCause("protected.host.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(failure("provider")))))));
1112
1137
  })),
1113
1138
  returnControl: control(Effect.gen(function* () {
1114
1139
  if ((yield* getHandoffState).active) return yield* failure("busy");
@@ -1126,7 +1151,7 @@ const browserRunProtectedHostLayer = () => Layer.effect(BrowserRunProtectedHost)
1126
1151
  return {
1127
1152
  open: (policy) => open(policy),
1128
1153
  resume: (checkpoint) => Schema.decodeEffect(BrowserRunProtectedCheckpoint)(checkpoint).pipe(Effect.mapError(() => failure("denied")), Effect.flatMap((decoded) => open(decoded.protected.policy, decoded))),
1129
- closeSession: (id) => lifecycle.close(id).pipe(Effect.as("confirmed"), Effect.catchCause(() => Effect.succeed("unconfirmed")))
1154
+ closeSession: (id) => lifecycle.close(id).pipe(Effect.as("confirmed"), Effect.catchCause((cause) => reportBrowserCause("protected.closeSession", cause).pipe(Effect.as("unconfirmed"))))
1130
1155
  };
1131
1156
  }));
1132
1157
  //#endregion