@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.
@@ -475,13 +475,13 @@ export declare const encodeUnknownResolutionCommand: (input: UnknownResolutionCo
475
475
  readonly author: string;
476
476
  readonly reason: string;
477
477
  readonly resolution: {
478
- readonly _tag: "SafeToRetry";
479
- } | {
480
478
  readonly _tag: "CompletedWithResult";
481
479
  readonly result: Schema.Json;
482
480
  readonly isFailure: boolean;
483
481
  } | {
484
482
  readonly _tag: "NeverHappened";
483
+ } | {
484
+ readonly _tag: "SafeToRetry";
485
485
  } | {
486
486
  readonly _tag: "AbortSubmission";
487
487
  };
@@ -645,11 +645,6 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
645
645
  readonly createdAt: string;
646
646
  readonly deploymentId: string;
647
647
  readonly payload: {
648
- readonly _tag: "AbortRequested";
649
- readonly submissionId: string;
650
- readonly author: string;
651
- readonly reason: string;
652
- } | {
653
648
  readonly _tag: "ThreadCreated";
654
649
  readonly agentId: string;
655
650
  readonly definitions: {
@@ -927,6 +922,11 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
927
922
  readonly runDisposition?: Schema.Json | undefined;
928
923
  readonly finishReason?: "budget-exhausted" | undefined;
929
924
  readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
925
+ } | {
926
+ readonly _tag: "AbortRequested";
927
+ readonly submissionId: string;
928
+ readonly author: string;
929
+ readonly reason: string;
930
930
  } | {
931
931
  readonly _tag: "SubmissionSettled";
932
932
  readonly submissionId: string;
@@ -1465,13 +1465,13 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
1465
1465
  readonly author: string;
1466
1466
  readonly reason: string;
1467
1467
  readonly resolution: {
1468
- readonly _tag: "SafeToRetry";
1469
- } | {
1470
1468
  readonly _tag: "CompletedWithResult";
1471
1469
  readonly result: Schema.Json;
1472
1470
  readonly isFailure: boolean;
1473
1471
  } | {
1474
1472
  readonly _tag: "NeverHappened";
1473
+ } | {
1474
+ readonly _tag: "SafeToRetry";
1475
1475
  } | {
1476
1476
  readonly _tag: "AbortSubmission";
1477
1477
  };
@@ -1481,6 +1481,21 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
1481
1481
  } | {
1482
1482
  readonly _tag: "HostFailed";
1483
1483
  readonly failure: {
1484
+ readonly _tag: "DurableAlarmError";
1485
+ readonly operation: string;
1486
+ readonly message: string;
1487
+ readonly cause?: Schema.Json | undefined;
1488
+ } | {
1489
+ readonly _tag: "HostProtocolError";
1490
+ readonly message: string;
1491
+ } | {
1492
+ readonly _tag: "AgentInputError";
1493
+ readonly message: string;
1494
+ } | {
1495
+ readonly _tag: "DigestError";
1496
+ readonly message: string;
1497
+ readonly cause?: Schema.Json | undefined;
1498
+ } | {
1484
1499
  readonly _tag: "AdmissionConflict";
1485
1500
  readonly threadId: string;
1486
1501
  readonly principal: string;
@@ -1497,6 +1512,15 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
1497
1512
  readonly _tag: "SettlementConflict";
1498
1513
  readonly submissionId: string;
1499
1514
  readonly existingOutcome: "aborted" | "completed" | "failed";
1515
+ } | {
1516
+ readonly _tag: "ApprovalConflict";
1517
+ readonly submissionId: string;
1518
+ readonly toolCallId: string;
1519
+ readonly existingDecision: "approved" | "denied";
1520
+ } | {
1521
+ readonly _tag: "UnknownResolutionConflict";
1522
+ readonly submissionId: string;
1523
+ readonly toolCallId: string;
1500
1524
  } | {
1501
1525
  readonly _tag: "JoinedToHost";
1502
1526
  readonly submissionId: string;
@@ -1526,25 +1550,6 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
1526
1550
  readonly threadId: string;
1527
1551
  readonly actualEpoch: number;
1528
1552
  readonly attemptedEpoch: number;
1529
- } | {
1530
- readonly _tag: "HostProtocolError";
1531
- readonly message: string;
1532
- } | {
1533
- readonly _tag: "AgentInputError";
1534
- readonly message: string;
1535
- } | {
1536
- readonly _tag: "DigestError";
1537
- readonly message: string;
1538
- readonly cause?: Schema.Json | undefined;
1539
- } | {
1540
- readonly _tag: "ApprovalConflict";
1541
- readonly submissionId: string;
1542
- readonly toolCallId: string;
1543
- readonly existingDecision: "approved" | "denied";
1544
- } | {
1545
- readonly _tag: "UnknownResolutionConflict";
1546
- readonly submissionId: string;
1547
- readonly toolCallId: string;
1548
1553
  } | {
1549
1554
  readonly _tag: "DurableRuntimeFailpointError";
1550
1555
  readonly location: "abort:after-intent" | "approval:after-request-append" | "approval:after-suspend" | "checkpoint:after-save" | "checkpoint:before-save" | "claim:after-claim" | "compaction:after-canonical-append" | "compaction:before-canonical-append" | "input:after-canonical-append" | "join:after-canonical-append" | "join:after-claim" | "policy:after-reservation-append" | "policy:before-reservation-append" | "resolve:after-intent" | "run:after-duration-append" | "run:after-start-append" | "run:before-duration-append" | "run:before-start-append" | "step:after-step-append" | "subagent:after-admit" | "subagent:after-child-abort-intent" | "subagent:after-child-ready" | "subagent:after-join-append" | "subagent:after-release" | "subagent:after-release-pending" | "subagent:after-request-append" | "subagent:after-reserve" | "subagent:after-sibling-settle" | "subagent:after-start-append" | "subagent:after-suspend" | "subagent:before-join-append" | "submit:after-admit" | "submit:after-materialize" | "terminalize:after-canonical-append" | "terminalize:after-reserve" | "tools:after-prepared-append" | "tools:after-unavailable-append" | "tools:before-prepared-append" | "tools:before-unavailable-append" | "turn:after-canonical-append" | "turn:after-response-append" | "turn:after-results-append" | "update:after-canonical-append" | "update:after-delivery-insert" | "update:before-canonical-append" | "update:before-delivery-insert" | "worker:after-completion-append" | "worker:after-origin-append" | "worker:after-report-append" | "worker:after-report-delivery" | "worker:after-source-append" | "worker:after-subtree-append" | "worker:before-completion-append" | "worker:before-origin-append" | "worker:before-report-append" | "worker:before-report-delivery" | "worker:before-source-append" | "worker:before-subtree-append";
@@ -1553,11 +1558,6 @@ export declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorde
1553
1558
  readonly limit: "database-bytes" | "input-bytes" | "queue-depth";
1554
1559
  readonly actual: number;
1555
1560
  readonly maximum: number;
1556
- } | {
1557
- readonly _tag: "DurableAlarmError";
1558
- readonly operation: string;
1559
- readonly message: string;
1560
- readonly cause?: Schema.Json | undefined;
1561
1561
  } | {
1562
1562
  readonly _tag: "OperationDenied";
1563
1563
  readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
@@ -6,6 +6,7 @@ import { BrowserHandle, BrowserSelectFileRequest, InteractiveBrowser, Interactiv
6
6
  declare const BrowserRunCleanupError_base: Schema.Class<BrowserRunCleanupError, Schema.TaggedStruct<"BrowserRunCleanupError", {
7
7
  readonly reason: Schema.Literals<readonly ["configuration", "authorization", "rate-limited", "provider", "malformed", "timeout", "pending"]>;
8
8
  readonly status: Schema.optionalKey<Schema.Int>;
9
+ readonly cause: Schema.optionalKey<Schema.Defect>;
9
10
  }>, import("effect/Cause").YieldableError>;
10
11
  declare class BrowserRunCleanupError extends BrowserRunCleanupError_base {}
11
12
  interface BrowserRunLifecycleOptions {
@@ -167,4 +168,4 @@ declare const CloudflareInteractiveBrowser: {
167
168
  };
168
169
  //#endregion
169
170
  export { isBrowserRunUndispatchedActionError as C, browserRunInteractiveLayer as S, BrowserRunSessionLifecycle as T, BrowserRunViewport as _, BrowserRunInteractiveAcquisition as a, browserRunInteractiveHostLayer as b, BrowserRunInteractiveCdpSession as c, BrowserRunInteractivePage as d, BrowserRunInteractiveRequest as f, BrowserRunLiveViewResult as g, BrowserRunLiveViewRequest as h, BrowserRunHandoffState as i, BrowserRunInteractiveContext as l, BrowserRunInteractiveSession as m, BrowserRunHandoffRequest as n, BrowserRunInteractiveBinding as o, BrowserRunInteractiveRequestListener as p, BrowserRunHandoffResult as r, BrowserRunInteractiveBrowser as s, BrowserRunCloudflareCommand as t, BrowserRunInteractiveHost as u, CloudflareInteractiveBrowser as v, BrowserRunCleanupError as w, browserRunInteractiveImplementation as x, CloudflareInteractiveBrowserOptions as y };
170
- //# sourceMappingURL=InteractiveBrowser-B3S8if51.d.mts.map
171
+ //# sourceMappingURL=InteractiveBrowser-CKp6pqmw.d.mts.map
@@ -1,9 +1,102 @@
1
- import { Clock, Context, Duration, Effect, Encoding, Layer, Option, Redacted, Ref, Schema, Scope, Semaphore, Stream } from "effect";
1
+ import { Cause, Clock, Context, Data, Duration, Effect, Encoding, ErrorReporter, Layer, Option, Predicate, Redacted, Ref, Schema, Scope, Semaphore, Stream } from "effect";
2
2
  import { PageScreenshotResult } from "effect-agent/page-screenshot";
3
3
  import { SandboxImplementation } from "effect-agent/sandbox";
4
4
  import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
5
5
  import puppeteer from "@cloudflare/puppeteer";
6
6
  import { BrowserActionResult, BrowserFileSelectionResult, BrowserNavigationResult, BrowserScreenshotRequest, BrowserScrollRequest, BrowserSelectFileRequest, BrowserTextResult, InteractiveBrowser, InteractiveBrowserActionError, InteractiveBrowserBusyError, InteractiveBrowserCapacityError, InteractiveBrowserExpiredError, InteractiveBrowserLimitError, InteractiveBrowserPolicy, InteractiveBrowserPolicyDeniedError, InteractiveBrowserProtocolError, InteractiveBrowserTargetUrl, InteractiveBrowserUnsupportedError } from "effect-agent/interactive-browser";
7
+ //#region src/internal/browser-failure.ts
8
+ /** Source-authored stages and classifications only; never retain SDK text or session capabilities. */
9
+ var BrowserRunFailure = class extends Data.TaggedError("BrowserRunFailure") {
10
+ [ErrorReporter.severity] = "Error";
11
+ };
12
+ /** A public projection of an already reported private failure must not produce another issue. */
13
+ const reportedBrowserError = (error) => Object.assign(error, { [ErrorReporter.ignore]: true });
14
+ const inheritBrowserReport = (original, error) => ErrorReporter.isIgnored(original) ? reportedBrowserError(error) : error;
15
+ const reasonSchema = Schema.Literals([
16
+ "provider",
17
+ "timeout",
18
+ "malformed",
19
+ "configuration",
20
+ "authorization",
21
+ "rate-limited",
22
+ "pending"
23
+ ]);
24
+ const statusSchema = Schema.Int.check(Schema.isBetween({
25
+ minimum: 100,
26
+ maximum: 599
27
+ }));
28
+ const failureNames = /* @__PURE__ */ new Set([
29
+ "TypeError",
30
+ "RangeError",
31
+ "SyntaxError",
32
+ "TimeoutError",
33
+ "SchemaError"
34
+ ]);
35
+ const browserFailure = (operation, cause) => cause instanceof BrowserRunFailure ? cause : new BrowserRunFailure({
36
+ operation,
37
+ reason: Predicate.isObject(cause) && "reason" in cause && Schema.is(reasonSchema)(cause.reason) ? cause.reason : "provider",
38
+ ...Predicate.isObject(cause) && "status" in cause && Schema.is(statusSchema)(cause.status) ? { status: cause.status } : {},
39
+ ...Predicate.isObject(cause) && "cause" in cause && cause.cause instanceof BrowserRunFailure ? { cause: cause.cause } : Predicate.isObject(cause) && "name" in cause && typeof cause.name === "string" && failureNames.has(cause.name) ? { cause: { name: cause.name } } : {}
40
+ });
41
+ /** Keep mixed defects and interruptions distinct while discarding all foreign diagnostic content. */
42
+ const reportBrowserCause = (operation, cause) => ErrorReporter.report(Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isFailReason(reason) || !ErrorReporter.isIgnored(reason.error)).map((reason) => {
43
+ switch (reason._tag) {
44
+ case "Fail": return Cause.makeFailReason(browserFailure(operation, reason.error));
45
+ case "Die": return Cause.makeDieReason(browserFailure(operation, reason.defect));
46
+ case "Interrupt": return reason;
47
+ }
48
+ })));
49
+ //#endregion
50
+ //#region src/internal/browser-binding.ts
51
+ const Acquired = Schema.Struct({ sessionId: Schema.String.check(Schema.isUUID()) });
52
+ /** Native transport port shared by both scoped adapters; the Layer owns the foreign binding. */
53
+ var BrowserRunBinding = class extends Context.Service()("@effect-agent/platform-cloudflare/internal/BrowserRunBinding") {
54
+ static layer(browser) {
55
+ return Layer.succeed(this)({
56
+ acquire: async (keepAliveMillis, operation) => {
57
+ const response = await browser.fetch(`https://browser-rendering.cloudflare.com/v1/devtools/browser?keep_alive=${keepAliveMillis}&recording=false`, { method: "POST" });
58
+ if (response.status !== 200) {
59
+ const failure = new BrowserRunFailure({
60
+ operation,
61
+ reason: "provider",
62
+ status: response.status
63
+ });
64
+ try {
65
+ await response.body?.cancel();
66
+ } catch {}
67
+ throw failure;
68
+ }
69
+ return Schema.decodeUnknownSync(Acquired)(await response.json()).sessionId;
70
+ },
71
+ connect: async (sessionId, operation) => {
72
+ let failure;
73
+ try {
74
+ return await puppeteer.connect({ fetch: async (input, init) => {
75
+ try {
76
+ const response = await browser.fetch(input, init);
77
+ if (response.status !== 101) {
78
+ failure = new BrowserRunFailure({
79
+ operation,
80
+ reason: "provider",
81
+ status: response.status
82
+ });
83
+ await response.body?.cancel();
84
+ throw failure;
85
+ }
86
+ return response;
87
+ } catch (cause) {
88
+ failure ??= browserFailure(operation, cause);
89
+ throw failure;
90
+ }
91
+ } }, sessionId);
92
+ } catch (cause) {
93
+ throw failure ?? browserFailure(operation, cause);
94
+ }
95
+ }
96
+ });
97
+ }
98
+ };
99
+ //#endregion
7
100
  //#region src/internal/browser-file-selection.ts
8
101
  function setFileSelection(element, encoded, name, type) {
9
102
  if (Reflect.get(element, "type") !== "file" || Reflect.get(element, "disabled") || Reflect.get(element, "webkitdirectory")) throw new Error("The target is not an enabled file input");
@@ -86,7 +179,8 @@ var BrowserRunCleanupError = class extends Schema.TaggedError()("BrowserRunClean
86
179
  "timeout",
87
180
  "pending"
88
181
  ]),
89
- status: Schema.optionalKey(Schema.Int)
182
+ status: Schema.optionalKey(Schema.Int),
183
+ cause: Schema.optionalKey(Schema.Defect())
90
184
  }) {};
91
185
  const Identity = Schema.String.check(Schema.isUUID());
92
186
  const Account = Schema.String.check(Schema.isPattern(/^[a-f0-9]{32}$/));
@@ -103,7 +197,10 @@ var BrowserRunSessionLifecycle = class extends Context.Service()("@effect-agent/
103
197
  const client = yield* HttpClient.HttpClient;
104
198
  const request = Effect.fn("BrowserRunSessionLifecycle.request")(function* (method, sessionId) {
105
199
  const path = method === "DELETE" ? "browser" : "session";
106
- const response = yield* client.execute(HttpClientRequest.make(method)(`https://api.cloudflare.com/client/v4/accounts/${options.accountId}/browser-rendering/devtools/${path}/${sessionId}`).pipe(HttpClientRequest.bearerToken(options.apiToken))).pipe(Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" }), Effect.mapError(() => new BrowserRunCleanupError({ reason: "provider" })));
200
+ const response = yield* client.execute(HttpClientRequest.make(method)(`https://api.cloudflare.com/client/v4/accounts/${options.accountId}/browser-rendering/devtools/${path}/${sessionId}`).pipe(HttpClientRequest.bearerToken(options.apiToken))).pipe(Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" }), Effect.mapError((cause) => new BrowserRunCleanupError({
201
+ reason: "provider",
202
+ cause: browserFailure("browser.close.request", cause)
203
+ })));
107
204
  if (response.status === 401 || response.status === 403) return yield* new BrowserRunCleanupError({
108
205
  reason: "authorization",
109
206
  status: response.status
@@ -123,21 +220,33 @@ var BrowserRunSessionLifecycle = class extends Context.Service()("@effect-agent/
123
220
  combined.set(body);
124
221
  combined.set(chunk, body.byteLength);
125
222
  return Effect.succeed(combined);
126
- }).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })));
223
+ }).pipe(Effect.mapError((cause) => new BrowserRunCleanupError({
224
+ reason: "malformed",
225
+ cause: browserFailure("browser.close.response", cause)
226
+ })));
127
227
  const body = yield* Effect.try({
128
228
  try: () => new TextDecoder("utf-8", {
129
229
  fatal: true,
130
230
  ignoreBOM: false
131
231
  }).decode(bytes),
132
- catch: () => new BrowserRunCleanupError({ reason: "malformed" })
232
+ catch: (cause) => new BrowserRunCleanupError({
233
+ reason: "malformed",
234
+ cause: browserFailure("browser.close.decode", cause)
235
+ })
133
236
  });
