@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.
- package/dist/CloudflareThreadClient.d.mts +33 -33
- package/dist/{InteractiveBrowser-B3S8if51.d.mts → InteractiveBrowser-CKp6pqmw.d.mts} +2 -1
- package/dist/{InteractiveBrowser-xVNX7ARl.mjs → InteractiveBrowser-l6J4eP6T.mjs} +192 -37
- package/dist/InteractiveBrowser-l6J4eP6T.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +1 -1
- package/dist/InteractiveBrowser.mjs +1 -1
- package/dist/ProtectedBrowser.d.mts +3 -3
- package/dist/ProtectedBrowser.mjs +160 -135
- package/dist/ProtectedBrowser.mjs.map +1 -1
- package/dist/{ThreadObject-CnqCuWlA.d.mts → ThreadObject-D_vvj7Qx.d.mts} +33 -33
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/index.d.mts +1 -1
- package/package.json +1 -1
- package/src/InteractiveBrowser.ts +189 -46
- package/src/internal/browser-binding.ts +82 -0
- package/src/internal/browser-failure.ts +92 -0
- package/src/internal/browser-session-lifecycle.ts +38 -5
- package/src/protected-browser/binding.ts +170 -129
- package/src/protected-browser/host.ts +52 -19
- package/src/protected-browser/native.ts +45 -17
- package/src/protected-browser/policy.ts +75 -39
- package/dist/InteractiveBrowser-xVNX7ARl.mjs.map +0 -1
|
@@ -705,18 +705,24 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
|
|
|
705
705
|
} | {
|
|
706
706
|
readonly _tag: "AdminFailed";
|
|
707
707
|
readonly failure: {
|
|
708
|
-
readonly _tag: "
|
|
708
|
+
readonly _tag: "DurableAlarmError";
|
|
709
709
|
readonly operation: string;
|
|
710
710
|
readonly message: string;
|
|
711
711
|
readonly cause?: Schema.Json | undefined;
|
|
712
712
|
} | {
|
|
713
|
-
readonly _tag: "
|
|
714
|
-
readonly
|
|
713
|
+
readonly _tag: "HostProtocolError";
|
|
714
|
+
readonly message: string;
|
|
715
715
|
} | {
|
|
716
716
|
readonly _tag: "LedgerError";
|
|
717
717
|
readonly operation: string;
|
|
718
718
|
readonly message: string;
|
|
719
719
|
readonly cause?: import("effect-agent/failure-diagnostic").Diagnostic | undefined;
|
|
720
|
+
} | {
|
|
721
|
+
readonly _tag: "AdmissionPolicyError";
|
|
722
|
+
readonly reason: "occupied" | "refused" | "unavailable";
|
|
723
|
+
readonly code: string;
|
|
724
|
+
readonly cause?: import("effect-agent/failure-diagnostic").Diagnostic | undefined;
|
|
725
|
+
readonly stack?: string | undefined;
|
|
720
726
|
} | {
|
|
721
727
|
readonly _tag: "AppendConflict";
|
|
722
728
|
readonly threadId: string;
|
|
@@ -724,23 +730,6 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
|
|
|
724
730
|
readonly reason: "batch-digest" | "record-identity" | "tail";
|
|
725
731
|
readonly actualTailSequence?: number | undefined;
|
|
726
732
|
readonly actualTailDigest?: string | undefined;
|
|
727
|
-
} | {
|
|
728
|
-
readonly _tag: "FenceRejected";
|
|
729
|
-
readonly threadId: string;
|
|
730
|
-
readonly actualEpoch: number;
|
|
731
|
-
readonly attemptedEpoch: number;
|
|
732
|
-
} | {
|
|
733
|
-
readonly _tag: "OperationDenied";
|
|
734
|
-
readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
|
|
735
|
-
readonly reason: string;
|
|
736
|
-
readonly threadId?: string | undefined;
|
|
737
|
-
readonly submissionId?: string | undefined;
|
|
738
|
-
} | {
|
|
739
|
-
readonly _tag: "AdmissionPolicyError";
|
|
740
|
-
readonly reason: "occupied" | "refused" | "unavailable";
|
|
741
|
-
readonly code: string;
|
|
742
|
-
readonly cause?: import("effect-agent/failure-diagnostic").Diagnostic | undefined;
|
|
743
|
-
readonly stack?: string | undefined;
|
|
744
733
|
} | {
|
|
745
734
|
readonly _tag: "DigestError";
|
|
746
735
|
readonly message: string;
|
|
@@ -749,31 +738,42 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
|
|
|
749
738
|
readonly _tag: "DurableRuntimeFailpointError";
|
|
750
739
|
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";
|
|
751
740
|
} | {
|
|
752
|
-
readonly _tag: "
|
|
753
|
-
readonly
|
|
754
|
-
readonly existingOutcome: "aborted" | "completed" | "failed";
|
|
755
|
-
} | {
|
|
756
|
-
readonly _tag: "OwnershipLost";
|
|
757
|
-
readonly submissionId: string;
|
|
741
|
+
readonly _tag: "FenceRejected";
|
|
742
|
+
readonly threadId: string;
|
|
758
743
|
readonly actualEpoch: number;
|
|
744
|
+
readonly attemptedEpoch: number;
|
|
759
745
|
} | {
|
|
760
|
-
readonly _tag: "
|
|
761
|
-
readonly
|
|
762
|
-
readonly cause?: Schema.Json | undefined;
|
|
746
|
+
readonly _tag: "ThreadNotMaterialized";
|
|
747
|
+
readonly threadId: string;
|
|
763
748
|
} | {
|
|
764
|
-
readonly _tag: "
|
|
749
|
+
readonly _tag: "ThreadStoreError";
|
|
765
750
|
readonly operation: string;
|
|
766
751
|
readonly message: string;
|
|
767
752
|
readonly cause?: Schema.Json | undefined;
|
|
768
753
|
} | {
|
|
769
|
-
readonly _tag: "
|
|
770
|
-
readonly
|
|
754
|
+
readonly _tag: "OperationDenied";
|
|
755
|
+
readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
|
|
756
|
+
readonly reason: string;
|
|
757
|
+
readonly threadId?: string | undefined;
|
|
758
|
+
readonly submissionId?: string | undefined;
|
|
771
759
|
} | {
|
|
772
760
|
readonly _tag: "RetryRefused";
|
|
773
761
|
readonly submissionId: string;
|
|
774
762
|
readonly refusal: "await-approval-decision" | "await-unknown-resolution" | "settled";
|
|
775
763
|
readonly decisionTag: string;
|
|
776
764
|
readonly message: string;
|
|
765
|
+
} | {
|
|
766
|
+
readonly _tag: "RunJournalError";
|
|
767
|
+
readonly message: string;
|
|
768
|
+
readonly cause?: Schema.Json | undefined;
|
|
769
|
+
} | {
|
|
770
|
+
readonly _tag: "OwnershipLost";
|
|
771
|
+
readonly submissionId: string;
|
|
772
|
+
readonly actualEpoch: number;
|
|
773
|
+
} | {
|
|
774
|
+
readonly _tag: "SettlementConflict";
|
|
775
|
+
readonly submissionId: string;
|
|
776
|
+
readonly existingOutcome: "aborted" | "completed" | "failed";
|
|
777
777
|
};
|
|
778
778
|
}, Schema.SchemaError, never>;
|
|
779
779
|
declare const decodeAdminResponse: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Effect.Effect<AdminFailed | ExplainedRecovery | ObligationsScanned | RetryExecuted | VerifiedIntegrity, Schema.SchemaError, never>;
|
|
@@ -826,4 +826,4 @@ interface Class<EventServices = never> {
|
|
|
826
826
|
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>;
|
|
827
827
|
//#endregion
|
|
828
828
|
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 };
|
|
829
|
-
//# sourceMappingURL=ThreadObject-
|
|
829
|
+
//# sourceMappingURL=ThreadObject-D_vvj7Qx.d.mts.map
|
package/dist/ThreadObject.d.mts
CHANGED
|
@@ -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-
|
|
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-D_vvj7Qx.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-
|
|
11
|
+
import { f as ThreadObject_d_exports } from "./ThreadObject-D_vvj7Qx.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.
|
|
1
|
+
{"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.108","dependencies":{"@effect-agent/storage-cloudflare":"0.1.0-beta.108","@effect/platform-browser":"4.0.0-rc.115","@effect/sql-sqlite-do":"4.0.0-rc.115","effect-agent":"0.1.0-beta.108"},"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.108","@effect/platform-node":"4.0.0-rc.115","@effect/sql-d1":"4.0.0-rc.115","@effect/vitest":"4.0.0-rc.115","effect":"4.0.0-rc.115","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.115","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,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
4
|
type Browser,
|
|
5
5
|
type BrowserContext,
|
|
6
6
|
type CDPSession,
|
|
@@ -8,6 +8,7 @@ import puppeteer, {
|
|
|
8
8
|
type Page,
|
|
9
9
|
} from "@cloudflare/puppeteer";
|
|
10
10
|
import {
|
|
11
|
+
Cause,
|
|
11
12
|
Context,
|
|
12
13
|
Clock,
|
|
13
14
|
Duration,
|
|
@@ -45,6 +46,12 @@ import {
|
|
|
45
46
|
import { PageScreenshotResult } from "effect-agent/page-screenshot";
|
|
46
47
|
import { SandboxImplementation } from "effect-agent/sandbox";
|
|
47
48
|
|
|
49
|
+
import { BrowserRunBinding } from "./internal/browser-binding.ts";
|
|
50
|
+
import {
|
|
51
|
+
BrowserRunFailure,
|
|
52
|
+
reportBrowserCause,
|
|
53
|
+
reportedBrowserError,
|
|
54
|
+
} from "./internal/browser-failure.ts";
|
|
48
55
|
import { makeFileSelection } from "./internal/browser-file-selection.ts";
|
|
49
56
|
import {
|
|
50
57
|
BrowserRunSessionLifecycle,
|
|
@@ -379,22 +386,26 @@ export class BrowserRunInteractiveBinding extends Context.Service<
|
|
|
379
386
|
return Layer.effect(BrowserRunInteractiveBinding)(
|
|
380
387
|
Effect.gen(function* () {
|
|
381
388
|
const lifecycle = yield* BrowserRunSessionLifecycle;
|
|
389
|
+
const binding = yield* BrowserRunBinding;
|
|
382
390
|
|
|
383
391
|
const viewport =
|
|
384
392
|
options.viewport === undefined ? undefined : yield* decodeViewport(options.viewport);
|
|
385
393
|
|
|
386
394
|
return {
|
|
387
395
|
acquire: async (keepAliveMillis: number) =>
|
|
388
|
-
|
|
396
|
+
binding.acquire(keepAliveMillis, "interactive.acquire"),
|
|
389
397
|
connect: async (sessionId: string) =>
|
|
390
|
-
makeProductionBrowser(
|
|
398
|
+
makeProductionBrowser(
|
|
399
|
+
await binding.connect(sessionId, "interactive.connect"),
|
|
400
|
+
viewport,
|
|
401
|
+
),
|
|
391
402
|
closeSession: (sessionId: Redacted.Redacted<string>) =>
|
|
392
403
|
lifecycle
|
|
393
404
|
.close(sessionId)
|
|
394
405
|
.pipe(Effect.mapError((cause) => actionError("close", cause))),
|
|
395
406
|
};
|
|
396
407
|
}),
|
|
397
|
-
);
|
|
408
|
+
).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
|
|
398
409
|
}
|
|
399
410
|
}
|
|
400
411
|
|
|
@@ -776,7 +787,14 @@ const makeProductionPage = (page: Page): BrowserRunInteractivePage => {
|
|
|
776
787
|
const listeners = new Map<BrowserRunInteractiveRequestListener, (request: HTTPRequest) => void>();
|
|
777
788
|
|
|
778
789
|
return {
|
|
779
|
-
close: () =>
|
|
790
|
+
close: async () => {
|
|
791
|
+
if (!page.browser().isConnected()) return;
|
|
792
|
+
try {
|
|
793
|
+
await page.close();
|
|
794
|
+
} catch (cause) {
|
|
795
|
+
if (!isRemoteClosure(cause)) throw cause;
|
|
796
|
+
}
|
|
797
|
+
},
|
|
780
798
|
setBypassServiceWorker: (enabled) => page.setBypassServiceWorker(enabled),
|
|
781
799
|
setRequestInterception: (enabled) => page.setRequestInterception(enabled),
|
|
782
800
|
onRequest: (listener) => {
|
|
@@ -1144,7 +1162,14 @@ const makeProductionContext = (
|
|
|
1144
1162
|
|
|
1145
1163
|
return makeProductionPage(page);
|
|
1146
1164
|
},
|
|
1147
|
-
close: () =>
|
|
1165
|
+
close: async () => {
|
|
1166
|
+
if (!context.browser().isConnected()) return;
|
|
1167
|
+
try {
|
|
1168
|
+
await context.close();
|
|
1169
|
+
} catch (cause) {
|
|
1170
|
+
if (!isRemoteClosure(cause)) throw cause;
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1148
1173
|
});
|
|
1149
1174
|
|
|
1150
1175
|
const makeProductionBrowser = (
|
|
@@ -1152,7 +1177,15 @@ const makeProductionBrowser = (
|
|
|
1152
1177
|
viewport?: BrowserRunViewport,
|
|
1153
1178
|
): BrowserRunInteractiveBrowser => ({
|
|
1154
1179
|
createContext: async () => makeProductionContext(await browser.createBrowserContext(), viewport),
|
|
1155
|
-
|
|
1180
|
+
// Exact-session termination may already have closed the provider transport.
|
|
1181
|
+
close: async () => {
|
|
1182
|
+
if (!browser.isConnected()) return;
|
|
1183
|
+
try {
|
|
1184
|
+
await browser.close();
|
|
1185
|
+
} catch (cause) {
|
|
1186
|
+
if (!isRemoteClosure(cause)) throw cause;
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1156
1189
|
sessionId: () => browser.sessionId(),
|
|
1157
1190
|
isConnected: () => browser.isConnected(),
|
|
1158
1191
|
onDisconnected: (listener) => {
|
|
@@ -1211,7 +1244,7 @@ const causeText = (cause: unknown): string => {
|
|
|
1211
1244
|
};
|
|
1212
1245
|
|
|
1213
1246
|
const isCapacityRefusal = (cause: unknown): boolean =>
|
|
1214
|
-
|
|
1247
|
+
cause instanceof BrowserRunFailure && cause.status === 429;
|
|
1215
1248
|
|
|
1216
1249
|
const isRemoteClosure = (cause: unknown): boolean =>
|
|
1217
1250
|
/target closed|browser.*closed|session.*closed|connection.*closed|not connected|websocket.*closed/i.test(
|
|
@@ -1270,6 +1303,8 @@ const hostAllowed = (policy: InteractiveBrowserPolicySnapshot, value: string): b
|
|
|
1270
1303
|
const keepAliveMillis = (policy: InteractiveBrowserPolicySnapshot): number =>
|
|
1271
1304
|
Math.max(MIN_KEEP_ALIVE_MILLIS, Math.min(MAX_KEEP_ALIVE_MILLIS, policy.maxElapsedMillis));
|
|
1272
1305
|
|
|
1306
|
+
class LateBrowserAcquisition extends Error {}
|
|
1307
|
+
|
|
1273
1308
|
const closeLateAcquisition = async <A>(
|
|
1274
1309
|
signal: AbortSignal,
|
|
1275
1310
|
acquire: () => Promise<A>,
|
|
@@ -1284,22 +1319,25 @@ const closeLateAcquisition = async <A>(
|
|
|
1284
1319
|
try {
|
|
1285
1320
|
await close(acquired);
|
|
1286
1321
|
} catch {
|
|
1287
|
-
//
|
|
1288
|
-
// must not escape through an unhandled rejection.
|
|
1322
|
+
// The captured runtime reports cleanup failures before this callback absorbs rejection.
|
|
1289
1323
|
}
|
|
1290
|
-
throw new
|
|
1324
|
+
throw new LateBrowserAcquisition("The interrupted browser acquisition completed late");
|
|
1291
1325
|
};
|
|
1292
1326
|
|
|
1293
1327
|
const closeWithWarning = (close: () => Promise<void>, warning: string): Effect.Effect<void> =>
|
|
1294
1328
|
Effect.tryPromise({
|
|
1295
1329
|
try: close,
|
|
1296
|
-
catch: () => protocolError(warning),
|
|
1330
|
+
catch: (cause) => protocolError(warning, cause),
|
|
1297
1331
|
}).pipe(
|
|
1298
1332
|
Effect.timeoutOrElse({
|
|
1299
1333
|
duration: Duration.millis(CLEANUP_STEP_TIMEOUT_MILLIS),
|
|
1300
1334
|
orElse: () => Effect.fail(protocolError(warning)),
|
|
1301
1335
|
}),
|
|
1302
|
-
Effect.catchCause(() =>
|
|
1336
|
+
Effect.catchCause((cause) =>
|
|
1337
|
+
reportBrowserCause("interactive.cleanup", cause).pipe(
|
|
1338
|
+
Effect.andThen(Effect.logWarning(warning)),
|
|
1339
|
+
),
|
|
1340
|
+
),
|
|
1303
1341
|
);
|
|
1304
1342
|
|
|
1305
1343
|
const deadlineError = Effect.fn("BrowserRunInteractive.deadlineError")(function* (
|
|
@@ -1459,15 +1497,21 @@ const makeRequestListener =
|
|
|
1459
1497
|
|
|
1460
1498
|
try {
|
|
1461
1499
|
settlement = allowed ? request.continue() : request.abort();
|
|
1462
|
-
} catch {
|
|
1463
|
-
state.violation.value = protocolError(
|
|
1500
|
+
} catch (cause) {
|
|
1501
|
+
state.violation.value = protocolError(
|
|
1502
|
+
"Resolving an intercepted browser request failed",
|
|
1503
|
+
cause,
|
|
1504
|
+
);
|
|
1464
1505
|
|
|
1465
1506
|
return;
|
|
1466
1507
|
}
|
|
1467
1508
|
|
|
1468
1509
|
const observed = settlement
|
|
1469
|
-
.catch(() => {
|
|
1470
|
-
state.violation.value = protocolError(
|
|
1510
|
+
.catch((cause) => {
|
|
1511
|
+
state.violation.value = protocolError(
|
|
1512
|
+
"Resolving an intercepted browser request failed",
|
|
1513
|
+
cause,
|
|
1514
|
+
);
|
|
1471
1515
|
})
|
|
1472
1516
|
.finally(() => {
|
|
1473
1517
|
state.pendingRequests.delete(observed);
|
|
@@ -1923,10 +1967,31 @@ const cdpCommand = <A>(
|
|
|
1923
1967
|
): Effect.Effect<A, InteractiveBrowserError> =>
|
|
1924
1968
|
Effect.scoped(
|
|
1925
1969
|
Effect.gen(function* () {
|
|
1970
|
+
const cleanupContext = yield* Effect.context<never>();
|
|
1971
|
+
|
|
1926
1972
|
const cdp = yield* Effect.acquireRelease(
|
|
1927
1973
|
Effect.tryPromise({
|
|
1928
|
-
try: (signal) =>
|
|
1929
|
-
closeLateAcquisition(signal, page.createCdpSession, (acquired) =>
|
|
1974
|
+
try: (signal) => {
|
|
1975
|
+
const pending = closeLateAcquisition(signal, page.createCdpSession, (acquired) =>
|
|
1976
|
+
Effect.runPromiseWith(cleanupContext)(
|
|
1977
|
+
Effect.tryPromise({
|
|
1978
|
+
try: () => acquired.detach(),
|
|
1979
|
+
catch: (cause) => actionError("close", cause),
|
|
1980
|
+
}).pipe(
|
|
1981
|
+
Effect.tapCause((cause) => reportBrowserCause("interactive.lateDetach", cause)),
|
|
1982
|
+
),
|
|
1983
|
+
),
|
|
1984
|
+
);
|
|
1985
|
+
|
|
1986
|
+
void pending.then(undefined, (cause) => {
|
|
1987
|
+
if (signal.aborted && !(cause instanceof LateBrowserAcquisition))
|
|
1988
|
+
return Effect.runPromiseWith(cleanupContext)(
|
|
1989
|
+
reportBrowserCause("interactive.connect", Cause.fail(cause)),
|
|
1990
|
+
);
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
return pending;
|
|
1994
|
+
},
|
|
1930
1995
|
catch: (cause) =>
|
|
1931
1996
|
state.disconnected.value || isRemoteClosure(cause)
|
|
1932
1997
|
? expiredError()
|
|
@@ -1949,7 +2014,11 @@ const cdpCommand = <A>(
|
|
|
1949
2014
|
});
|
|
1950
2015
|
|
|
1951
2016
|
return yield* Schema.decodeUnknownEffect(output)(raw).pipe(
|
|
1952
|
-
Effect.
|
|
2017
|
+
Effect.catch((error) =>
|
|
2018
|
+
reportBrowserCause("interactive.command.decode", Cause.fail(error)).pipe(
|
|
2019
|
+
Effect.andThen(Effect.fail(reportedBrowserError(protocolError(malformedMessage)))),
|
|
2020
|
+
),
|
|
2021
|
+
),
|
|
1953
2022
|
);
|
|
1954
2023
|
}),
|
|
1955
2024
|
);
|
|
@@ -1981,14 +2050,25 @@ const makeHostService = (
|
|
|
1981
2050
|
// Remote termination is authoritative. Local cleanup must not veto it or extend the deadline.
|
|
1982
2051
|
yield* runTeardown(entries).pipe(
|
|
1983
2052
|
Effect.flatMap((failures) =>
|
|
1984
|
-
Effect.forEach(
|
|
1985
|
-
|
|
1986
|
-
|
|
2053
|
+
Effect.forEach(
|
|
2054
|
+
failures,
|
|
2055
|
+
(failure) =>
|
|
2056
|
+
reportBrowserCause("interactive.cleanup", Cause.fail(failure.error)).pipe(
|
|
2057
|
+
Effect.andThen(Effect.logWarning(failure.warning)),
|
|
2058
|
+
),
|
|
2059
|
+
{
|
|
2060
|
+
discard: true,
|
|
2061
|
+
},
|
|
2062
|
+
),
|
|
1987
2063
|
),
|
|
1988
2064
|
Effect.interruptible,
|
|
1989
2065
|
Effect.timeout(`${remaining} millis`),
|
|
1990
|
-
Effect.catchCause(() =>
|
|
1991
|
-
|
|
2066
|
+
Effect.catchCause((cause) =>
|
|
2067
|
+
reportBrowserCause("interactive.cleanup", cause).pipe(
|
|
2068
|
+
Effect.andThen(
|
|
2069
|
+
Effect.logWarning("Local browser teardown incomplete after confirmed termination"),
|
|
2070
|
+
),
|
|
2071
|
+
),
|
|
1992
2072
|
),
|
|
1993
2073
|
);
|
|
1994
2074
|
});
|
|
@@ -1998,10 +2078,10 @@ const makeHostService = (
|
|
|
1998
2078
|
) {
|
|
1999
2079
|
const sessionId = yield* Effect.try({
|
|
2000
2080
|
try: browser.sessionId,
|
|
2001
|
-
catch: () => actionError("close"),
|
|
2081
|
+
catch: (cause) => actionError("close", cause),
|
|
2002
2082
|
}).pipe(
|
|
2003
2083
|
Effect.flatMap(Schema.decodeUnknownEffect(BrowserRunSessionId)),
|
|
2004
|
-
Effect.mapError(() => actionError("close")),
|
|
2084
|
+
Effect.mapError((cause) => actionError("close", cause)),
|
|
2005
2085
|
Effect.onError(() =>
|
|
2006
2086
|
closeWithWarning(browser.close, "Closing an unidentified browser failed"),
|
|
2007
2087
|
),
|
|
@@ -2018,8 +2098,26 @@ const makeHostService = (
|
|
|
2018
2098
|
const scope = yield* Scope.Scope;
|
|
2019
2099
|
const fixedPolicy = yield* snapshotPolicy(policy);
|
|
2020
2100
|
const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
2021
|
-
|
|
2022
|
-
|
|
2101
|
+
|
|
2102
|
+
// Late SDK replies retain this pass's clock and private reporting scope.
|
|
2103
|
+
const runCleanup = <A, E>(effect: Effect.Effect<A, E>) =>
|
|
2104
|
+
Effect.runPromiseWith(cleanupContext)(
|
|
2105
|
+
effect.pipe(
|
|
2106
|
+
Effect.tapCause((cause) => reportBrowserCause("interactive.lateCleanup", cause)),
|
|
2107
|
+
),
|
|
2108
|
+
);
|
|
2109
|
+
|
|
2110
|
+
const cleanupContext = yield* Effect.context<never>();
|
|
2111
|
+
|
|
2112
|
+
const observeLate = <A>(signal: AbortSignal, pending: Promise<A>): Promise<A> => {
|
|
2113
|
+
// Observe the original Promise without creating another rejected continuation.
|
|
2114
|
+
void pending.then(undefined, (cause) => {
|
|
2115
|
+
if (signal.aborted && !(cause instanceof LateBrowserAcquisition))
|
|
2116
|
+
return runCleanup(reportBrowserCause("interactive.acquire", Cause.fail(cause)));
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
return pending;
|
|
2120
|
+
};
|
|
2023
2121
|
|
|
2024
2122
|
const state: HandleState = {
|
|
2025
2123
|
closed: { value: false },
|
|
@@ -2039,22 +2137,31 @@ const makeHostService = (
|
|
|
2039
2137
|
Effect.suspend(() =>
|
|
2040
2138
|
lifecycle.managedTeardownInstalled
|
|
2041
2139
|
? Effect.void
|
|
2042
|
-
: entry.close.pipe(
|
|
2140
|
+
: entry.close.pipe(
|
|
2141
|
+
Effect.catchCause((cause) =>
|
|
2142
|
+
reportBrowserCause("interactive.cleanup", cause).pipe(
|
|
2143
|
+
Effect.andThen(Effect.logWarning(entry.warning)),
|
|
2144
|
+
),
|
|
2145
|
+
),
|
|
2146
|
+
),
|
|
2043
2147
|
);
|
|
2044
2148
|
|
|
2045
2149
|
const sessionIdValue = yield* Effect.acquireRelease(
|
|
2046
2150
|
withinDeadline(
|
|
2047
2151
|
Effect.tryPromise({
|
|
2048
2152
|
try: (signal) =>
|
|
2049
|
-
|
|
2153
|
+
observeLate(
|
|
2050
2154
|
signal,
|
|
2051
|
-
(
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2155
|
+
closeLateAcquisition(
|
|
2156
|
+
signal,
|
|
2157
|
+
() => binding.acquire(keepAliveMillis(fixedPolicy)),
|
|
2158
|
+
(id) =>
|
|
2159
|
+
runCleanup(
|
|
2160
|
+
Schema.decodeUnknownEffect(BrowserRunSessionId)(id).pipe(
|
|
2161
|
+
Effect.flatMap((value) => terminate(Redacted.make(value), [])),
|
|
2162
|
+
),
|
|
2056
2163
|
),
|
|
2057
|
-
|
|
2164
|
+
),
|
|
2058
2165
|
),
|
|
2059
2166
|
catch: (cause) =>
|
|
2060
2167
|
isCapacityRefusal(cause)
|
|
@@ -2080,7 +2187,11 @@ const makeHostService = (
|
|
|
2080
2187
|
return lifecycle.managedTeardownInstalled
|
|
2081
2188
|
? Effect.void
|
|
2082
2189
|
: terminate(Redacted.make(id), []).pipe(
|
|
2083
|
-
Effect.
|
|
2190
|
+
Effect.catchCause((cause) =>
|
|
2191
|
+
reportBrowserCause("interactive.close", cause).pipe(
|
|
2192
|
+
Effect.andThen(Effect.logWarning("Whole-browser cleanup remains unconfirmed")),
|
|
2193
|
+
),
|
|
2194
|
+
),
|
|
2084
2195
|
);
|
|
2085
2196
|
}),
|
|
2086
2197
|
{ interruptible: true },
|
|
@@ -2096,10 +2207,13 @@ const makeHostService = (
|
|
|
2096
2207
|
withinDeadline(
|
|
2097
2208
|
Effect.tryPromise({
|
|
2098
2209
|
try: (signal) =>
|
|
2099
|
-
|
|
2210
|
+
observeLate(
|
|
2100
2211
|
signal,
|
|
2101
|
-
(
|
|
2102
|
-
|
|
2212
|
+
closeLateAcquisition(
|
|
2213
|
+
signal,
|
|
2214
|
+
() => binding.connect(sessionIdValue),
|
|
2215
|
+
(acquired) => runCleanup(closeAcquired(acquired)),
|
|
2216
|
+
),
|
|
2103
2217
|
),
|
|
2104
2218
|
catch: (cause) =>
|
|
2105
2219
|
protocolError("Connecting to the Browser Run session failed", cause),
|
|
@@ -2156,7 +2270,17 @@ const makeHostService = (
|
|
|
2156
2270
|
withinDeadline(
|
|
2157
2271
|
Effect.tryPromise({
|
|
2158
2272
|
try: (signal) =>
|
|
2159
|
-
|
|
2273
|
+
observeLate(
|
|
2274
|
+
signal,
|
|
2275
|
+
closeLateAcquisition(signal, browser.createContext, (acquired) =>
|
|
2276
|
+
runCleanup(
|
|
2277
|
+
Effect.tryPromise({
|
|
2278
|
+
try: () => acquired.close(),
|
|
2279
|
+
catch: (cause) => actionError("close", cause),
|
|
2280
|
+
}),
|
|
2281
|
+
),
|
|
2282
|
+
),
|
|
2283
|
+
),
|
|
2160
2284
|
catch: (cause) =>
|
|
2161
2285
|
state.disconnected.value || isRemoteClosure(cause)
|
|
2162
2286
|
? expiredError()
|
|
@@ -2178,7 +2302,17 @@ const makeHostService = (
|
|
|
2178
2302
|
withinDeadline(
|
|
2179
2303
|
Effect.tryPromise({
|
|
2180
2304
|
try: (signal) =>
|
|
2181
|
-
|
|
2305
|
+
observeLate(
|
|
2306
|
+
signal,
|
|
2307
|
+
closeLateAcquisition(signal, context.newPage, (acquired) =>
|
|
2308
|
+
runCleanup(
|
|
2309
|
+
Effect.tryPromise({
|
|
2310
|
+
try: () => acquired.close(),
|
|
2311
|
+
catch: (cause) => actionError("close", cause),
|
|
2312
|
+
}),
|
|
2313
|
+
),
|
|
2314
|
+
),
|
|
2315
|
+
),
|
|
2182
2316
|
catch: (cause) =>
|
|
2183
2317
|
state.disconnected.value || isRemoteClosure(cause)
|
|
2184
2318
|
? expiredError()
|
|
@@ -2244,7 +2378,12 @@ const makeHostService = (
|
|
|
2244
2378
|
|
|
2245
2379
|
const teardown = yield* Effect.uninterruptible(
|
|
2246
2380
|
Effect.gen(function* () {
|
|
2247
|
-
const cached = yield* Effect.cached(
|
|
2381
|
+
const cached = yield* Effect.cached(
|
|
2382
|
+
terminate(Redacted.make(sessionIdValue), closers).pipe(
|
|
2383
|
+
Effect.tapCause((cause) => reportBrowserCause("interactive.close", cause)),
|
|
2384
|
+
Effect.mapError(reportedBrowserError),
|
|
2385
|
+
),
|
|
2386
|
+
);
|
|
2248
2387
|
|
|
2249
2388
|
lifecycle.managedTeardownInstalled = true;
|
|
2250
2389
|
yield* Effect.addFinalizer(() =>
|
|
@@ -2254,8 +2393,12 @@ const makeHostService = (
|
|
|
2254
2393
|
state.disconnected.value = true;
|
|
2255
2394
|
}).pipe(
|
|
2256
2395
|
Effect.andThen(cached),
|
|
2257
|
-
Effect.
|
|
2258
|
-
|
|
2396
|
+
Effect.catchCause((cause) =>
|
|
2397
|
+
reportBrowserCause("interactive.close", cause).pipe(
|
|
2398
|
+
Effect.andThen(
|
|
2399
|
+
Effect.logWarning("Whole-browser cleanup remains unconfirmed"),
|
|
2400
|
+
),
|
|
2401
|
+
),
|
|
2259
2402
|
),
|
|
2260
2403
|
),
|
|
2261
2404
|
),
|