@effect-agent/platform-cloudflare 0.1.0-beta.120 → 0.1.0-beta.121

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.
@@ -705,20 +705,17 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
705
705
  } | {
706
706
  readonly _tag: "AdminFailed";
707
707
  readonly failure: {
708
- readonly _tag: "ThreadNotMaterialized";
709
- readonly threadId: string;
710
- } | {
711
- readonly _tag: "ThreadStoreError";
708
+ readonly _tag: "DurableAlarmError";
712
709
  readonly operation: string;
713
710
  readonly message: string;
714
711
  readonly cause?: Schema.Json | undefined;
715
- readonly diagnostic?: {
716
- readonly causeTag: string;
717
- readonly operation: string;
718
- readonly decoder?: string | undefined;
719
- readonly issueTag?: string | undefined;
720
- readonly sequence?: number | undefined;
721
- } | undefined;
712
+ } | {
713
+ readonly _tag: "HostProtocolError";
714
+ readonly message: string;
715
+ } | {
716
+ readonly _tag: "DigestError";
717
+ readonly message: string;
718
+ readonly cause?: Schema.Json | undefined;
722
719
  } | {
723
720
  readonly _tag: "AdmissionPolicyError";
724
721
  readonly reason: "occupied" | "refused" | "unavailable";
@@ -734,6 +731,21 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
734
731
  readonly operation: string;
735
732
  readonly message: string;
736
733
  readonly cause?: import("effect-agent/failure-diagnostic").Diagnostic | undefined;
734
+ } | {
735
+ readonly _tag: "ThreadStoreError";
736
+ readonly operation: string;
737
+ readonly message: string;
738
+ readonly cause?: Schema.Json | undefined;
739
+ readonly diagnostic?: {
740
+ readonly causeTag: string;
741
+ readonly operation: string;
742
+ readonly decoder?: string | undefined;
743
+ readonly issueTag?: string | undefined;
744
+ readonly sequence?: number | undefined;
745
+ } | undefined;
746
+ } | {
747
+ readonly _tag: "ThreadNotMaterialized";
748
+ readonly threadId: string;
737
749
  } | {
738
750
  readonly _tag: "AppendConflict";
739
751
  readonly threadId: string;
@@ -746,30 +758,18 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
746
758
  readonly threadId: string;
747
759
  readonly actualEpoch: number;
748
760
  readonly attemptedEpoch: number;
749
- } | {
750
- readonly _tag: "DurableAlarmError";
751
- readonly operation: string;
752
- readonly message: string;
753
- readonly cause?: Schema.Json | undefined;
754
- } | {
755
- readonly _tag: "HostProtocolError";
756
- readonly message: string;
757
- } | {
758
- readonly _tag: "DigestError";
759
- readonly message: string;
760
- readonly cause?: Schema.Json | undefined;
761
761
  } | {
762
762
  readonly _tag: "DurableRuntimeFailpointError";
763
763
  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-stop-append" | "worker:after-stop-seal" | "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-stop-append" | "worker:before-stop-seal" | "worker:before-subtree-append";
764
- } | {
765
- readonly _tag: "ApprovalSuspensionError";
766
- readonly cause: Schema.Json;
767
764
  } | {
768
765
  readonly _tag: "OperationDenied";
769
766
  readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
770
767
  readonly reason: string;
771
768
  readonly threadId?: string | undefined;
772
769
  readonly submissionId?: string | undefined;
770
+ } | {
771
+ readonly _tag: "ApprovalSuspensionError";
772
+ readonly cause: Schema.Json;
773
773
  } | {
774
774
  readonly _tag: "RetryRefused";
775
775
  readonly submissionId: string;
@@ -836,4 +836,4 @@ interface Class<EventServices = never> {
836
836
  declare const make: <ApplicationServices, ApplicationError, EventServices = never, EventLayerError = never>(applicationLayer: Layer.Layer<CloudflareDurableRuntimeServices | ApplicationServices, ApplicationError, CloudflareBootstrapServices | DurableObjectState$1.DurableObjectState | WorkerEnvironment | DurableObjectContext | ThreadObjectNamespace>, options: Options<ApplicationServices, EventServices, EventLayerError>) => Class<ApplicationServices | EventServices>;
837
837
  //#endregion
838
838
  export { ThreadPublicationOptions as A, portCall as C, CloudflareDurableRuntimeOptions as D, CloudflareDurableRuntimeInitializationError as E, layerConfig as M, layerHostConfig as N, CloudflareDurableRuntimeServices as O, layerInHost as P, make as S, CloudflareBootstrapServices as T, decodeAdminVerifyRequest as _, AdminVerifyRequest as a, encodeAdminResponse as b, Instance as c, RetryExecuted as d, ThreadObject_d_exports as f, decodeAdminResponse as g, decodeAdminExplainRequest as h, AdminResponse as i, layer as j, ThreadObjectPorts as k, ObligationsScanned as l, VerifiedIntegrity as m, AdminFailed as n, Class as o, ThreadRpcOperation as p, AdminFailure as r, ExplainedRecovery as s, AdminExplainRequest as t, Options as u, decodeObligationThresholds as v, submit as w, handleRpc as x, decodeRetryCommand as y };
839
- //# sourceMappingURL=ThreadObject-9IVibJM7.d.mts.map
839
+ //# sourceMappingURL=ThreadObject-mt0uxvZZ.d.mts.map
@@ -1,2 +1,2 @@
1
- import { A as ThreadPublicationOptions, C as portCall, D as CloudflareDurableRuntimeOptions, E as CloudflareDurableRuntimeInitializationError, M as layerConfig, N as layerHostConfig, O as CloudflareDurableRuntimeServices, P as layerInHost, S as make, T as CloudflareBootstrapServices, _ as decodeAdminVerifyRequest, a as AdminVerifyRequest, b as encodeAdminResponse, c as Instance, d as RetryExecuted, g as decodeAdminResponse, h as decodeAdminExplainRequest, i as AdminResponse, j as layer, k as ThreadObjectPorts, l as ObligationsScanned, m as VerifiedIntegrity, n as AdminFailed, o as Class, p as ThreadRpcOperation, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeObligationThresholds, w as submit, x as handleRpc, y as decodeRetryCommand } from "./ThreadObject-9IVibJM7.mjs";
1
+ import { A as ThreadPublicationOptions, C as portCall, D as CloudflareDurableRuntimeOptions, E as CloudflareDurableRuntimeInitializationError, M as layerConfig, N as layerHostConfig, O as CloudflareDurableRuntimeServices, P as layerInHost, S as make, T as CloudflareBootstrapServices, _ as decodeAdminVerifyRequest, a as AdminVerifyRequest, b as encodeAdminResponse, c as Instance, d as RetryExecuted, g as decodeAdminResponse, h as decodeAdminExplainRequest, i as AdminResponse, j as layer, k as ThreadObjectPorts, l as ObligationsScanned, m as VerifiedIntegrity, n as AdminFailed, o as Class, p as ThreadRpcOperation, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeObligationThresholds, w as submit, x as handleRpc, y as decodeRetryCommand } from "./ThreadObject-mt0uxvZZ.mjs";
2
2
  export { AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, type CloudflareBootstrapServices as BootstrapServices, Class, ExplainedRecovery, type CloudflareDurableRuntimeInitializationError as InitializationError, Instance, ObligationsScanned, Options, type ThreadPublicationOptions as PublicationOptions, RetryExecuted, type CloudflareDurableRuntimeOptions as RuntimeOptions, type CloudflareDurableRuntimeServices as Services, ThreadObjectPorts, ThreadRpcOperation, VerifiedIntegrity, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeObligationThresholds, decodeRetryCommand, encodeAdminResponse, handleRpc, layer, layerConfig, layerHostConfig, layerInHost, make, portCall, submit };
package/dist/index.d.mts CHANGED
@@ -8,6 +8,6 @@ import { t as CloudflareMemory_d_exports } from "./CloudflareMemory.mjs";
8
8
  import { t as CloudflareScheduling_d_exports } from "./CloudflareScheduling.mjs";
9
9
  import { t as CloudflareSubscriptions_d_exports } from "./CloudflareSubscriptions.mjs";
10
10
  import { t as CloudflareThreadClient_d_exports } from "./CloudflareThreadClient.mjs";
11
- import { f as ThreadObject_d_exports } from "./ThreadObject-9IVibJM7.mjs";
11
+ import { f as ThreadObject_d_exports } from "./ThreadObject-mt0uxvZZ.mjs";
12
12
  import { t as WakeScheduler_d_exports } from "./WakeScheduler.mjs";
13
13
  export { Alarm_d_exports as Alarm, CloudflareAiGateway_d_exports as CloudflareAiGateway, CloudflareBindings_d_exports as CloudflareBindings, CloudflareBrowser_d_exports as CloudflareBrowser, CloudflareCodeMode_d_exports as CloudflareCodeMode, CloudflareConfig_d_exports as CloudflareConfig, CloudflareMemory_d_exports as CloudflareMemory, CloudflareScheduling_d_exports as CloudflareScheduling, CloudflareSubscriptions_d_exports as CloudflareSubscriptions, CloudflareThreadClient_d_exports as CloudflareThreadClient, ThreadObject_d_exports as ThreadObject, WakeScheduler_d_exports as WakeScheduler };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.120","dependencies":{"@effect-agent/storage-cloudflare":"0.1.0-beta.120","@effect/platform-browser":"4.0.0-rc.116","@effect/sql-sqlite-do":"4.0.0-rc.116","effect-agent":"0.1.0-beta.120"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/testing":"0.1.0-beta.120","@effect/platform-node":"4.0.0-rc.116","@effect/sql-d1":"4.0.0-rc.116","@effect/vitest":"4.0.0-rc.116","effect":"4.0.0-rc.116","effect-cf":"0.44.1","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.2","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.116","effect-cf":"^0.44.1 || ^0.45.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./browser-rest-capture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./browser-rest-crawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./cloudflare-bindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./cloudflare-browser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./cloudflare-code-mode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./cloudflare-config":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./cloudflare-memory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./cloudflare-scheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./cloudflare-subscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./cloudflare-thread-client":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./interactive-browser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./protected-browser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./thread-object":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./wake-scheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"},"./cloudflare-ai-gateway":{"types":"./dist/CloudflareAiGateway.d.mts","default":"./dist/CloudflareAiGateway.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
1
+ {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.121","dependencies":{"@effect-agent/storage-cloudflare":"0.1.0-beta.121","@effect/platform-browser":"4.0.0-rc.116","@effect/sql-sqlite-do":"4.0.0-rc.116","effect-agent":"0.1.0-beta.121","puppeteer-core":"24.29.0"},"devDependencies":{"@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/testing":"0.1.0-beta.121","@effect/platform-node":"4.0.0-rc.116","@effect/sql-d1":"4.0.0-rc.116","@effect/vitest":"4.0.0-rc.116","effect":"4.0.0-rc.116","effect-cf":"0.44.1","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.2","vitest":"4.1.11"},"peerDependencies":{"effect":"^4.0.0-rc.116","effect-cf":"^0.44.1 || ^0.45.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./browser-rest-capture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./browser-rest-crawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./cloudflare-bindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./cloudflare-browser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./cloudflare-code-mode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./cloudflare-config":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./cloudflare-memory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./cloudflare-scheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./cloudflare-subscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./cloudflare-thread-client":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./interactive-browser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./protected-browser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./thread-object":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./wake-scheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"},"./cloudflare-ai-gateway":{"types":"./dist/CloudflareAiGateway.d.mts","default":"./dist/CloudflareAiGateway.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"}}
@@ -1,12 +1,5 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
2
 
3
- import {
4
- type Browser,
5
- type BrowserContext,
6
- type CDPSession,
7
- type HTTPRequest,
8
- type Page,
9
- } from "@cloudflare/puppeteer";
10
3
  import {
11
4
  Cause,
12
5
  Context,
@@ -47,6 +40,13 @@ import {
47
40
  } from "effect-agent/interactive-browser";
48
41
  import { PageScreenshotResult } from "effect-agent/page-screenshot";
49
42
  import { SandboxImplementation } from "effect-agent/sandbox";
43
+ import {
44
+ type Browser,
45
+ type BrowserContext,
46
+ type CDPSession,
47
+ type HTTPRequest,
48
+ type Page,
49
+ } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
50
50
 
51
51
  import { BrowserRunBinding } from "./internal/browser-binding.ts";
52
52
  import {
@@ -380,7 +380,10 @@ export class BrowserRunInteractiveBinding extends Context.Service<
380
380
  BrowserRunInteractiveBinding,
381
381
  {
382
382
  readonly acquire: (keepAliveMillis: number) => Promise<unknown>;
383
- readonly connect: (sessionId: string) => Promise<BrowserRunInteractiveBrowser>;
383
+ readonly connect: (
384
+ sessionId: string,
385
+ signal?: AbortSignal,
386
+ ) => Promise<BrowserRunInteractiveBrowser>;
384
387
  /** Success proves whole-browser termination or exact-session absence. */
385
388
  readonly closeSession: (
386
389
  sessionId: Redacted.Redacted<string>,
@@ -406,9 +409,10 @@ export class BrowserRunInteractiveBinding extends Context.Service<
406
409
  return {
407
410
  acquire: async (keepAliveMillis: number) =>
408
411
  binding.acquire(keepAliveMillis, "interactive.acquire"),
409
- connect: async (sessionId: string) =>
412
+ connect: async (sessionId: string, signal?: AbortSignal) =>
410
413
  makeProductionBrowser(
411
- await binding.connect(sessionId, "interactive.connect"),
414
+ await binding.connect(sessionId, "interactive.connect", signal),
415
+ sessionId,
412
416
  viewport,
413
417
  ),
414
418
  closeSession: (sessionId: Redacted.Redacted<string>) =>
@@ -1385,6 +1389,7 @@ const makeProductionContext = (
1385
1389
 
1386
1390
  const makeProductionBrowser = (
1387
1391
  browser: Browser,
1392
+ sessionId: string,
1388
1393
  viewport?: BrowserRunViewport,
1389
1394
  ): BrowserRunInteractiveBrowser => ({
1390
1395
  detach: () => browser.disconnect(),
@@ -1417,7 +1422,7 @@ const makeProductionBrowser = (
1417
1422
  if (!isRemoteClosure(cause)) throw cause;
1418
1423
  }
1419
1424
  },
1420
- sessionId: () => browser.sessionId(),
1425
+ sessionId: () => sessionId,
1421
1426
  isConnected: () => browser.isConnected(),
1422
1427
  onDisconnected: (listener) => {
1423
1428
  browser.on("disconnected", listener);
@@ -2673,7 +2678,7 @@ const makeHostService = (
2673
2678
  signal,
2674
2679
  closeLateAcquisition(
2675
2680
  signal,
2676
- () => binding.connect(sessionIdValue),
2681
+ () => binding.connect(sessionIdValue, signal),
2677
2682
  (acquired) =>
2678
2683
  runCleanup(
2679
2684
  resume === undefined
@@ -1,8 +1,11 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import puppeteer, { type Browser } from "@cloudflare/puppeteer";
3
2
  import { Context, Layer, Schema } from "effect";
3
+ import puppeteer, {
4
+ type Browser,
5
+ type ConnectionTransport,
6
+ } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
4
7
 
5
- import { browserFailure, BrowserRunFailure } from "./browser-failure.ts";
8
+ import { browserFailure, BrowserRunFailure, reportedBrowserError } from "./browser-failure.ts";
6
9
 
7
10
  const Acquired = Schema.Struct({ sessionId: Schema.String.check(Schema.isUUID()) });
8
11
 
@@ -14,7 +17,11 @@ export class BrowserRunBinding extends Context.Service<
14
17
  keepAliveMillis: number,
15
18
  operation: "protected.acquire" | "interactive.acquire",
16
19
  ) => Promise<string>;
17
- readonly connect: (sessionId: string, operation: string) => Promise<Browser>;
20
+ readonly connect: (
21
+ sessionId: string,
22
+ operation: string,
23
+ signal?: AbortSignal,
24
+ ) => Promise<Browser>;
18
25
  }
19
26
  >()("@effect-agent/platform-cloudflare/internal/BrowserRunBinding") {
20
27
  static layer(browser: Pick<BrowserRun, "fetch">) {
@@ -43,38 +50,81 @@ export class BrowserRunBinding extends Context.Service<
43
50
 
44
51
  return Schema.decodeUnknownSync(Acquired)(await response.json()).sessionId;
45
52
  },
46
- // The SDK embeds bodies and session IDs in errors; retain status before that loss.
47
- connect: async (sessionId, operation) => {
48
- let failure: BrowserRunFailure | undefined;
53
+ connect: async (sessionId, operation, signal) => {
54
+ let socket: WebSocket | undefined;
55
+ let closedByAbort = false;
49
56
 
50
57
  try {
51
- return await puppeteer.connect(
52
- {
53
- fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
54
- try {
55
- const response = await browser.fetch(input, init);
56
-
57
- if (response.status !== 101) {
58
- failure = new BrowserRunFailure({
59
- operation,
60
- reason: "provider",
61
- status: response.status,
62
- });
63
- await response.body?.cancel();
64
- throw failure;
65
- }
66
-
67
- return response;
68
- } catch (cause) {
69
- failure ??= browserFailure(operation, cause);
70
- throw failure;
71
- }
72
- },
73
- },
74
- sessionId,
58
+ const response = await browser.fetch(
59
+ `https://browser-rendering.cloudflare.com/v1/devtools/browser/${sessionId}`,
60
+ { headers: { Upgrade: "websocket" }, ...(signal === undefined ? {} : { signal }) },
75
61
  );
62
+
63
+ if (response.status !== 101) {
64
+ const failure = new BrowserRunFailure({
65
+ operation,
66
+ reason: "provider",
67
+ status: response.status,
68
+ });
69
+
70
+ try {
71
+ await response.body?.cancel();
72
+ } catch {
73
+ // Body cancellation cannot replace the provider refusal.
74
+ }
75
+ throw failure;
76
+ }
77
+ const connected = response.webSocket;
78
+
79
+ if (connected === null) throw new BrowserRunFailure({ operation, reason: "malformed" });
80
+ socket = connected;
81
+
82
+ const transport: ConnectionTransport = {
83
+ send: (message) => connected.send(message),
84
+ close: () => connected.close(),
85
+ };
86
+
87
+ connected.addEventListener("message", (event) => {
88
+ transport.onmessage?.(event.data);
89
+ });
90
+ connected.addEventListener("close", () => transport.onclose?.());
91
+ connected.accept();
92
+
93
+ const abort = () => {
94
+ try {
95
+ const wasOpen = connected.readyState === WebSocket.OPEN;
96
+
97
+ connected.close();
98
+ closedByAbort = wasOpen;
99
+ } catch {
100
+ // The local transport may already have closed; never terminate the provider here.
101
+ }
102
+ };
103
+
104
+ signal?.addEventListener("abort", abort, { once: true });
105
+ try {
106
+ signal?.throwIfAborted();
107
+
108
+ return await puppeteer.connect({ transport });
109
+ } finally {
110
+ signal?.removeEventListener("abort", abort);
111
+ }
76
112
  } catch (cause) {
77
- throw failure ?? browserFailure(operation, cause);
113
+ try {
114
+ socket?.close();
115
+ } catch {
116
+ // Preserve the connection failure when local release also fails.
117
+ }
118
+ const failure = browserFailure(operation, cause);
119
+
120
+ // A local abort can reject SDK initialization as target-closed. Genuine late
121
+ // provider failures still report, even when the caller's signal has aborted.
122
+ const localCancellation =
123
+ cause instanceof Error &&
124
+ signal?.aborted === true &&
125
+ (cause === signal.reason || (closedByAbort && cause.name === "TargetCloseError"));
126
+
127
+ throw localCancellation ? reportedBrowserError(failure) : failure;
78
128
  }
79
129
  },
80
130
  });
@@ -1,6 +1,6 @@
1
- import type { Page } from "@cloudflare/puppeteer";
2
1
  import { Encoding } from "effect";
3
2
  import type { BrowserSelectFileRequest } from "effect-agent/interactive-browser";
3
+ import type { Page } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
4
4
 
5
5
  type Element = NonNullable<Awaited<ReturnType<Page["$"]>>>;
6
6
 
@@ -1,8 +1,13 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import { type Browser, type Page, type CDPSession } from "@cloudflare/puppeteer";
2
+
3
3
  import { Cause, Context, Crypto, Effect, Layer, Redacted, Schema, type Scope } from "effect";
4
4
  import { type InteractiveBrowserPolicy } from "effect-agent/interactive-browser";
5
5
  import { ProtectedBrowserError } from "effect-agent/protected-browser";
6
+ import {
7
+ type Browser,
8
+ type Page,
9
+ type CDPSession,
10
+ } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
6
11
 
7
12
  import { BrowserRunBinding } from "../internal/browser-binding.ts";
8
13
  import {
@@ -89,23 +94,29 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
89
94
  driver?.invalidate();
90
95
  if (sessionId === undefined) return "unconfirmed" as const;
91
96
 
92
- const cleanup = yield* lifecycle.close(sessionId).pipe(
93
- Effect.as("confirmed" as const),
94
- Effect.catchCause((cause) =>
95
- reportBrowserCause("protected.close", cause).pipe(Effect.as("unconfirmed" as const)),
96
- ),
97
- Effect.interruptible,
98
- Effect.timeoutOrElse({
99
- duration: "10 seconds",
100
- orElse: () =>
101
- reportBrowserCause(
102
- "protected.close",
103
- Cause.fail(
104
- new BrowserRunFailure({ operation: "protected.close", reason: "timeout" }),
97
+ // A failed resume has not acquired the host's retained provider. Release only
98
+ // this connection; successful attachments and new allocations own termination.
99
+ const cleanup = yield* identity !== undefined && driver === undefined
100
+ ? Effect.succeed("unconfirmed" as const)
101
+ : lifecycle.close(sessionId).pipe(
102
+ Effect.as("confirmed" as const),
103
+ Effect.catchCause((cause) =>
104
+ reportBrowserCause("protected.close", cause).pipe(
105
+ Effect.as("unconfirmed" as const),
105
106
  ),
106
- ).pipe(Effect.as("unconfirmed" as const)),
107
- }),
108
- );
107
+ ),
108
+ Effect.interruptible,
109
+ Effect.timeoutOrElse({
110
+ duration: "10 seconds",
111
+ orElse: () =>
112
+ reportBrowserCause(
113
+ "protected.close",
114
+ Cause.fail(
115
+ new BrowserRunFailure({ operation: "protected.close", reason: "timeout" }),
116
+ ),
117
+ ).pipe(Effect.as("unconfirmed" as const)),
118
+ }),
119
+ );
109
120
 
110
121
  // Local disconnect is not remote-closure evidence. Do it even when confirmation fails.
111
122
  const connected = browser;
@@ -155,7 +166,7 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
155
166
  }
156
167
  // Resume only a host-persisted exact page. Never open a replacement page or context.
157
168
  stage = "protected.connect";
158
- browser = await binding.connect(Redacted.value(sessionId), stage);
169
+ browser = await binding.connect(Redacted.value(sessionId), stage, signal);
159
170
  if (signal.aborted || invalid) {
160
171
  await runCleanup(terminate);
161
172
  throw new ProtectedTransportError({ reason: "stale-reference" });
@@ -262,7 +273,8 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
262
273
  new ProtectedBrowserError({
263
274
  ...failure(),
264
275
  reason: error.reason === "timeout" ? "timeout" : "provider",
265
- cleanup,
276
+ cleanup:
277
+ identity !== undefined && driver === undefined ? "not-requested" : cleanup,
266
278
  }),
267
279
  ),
268
280
  ),
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import type { Browser, Frame, JSHandle, Page } from "@cloudflare/puppeteer";
2
+
3
3
  import { Cause, Clock, Context, Crypto, Effect, Redacted, Schema, type Scope } from "effect";
4
4
  import { type InteractiveBrowserPolicy } from "effect-agent/interactive-browser";
5
5
  import {
@@ -7,6 +7,12 @@ import {
7
7
  CredentialTarget,
8
8
  ProtectedBrowserControl,
9
9
  } from "effect-agent/protected-browser";
10
+ import type {
11
+ Browser,
12
+ Frame,
13
+ JSHandle,
14
+ Page,
15
+ } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
10
16
 
11
17
  import {
12
18
  browserFailure,
@@ -22,14 +28,6 @@ import {
22
28
  type ProtectedBrowserTransport,
23
29
  } from "./policy.ts";
24
30
 
25
- // The pinned SDK implements this method but strips its internal declaration from lib/types.d.ts.
26
- // Keep the declaration narrow and refuse runtimes without it. No main-world fallback is safe.
27
- declare module "@cloudflare/puppeteer" {
28
- interface Frame {
29
- isolatedRealm(): { evaluateHandle(source: string): Promise<JSHandle<unknown>> };
30
- }
31
- }
32
-
33
31
  const Description = Schema.Struct({
34
32
  role: ProtectedBrowserControl.fields.role,
35
33
  formIndex: Schema.Natural,