134
237
  if (response.status === 404) {
135
238
  const absent = Schema.decodeOption(Schema.fromJsonString(Absent))(body, { onExcessProperty: "error" });
136
239
  if (Option.isSome(absent)) return true;
137
240
  return yield* new BrowserRunCleanupError({ reason: "malformed" });
138
241
  }
139
- if (method === "DELETE") return (yield* Schema.decodeEffect(Schema.fromJsonString(Closed))(body).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })))).status === "closed";
140
- const result = yield* Schema.decodeEffect(Schema.fromJsonString(Metadata))(body).pipe(Effect.mapError(() => new BrowserRunCleanupError({ reason: "malformed" })));
242
+ if (method === "DELETE") return (yield* Schema.decodeEffect(Schema.fromJsonString(Closed))(body).pipe(Effect.mapError((cause) => new BrowserRunCleanupError({
243
+ reason: "malformed",
244
+ cause: browserFailure("browser.close.response", cause)
245
+ })))).status === "closed";
246
+ const result = yield* Schema.decodeEffect(Schema.fromJsonString(Metadata))(body).pipe(Effect.mapError((cause) => new BrowserRunCleanupError({
247
+ reason: "malformed",
248
+ cause: browserFailure("browser.close.response", cause)
249
+ })));
141
250
  if (result.sessionId !== sessionId) return yield* new BrowserRunCleanupError({ reason: "malformed" });
142
251
  return result.endTime !== void 0 && result.endTime > 0;
143
252
  });
@@ -302,13 +411,14 @@ var BrowserRunInteractiveBinding = class BrowserRunInteractiveBinding extends Co
302
411
  static layer(options) {
303
412
  return Layer.effect(BrowserRunInteractiveBinding)(Effect.gen(function* () {
304
413
  const lifecycle = yield* BrowserRunSessionLifecycle;
414
+ const binding = yield* BrowserRunBinding;
305
415
  const viewport = options.viewport === void 0 ? void 0 : yield* decodeViewport(options.viewport);
306
416
  return {
307
- acquire: async (keepAliveMillis) => (await puppeteer.acquire(options.browser, { keep_alive: keepAliveMillis })).sessionId,
308
- connect: async (sessionId) => makeProductionBrowser(await puppeteer.connect(options.browser, sessionId), viewport),
417
+ acquire: async (keepAliveMillis) => binding.acquire(keepAliveMillis, "interactive.acquire"),
418
+ connect: async (sessionId) => makeProductionBrowser(await binding.connect(sessionId, "interactive.connect"), viewport),
309
419
  closeSession: (sessionId) => lifecycle.close(sessionId).pipe(Effect.mapError((cause) => actionError("close", cause)))
310
420
  };
311
- }));
421
+ })).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
312
422
  }
313
423
  };
314
424
  /** Cloudflare host authority kept separate from the provider-neutral browser handle. */
@@ -529,7 +639,14 @@ const makeProductionPage = (page) => {
529
639
  const prepareFileSelection = makeFileSelection(page);
530
640
  const listeners = /* @__PURE__ */ new Map();
531
641
  return {
532
- close: () => page.close(),
642
+ close: async () => {
643
+ if (!page.browser().isConnected()) return;
644
+ try {
645
+ await page.close();
646
+ } catch (cause) {
647
+ if (!isRemoteClosure(cause)) throw cause;
648
+ }
649
+ },
533
650
  setBypassServiceWorker: (enabled) => page.setBypassServiceWorker(enabled),
534
651
  setRequestInterception: (enabled) => page.setRequestInterception(enabled),
535
652
  onRequest: (listener) => {
@@ -716,11 +833,25 @@ const makeProductionContext = (context, viewport) => ({
716
833
  if (viewport !== void 0) await page.setViewport(viewport);
717
834
  return makeProductionPage(page);
718
835
  },
719
- close: () => context.close()
836
+ close: async () => {
837
+ if (!context.browser().isConnected()) return;
838
+ try {
839
+ await context.close();
840
+ } catch (cause) {
841
+ if (!isRemoteClosure(cause)) throw cause;
842
+ }
843
+ }
720
844
  });
721
845
  const makeProductionBrowser = (browser, viewport) => ({
722
846
  createContext: async () => makeProductionContext(await browser.createBrowserContext(), viewport),
723
- close: () => browser.close(),
847
+ close: async () => {
848
+ if (!browser.isConnected()) return;
849
+ try {
850
+ await browser.close();
851
+ } catch (cause) {
852
+ if (!isRemoteClosure(cause)) throw cause;
853
+ }
854
+ },
724
855
  sessionId: () => browser.sessionId(),
725
856
  isConnected: () => browser.isConnected(),
726
857
  onDisconnected: (listener) => {
@@ -760,7 +891,7 @@ const causeText = (cause) => {
760
891
  if (cause instanceof Error) return cause.message.slice(0, 8e3);
761
892
  return String(cause).slice(0, 8e3);
762
893
  };
763
- const isCapacityRefusal = (cause) => /(^|\D)429(\D|$)|browser time limit|capacity|too many concurrent/i.test(causeText(cause));
894
+ const isCapacityRefusal = (cause) => cause instanceof BrowserRunFailure && cause.status === 429;
764
895
  const isRemoteClosure = (cause) => /target closed|browser.*closed|session.*closed|connection.*closed|not connected|websocket.*closed/i.test(causeText(cause));
765
896
  const snapshotPolicy = Effect.fn("BrowserRunInteractive.snapshotPolicy")(function* (input) {
766
897
  const decoded = yield* Schema.decodeEffect(InteractiveBrowserPolicy, { onExcessProperty: "error" })(input).pipe(Effect.mapError(() => policyError("The interactive browser policy is malformed")));
@@ -789,21 +920,22 @@ const hostAllowed = (policy, value) => {
789
920
  }
790
921
  };
791
922
  const keepAliveMillis = (policy) => Math.max(MIN_KEEP_ALIVE_MILLIS, Math.min(MAX_KEEP_ALIVE_MILLIS, policy.maxElapsedMillis));
923
+ var LateBrowserAcquisition = class extends Error {};
792
924
  const closeLateAcquisition = async (signal, acquire, close) => {
793
925
  const acquired = await acquire();
794
926
  if (!signal.aborted) return acquired;
795
927
  try {
796
928
  await close(acquired);
797
929
  } catch {}
798
- throw new Error("The interrupted browser acquisition completed late");
930
+ throw new LateBrowserAcquisition("The interrupted browser acquisition completed late");
799
931
  };
800
932
  const closeWithWarning = (close, warning) => Effect.tryPromise({
801
933
  try: close,
802
- catch: () => protocolError(warning)
934
+ catch: (cause) => protocolError(warning, cause)
803
935
  }).pipe(Effect.timeoutOrElse({
804
936
  duration: Duration.millis(CLEANUP_STEP_TIMEOUT_MILLIS),
805
937
  orElse: () => Effect.fail(protocolError(warning))
806
- }), Effect.catchCause(() => Effect.logWarning(warning)));
938
+ }), Effect.catchCause((cause) => reportBrowserCause("interactive.cleanup", cause).pipe(Effect.andThen(Effect.logWarning(warning)))));
807
939
  const deadlineError = Effect.fn("BrowserRunInteractive.deadlineError")(function* (policy, startedAt) {
808
940
  const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
809
941
  return yield* InteractiveBrowserLimitError.make({
@@ -867,12 +999,12 @@ const makeRequestListener = (policy, state) => (request) => {
867
999
  let settlement;
868
1000
  try {
869
1001
  settlement = allowed ? request.continue() : request.abort();
870
- } catch {
871
- state.violation.value = protocolError("Resolving an intercepted browser request failed");
1002
+ } catch (cause) {
1003
+ state.violation.value = protocolError("Resolving an intercepted browser request failed", cause);
872
1004
  return;
873
1005
  }
874
- const observed = settlement.catch(() => {
875
- state.violation.value = protocolError("Resolving an intercepted browser request failed");
1006
+ const observed = settlement.catch((cause) => {
1007
+ state.violation.value = protocolError("Resolving an intercepted browser request failed", cause);
876
1008
  }).finally(() => {
877
1009
  state.pendingRequests.delete(observed);
878
1010
  });
@@ -1084,15 +1216,25 @@ const runTeardown = (entries) => Effect.forEach([...entries].reverse(), (entry)
1084
1216
  onSuccess: () => void 0
1085
1217
  }))).pipe(Effect.map((failures) => failures.filter((failure) => failure !== void 0)));
1086
1218
  const cdpCommand = (page, state, command, parameters, output, malformedMessage) => Effect.scoped(Effect.gen(function* () {
1219
+ const cleanupContext = yield* Effect.context();
1087
1220
  const cdp = yield* Effect.acquireRelease(Effect.tryPromise({
1088
- try: (signal) => closeLateAcquisition(signal, page.createCdpSession, (acquired) => acquired.detach()),
1221
+ try: (signal) => {
1222
+ const pending = closeLateAcquisition(signal, page.createCdpSession, (acquired) => Effect.runPromiseWith(cleanupContext)(Effect.tryPromise({
1223
+ try: () => acquired.detach(),
1224
+ catch: (cause) => actionError("close", cause)
1225
+ }).pipe(Effect.tapCause((cause) => reportBrowserCause("interactive.lateDetach", cause)))));
1226
+ pending.then(void 0, (cause) => {
1227
+ if (signal.aborted && !(cause instanceof LateBrowserAcquisition)) return Effect.runPromiseWith(cleanupContext)(reportBrowserCause("interactive.connect", Cause.fail(cause)));
1228
+ });
1229
+ return pending;
1230
+ },
1089
1231
  catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("Creating the Cloudflare browser control session failed", cause)
1090
1232
  }), (acquired) => closeWithWarning(acquired.detach, "Detaching the Cloudflare browser control session failed"), { interruptible: true });
1091
1233
  const raw = yield* Effect.tryPromise({
1092
1234
  try: () => cdp.send(command, parameters),
1093
1235
  catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("The Cloudflare browser control command failed", cause)
1094
1236
  });
1095
- return yield* Schema.decodeUnknownEffect(output)(raw).pipe(Effect.mapError(() => protocolError(malformedMessage)));
1237
+ return yield* Schema.decodeUnknownEffect(output)(raw).pipe(Effect.catch((error) => reportBrowserCause("interactive.command.decode", Cause.fail(error)).pipe(Effect.andThen(Effect.fail(reportedBrowserError(protocolError(malformedMessage)))))));
1096
1238
  }));
1097
1239
  const makeHostService = (binding) => {
1098
1240
  const closeSession = binding.closeSession;
@@ -1104,20 +1246,27 @@ const makeHostService = (binding) => {
1104
1246
  }));
1105
1247
  const remaining = deadline - (yield* Clock.currentTimeMillis);
1106
1248
  if (remaining <= 0) return yield* Effect.logWarning("Local browser teardown skipped after confirmed termination deadline");
1107
- yield* runTeardown(entries).pipe(Effect.flatMap((failures) => Effect.forEach(failures, (failure) => Effect.logWarning(failure.warning), { discard: true })), Effect.interruptible, Effect.timeout(`${remaining} millis`), Effect.catchCause(() => Effect.logWarning("Local browser teardown incomplete after confirmed termination")));
1249
+ yield* runTeardown(entries).pipe(Effect.flatMap((failures) => Effect.forEach(failures, (failure) => reportBrowserCause("interactive.cleanup", Cause.fail(failure.error)).pipe(Effect.andThen(Effect.logWarning(failure.warning))), { discard: true })), Effect.interruptible, Effect.timeout(`${remaining} millis`), Effect.catchCause((cause) => reportBrowserCause("interactive.cleanup", cause).pipe(Effect.andThen(Effect.logWarning("Local browser teardown incomplete after confirmed termination")))));
1108
1250
  });
1109
1251
  const closeAcquired = Effect.fn("BrowserRunInteractiveHost.closeAcquired")(function* (browser) {
1110
1252
  const sessionId = yield* Effect.try({
1111
1253
  try: browser.sessionId,
1112
- catch: () => actionError("close")
1113
- }).pipe(Effect.flatMap(Schema.decodeUnknownEffect(BrowserRunSessionId)), Effect.mapError(() => actionError("close")), Effect.onError(() => closeWithWarning(browser.close, "Closing an unidentified browser failed")));
1254
+ catch: (cause) => actionError("close", cause)
1255
+ }).pipe(Effect.flatMap(Schema.decodeUnknownEffect(BrowserRunSessionId)), Effect.mapError((cause) => actionError("close", cause)), Effect.onError(() => closeWithWarning(browser.close, "Closing an unidentified browser failed")));
1114
1256
  yield* terminate(Redacted.make(sessionId), [closeEntry(browser.close, "Closing the local browser connection failed")]);
1115
1257
  });
1116
1258
  const acquire = Effect.fn("BrowserRunInteractiveHost.acquire")(function* (policy) {
1117
1259
  const scope = yield* Scope.Scope;
1118
1260
  const fixedPolicy = yield* snapshotPolicy(policy);
1119
1261
  const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
1120
- const runCleanup = Effect.runPromiseWith(Context.make(Clock.Clock, yield* Clock.Clock));
1262
+ const runCleanup = (effect) => Effect.runPromiseWith(cleanupContext)(effect.pipe(Effect.tapCause((cause) => reportBrowserCause("interactive.lateCleanup", cause))));
1263
+ const cleanupContext = yield* Effect.context();
1264
+ const observeLate = (signal, pending) => {
1265
+ pending.then(void 0, (cause) => {
1266
+ if (signal.aborted && !(cause instanceof LateBrowserAcquisition)) return runCleanup(reportBrowserCause("interactive.acquire", Cause.fail(cause)));
1267
+ });
1268
+ return pending;
1269
+ };
1121
1270
  const state = {
1122
1271
  closed: { value: false },
1123
1272
  disconnected: { value: false },
@@ -1127,9 +1276,9 @@ const makeHostService = (binding) => {
1127
1276
  };
1128
1277
  const lifecycle = { managedTeardownInstalled: false };
1129
1278
  const closers = [];
1130
- const releaseBeforeManaged = (entry) => Effect.suspend(() => lifecycle.managedTeardownInstalled ? Effect.void : entry.close.pipe(Effect.catchCause(() => Effect.logWarning(entry.warning))));
1279
+ const releaseBeforeManaged = (entry) => Effect.suspend(() => lifecycle.managedTeardownInstalled ? Effect.void : entry.close.pipe(Effect.catchCause((cause) => reportBrowserCause("interactive.cleanup", cause).pipe(Effect.andThen(Effect.logWarning(entry.warning))))));
1131
1280
  const sessionIdValue = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
1132
- try: (signal) => closeLateAcquisition(signal, () => binding.acquire(keepAliveMillis(fixedPolicy)), (id) => runCleanup(Schema.decodeUnknownEffect(BrowserRunSessionId)(id).pipe(Effect.flatMap((value) => terminate(Redacted.make(value), []))))),
1281
+ try: (signal) => observeLate(signal, closeLateAcquisition(signal, () => binding.acquire(keepAliveMillis(fixedPolicy)), (id) => runCleanup(Schema.decodeUnknownEffect(BrowserRunSessionId)(id).pipe(Effect.flatMap((value) => terminate(Redacted.make(value), [])))))),
1133
1282
  catch: (cause) => isCapacityRefusal(cause) ? InteractiveBrowserCapacityError.make({
1134
1283
  implementation: browserRunInteractiveImplementation,
1135
1284
  message: "Browser Run has no capacity for a new browser session"
@@ -1137,11 +1286,11 @@ const makeHostService = (binding) => {
1137
1286
  }).pipe(Effect.flatMap(Schema.decodeUnknownEffect(BrowserRunSessionId)), Effect.catchTag("SchemaError", () => protocolError("The Browser Run session identity was malformed"))), fixedPolicy, startedAt), (id) => Effect.suspend(() => {
1138
1287
  state.closed.value = true;
1139
1288
  state.disconnected.value = true;
1140
- return lifecycle.managedTeardownInstalled ? Effect.void : terminate(Redacted.make(id), []).pipe(Effect.catch(() => Effect.logWarning("Whole-browser cleanup remains unconfirmed")));
1289
+ return lifecycle.managedTeardownInstalled ? Effect.void : terminate(Redacted.make(id), []).pipe(Effect.catchCause((cause) => reportBrowserCause("interactive.close", cause).pipe(Effect.andThen(Effect.logWarning("Whole-browser cleanup remains unconfirmed")))));
1141
1290
  }), { interruptible: true });
1142
1291
  const connect = yield* Effect.cached(Effect.gen(function* () {
1143
1292
  const browser = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
1144
- try: (signal) => closeLateAcquisition(signal, () => binding.connect(sessionIdValue), (acquired) => runCleanup(closeAcquired(acquired))),
1293
+ try: (signal) => observeLate(signal, closeLateAcquisition(signal, () => binding.connect(sessionIdValue), (acquired) => runCleanup(closeAcquired(acquired)))),
1145
1294
  catch: (cause) => protocolError("Connecting to the Browser Run session failed", cause)
1146
1295
  }), fixedPolicy, startedAt), (acquired) => releaseBeforeManaged(closeEntry(acquired.close, "Closing the local browser connection failed")), { interruptible: true });
1147
1296
  closers.push(closeEntry(browser.close, "Closing the interactive browser failed"));
@@ -1161,12 +1310,18 @@ const makeHostService = (binding) => {
1161
1310
  return yield* expiredError();
1162
1311
  }
1163
1312
  const context = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
1164
- try: (signal) => closeLateAcquisition(signal, browser.createContext, (acquired) => acquired.close()),
1313
+ try: (signal) => observeLate(signal, closeLateAcquisition(signal, browser.createContext, (acquired) => runCleanup(Effect.tryPromise({
1314
+ try: () => acquired.close(),
1315
+ catch: (cause) => actionError("close", cause)
1316
+ })))),
1165
1317
  catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("Creating the browser context failed", cause)
1166
1318
  }), fixedPolicy, startedAt), (acquired) => releaseBeforeManaged(closeEntry(acquired.close, "Closing the interactive browser context failed")), { interruptible: true });
1167
1319
  closers.push(closeEntry(context.close, "Closing the interactive browser context failed"));
1168
1320
  const page = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
1169
- try: (signal) => closeLateAcquisition(signal, context.newPage, (acquired) => acquired.close()),
1321
+ try: (signal) => observeLate(signal, closeLateAcquisition(signal, context.newPage, (acquired) => runCleanup(Effect.tryPromise({
1322
+ try: () => acquired.close(),
1323
+ catch: (cause) => actionError("close", cause)
1324
+ })))),
1170
1325
  catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("Creating the browser page failed", cause)
1171
1326
  }), fixedPolicy, startedAt), (acquired) => releaseBeforeManaged(closeEntry(acquired.close, "Closing the interactive browser page failed")), { interruptible: true });
1172
1327
  closers.push(closeEntry(page.close, "Closing the interactive browser page failed"));
@@ -1188,12 +1343,12 @@ const makeHostService = (binding) => {
1188
1343
  const setupFailure = stateFailure(state);
1189
1344
  if (setupFailure !== void 0) return yield* setupFailure;
1190
1345
  const teardown = yield* Effect.uninterruptible(Effect.gen(function* () {
1191
- const cached = yield* Effect.cached(terminate(Redacted.make(sessionIdValue), closers));
1346
+ const cached = yield* Effect.cached(terminate(Redacted.make(sessionIdValue), closers).pipe(Effect.tapCause((cause) => reportBrowserCause("interactive.close", cause)), Effect.mapError(reportedBrowserError)));
1192
1347
  lifecycle.managedTeardownInstalled = true;
1193
1348
  yield* Effect.addFinalizer(() => Effect.uninterruptible(Effect.sync(() => {
1194
1349
  state.closed.value = true;
1195
1350
  state.disconnected.value = true;
1196
- }).pipe(Effect.andThen(cached), Effect.catch(() => Effect.logWarning("Whole-browser cleanup remains unconfirmed")))));
1351
+ }).pipe(Effect.andThen(cached), Effect.catchCause((cause) => reportBrowserCause("interactive.close", cause).pipe(Effect.andThen(Effect.logWarning("Whole-browser cleanup remains unconfirmed")))))));
1197
1352
  return cached;
1198
1353
  }));
1199
1354
  const close = Effect.uninterruptible(Effect.sync(() => {
@@ -1267,6 +1422,6 @@ const CloudflareInteractiveBrowser = {
1267
1422
  hostLayer: (options) => browserRunInteractiveHostLayer().pipe(Layer.provide(interactiveBindingLayer(options)))
1268
1423
  };
1269
1424
  //#endregion
1270
- export { BrowserRunInteractiveHost as a, BrowserRunViewport as c, browserRunInteractiveImplementation as d, browserRunInteractiveLayer as f, BrowserRunSessionLifecycle as h, BrowserRunInteractiveBinding as i, CloudflareInteractiveBrowser as l, BrowserRunCleanupError as m, BrowserRunHandoffResult as n, BrowserRunLiveViewRequest as o, isBrowserRunUndispatchedActionError as p, BrowserRunHandoffState as r, BrowserRunLiveViewResult as s, BrowserRunHandoffRequest as t, browserRunInteractiveHostLayer as u };
1425
+ export { BrowserRunFailure as _, BrowserRunInteractiveHost as a, reportBrowserCause as b, BrowserRunViewport as c, browserRunInteractiveImplementation as d, browserRunInteractiveLayer as f, BrowserRunBinding as g, BrowserRunSessionLifecycle as h, BrowserRunInteractiveBinding as i, CloudflareInteractiveBrowser as l, BrowserRunCleanupError as m, BrowserRunHandoffResult as n, BrowserRunLiveViewRequest as o, isBrowserRunUndispatchedActionError as p, BrowserRunHandoffState as r, BrowserRunLiveViewResult as s, BrowserRunHandoffRequest as t, browserRunInteractiveHostLayer as u, browserFailure as v, reportedBrowserError as x, inheritBrowserReport as y };
1271
1426
 
1272
- //# sourceMappingURL=InteractiveBrowser-xVNX7ARl.mjs.map
1427
+ //# sourceMappingURL=InteractiveBrowser-l6J4eP6T.mjs.map