@effect-agent/platform-cloudflare 0.1.0-beta.120 → 0.1.0-beta.122
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 +50 -50
- package/dist/{InteractiveBrowser-BQo7QKaP.d.mts → InteractiveBrowser-Cm4xgiGy.d.mts} +2 -2
- package/dist/{InteractiveBrowser-CBUFAECo.mjs → InteractiveBrowser-DXwmvWdA.mjs} +197 -25
- package/dist/InteractiveBrowser-DXwmvWdA.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +1 -1
- package/dist/InteractiveBrowser.mjs +1 -1
- package/dist/ProtectedBrowser.d.mts +15 -1
- package/dist/ProtectedBrowser.mjs +194 -153
- package/dist/ProtectedBrowser.mjs.map +1 -1
- package/dist/{ThreadObject-9IVibJM7.d.mts → ThreadObject-mt0uxvZZ.d.mts} +27 -27
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/index.d.mts +1 -1
- package/package.json +1 -1
- package/src/InteractiveBrowser.ts +17 -12
- package/src/internal/browser-binding.ts +270 -32
- package/src/internal/browser-file-selection.ts +1 -1
- package/src/protected-browser/binding.ts +124 -33
- package/src/protected-browser/host.ts +16 -0
- package/src/protected-browser/native.ts +7 -9
- package/dist/InteractiveBrowser-CBUFAECo.mjs.map +0 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
-
|
|
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
|
-
import { BrowserRunBinding } from "../internal/browser-binding.ts";
|
|
12
|
+
import { BrowserRunBinding, type BrowserRunAttachment } from "../internal/browser-binding.ts";
|
|
8
13
|
import {
|
|
9
14
|
browserFailure,
|
|
10
15
|
BrowserRunFailure,
|
|
@@ -45,6 +50,9 @@ export class BrowserRunProtectedBinding extends Context.Service<
|
|
|
45
50
|
policy: InteractiveBrowserPolicy,
|
|
46
51
|
identity?: ProtectedProviderIdentity,
|
|
47
52
|
) => Effect.Effect<ProtectedProviderSession, ProtectedBrowserError, Scope.Scope>;
|
|
53
|
+
readonly keepAlive: (
|
|
54
|
+
sessionId: Redacted.Redacted<string>,
|
|
55
|
+
) => Effect.Effect<void, ProtectedBrowserError>;
|
|
48
56
|
}
|
|
49
57
|
>()("@effect-agent/platform-cloudflare/BrowserRunProtectedBinding") {}
|
|
50
58
|
|
|
@@ -78,6 +86,7 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
78
86
|
let control: CDPSession | undefined;
|
|
79
87
|
let providerIdentity: ProtectedProviderIdentity | undefined;
|
|
80
88
|
let browser: Browser | undefined;
|
|
89
|
+
let attachment: BrowserRunAttachment | undefined;
|
|
81
90
|
let driver: ProtectedBrowserTransport | undefined;
|
|
82
91
|
let invalid = false;
|
|
83
92
|
// SDK acquisition may finish after interruption. Its late-reply callback must await cleanup,
|
|
@@ -89,6 +98,32 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
89
98
|
driver?.invalidate();
|
|
90
99
|
if (sessionId === undefined) return "unconfirmed" as const;
|
|
91
100
|
|
|
101
|
+
// A failed resume has not acquired the host's retained provider. Release only
|
|
102
|
+
// this connection; successful attachments and new allocations own termination.
|
|
103
|
+
if (identity !== undefined && driver === undefined) {
|
|
104
|
+
if (attachment !== undefined)
|
|
105
|
+
yield* attachment.retire.pipe(
|
|
106
|
+
Effect.interruptible,
|
|
107
|
+
Effect.timeoutOrElse({
|
|
108
|
+
duration: "1 second",
|
|
109
|
+
orElse: () =>
|
|
110
|
+
Effect.fail(
|
|
111
|
+
new BrowserRunFailure({
|
|
112
|
+
operation: "protected.disconnect",
|
|
113
|
+
reason: "timeout",
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
}),
|
|
117
|
+
Effect.catch((error) =>
|
|
118
|
+
reportBrowserCause("protected.disconnect", Cause.fail(error)).pipe(
|
|
119
|
+
Effect.andThen(() => Effect.die(reportedBrowserError(error))),
|
|
120
|
+
),
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return "unconfirmed" as const;
|
|
125
|
+
}
|
|
126
|
+
|
|
92
127
|
const cleanup = yield* lifecycle.close(sessionId).pipe(
|
|
93
128
|
Effect.as("confirmed" as const),
|
|
94
129
|
Effect.catchCause((cause) =>
|
|
@@ -155,7 +190,8 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
155
190
|
}
|
|
156
191
|
// Resume only a host-persisted exact page. Never open a replacement page or context.
|
|
157
192
|
stage = "protected.connect";
|
|
158
|
-
|
|
193
|
+
attachment = binding.connect(Redacted.value(sessionId), stage, signal);
|
|
194
|
+
browser = await attachment.browser;
|
|
159
195
|
if (signal.aborted || invalid) {
|
|
160
196
|
await runCleanup(terminate);
|
|
161
197
|
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
@@ -203,32 +239,38 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
203
239
|
targetId: Redacted.make(info.targetInfo.targetId),
|
|
204
240
|
});
|
|
205
241
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
// Unrestricted passes need no attachment-local network enforcement. Leave their
|
|
243
|
+
// service workers and requests alone, including across host-owned detach/resume.
|
|
244
|
+
if (policy.network._tag !== "Unrestricted") {
|
|
245
|
+
stage = "protected.interception";
|
|
246
|
+
await page.setBypassServiceWorker(true);
|
|
247
|
+
await page.setRequestInterception(true);
|
|
248
|
+
page.on("request", (request) => {
|
|
249
|
+
let allowed = false;
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const url = new URL(request.url());
|
|
253
|
+
|
|
254
|
+
allowed =
|
|
255
|
+
policy.network._tag === "ExactHosts" &&
|
|
256
|
+
url.protocol === "https:" &&
|
|
257
|
+
!url.username &&
|
|
258
|
+
!url.password &&
|
|
259
|
+
policy.network.allowedHosts.includes(url.host);
|
|
260
|
+
} catch {
|
|
261
|
+
/* Refuse malformed destinations. */
|
|
262
|
+
}
|
|
263
|
+
void (
|
|
264
|
+
allowed && !invalid ? request.continue() : request.abort("blockedbyclient")
|
|
265
|
+
).catch(async (cause) => {
|
|
266
|
+
invalid = true;
|
|
267
|
+
driver?.invalidate();
|
|
268
|
+
await runCleanup(
|
|
269
|
+
reportBrowserCause("protected.interception", Cause.fail(cause)),
|
|
270
|
+
);
|
|
271
|
+
});
|
|
230
272
|
});
|
|
231
|
-
}
|
|
273
|
+
}
|
|
232
274
|
if (signal.aborted || invalid) {
|
|
233
275
|
await runCleanup(terminate);
|
|
234
276
|
throw new ProtectedTransportError({ reason: "stale-reference" });
|
|
@@ -253,8 +295,26 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
253
295
|
orElse: () =>
|
|
254
296
|
Effect.fail(new BrowserRunFailure({ operation: stage, reason: "timeout" })),
|
|
255
297
|
}),
|
|
256
|
-
Effect.catch((error) =>
|
|
257
|
-
|
|
298
|
+
Effect.catch((error) => {
|
|
299
|
+
if (identity !== undefined && driver === undefined)
|
|
300
|
+
return reportBrowserCause(stage, Cause.fail(error)).pipe(
|
|
301
|
+
Effect.andThen(
|
|
302
|
+
Effect.fail(
|
|
303
|
+
reportedBrowserError(
|
|
304
|
+
new ProtectedBrowserError({
|
|
305
|
+
...failure(),
|
|
306
|
+
reason: error.reason === "timeout" ? "timeout" : "provider",
|
|
307
|
+
cleanup: "not-requested",
|
|
308
|
+
}),
|
|
309
|
+
),
|
|
310
|
+
),
|
|
311
|
+
),
|
|
312
|
+
// A disposal defect stays alongside the original failure. Only a pure typed
|
|
313
|
+
// failure proves that this pre-handle attachment is safe to relinquish.
|
|
314
|
+
Effect.ensuring(close),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return reportBrowserCause(stage, Cause.fail(error)).pipe(
|
|
258
318
|
Effect.andThen(close),
|
|
259
319
|
Effect.flatMap((cleanup) =>
|
|
260
320
|
Effect.fail(
|
|
@@ -267,8 +327,8 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
267
327
|
),
|
|
268
328
|
),
|
|
269
329
|
),
|
|
270
|
-
)
|
|
271
|
-
),
|
|
330
|
+
);
|
|
331
|
+
}),
|
|
272
332
|
);
|
|
273
333
|
|
|
274
334
|
driver = yield* makeProtectedNativeTransport(policy).pipe(
|
|
@@ -334,7 +394,38 @@ const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fe
|
|
|
334
394
|
};
|
|
335
395
|
}, Effect.withTracerEnabled(false));
|
|
336
396
|
|
|
337
|
-
|
|
397
|
+
const keepAlive = Effect.fn("BrowserRunProtectedBinding.keepAlive")(function* (
|
|
398
|
+
sessionId: Redacted.Redacted<string>,
|
|
399
|
+
) {
|
|
400
|
+
const failure = (reason: ProtectedBrowserError["reason"]) =>
|
|
401
|
+
new ProtectedBrowserError({
|
|
402
|
+
reason,
|
|
403
|
+
dispatch: "not-dispatched",
|
|
404
|
+
milestone: "none",
|
|
405
|
+
observation: "protected",
|
|
406
|
+
cleanup: "not-requested",
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const id = yield* Schema.decodeEffect(ProtectedProviderIdentity.fields.sessionId)(
|
|
410
|
+
sessionId,
|
|
411
|
+
).pipe(Effect.mapError(() => failure("denied")));
|
|
412
|
+
|
|
413
|
+
yield* binding.keepAlive(Redacted.value(id)).pipe(
|
|
414
|
+
Effect.timeoutOrElse({
|
|
415
|
+
duration: "10 seconds",
|
|
416
|
+
orElse: () =>
|
|
417
|
+
Effect.fail(
|
|
418
|
+
new BrowserRunFailure({ operation: "protected.keepAlive", reason: "timeout" }),
|
|
419
|
+
),
|
|
420
|
+
}),
|
|
421
|
+
Effect.tapError((error) => reportBrowserCause("protected.keepAlive", Cause.fail(error))),
|
|
422
|
+
Effect.mapError((error) =>
|
|
423
|
+
reportedBrowserError(failure(error.reason === "timeout" ? "timeout" : "provider")),
|
|
424
|
+
),
|
|
425
|
+
);
|
|
426
|
+
}, Effect.withTracerEnabled(false));
|
|
427
|
+
|
|
428
|
+
return { open, keepAlive };
|
|
338
429
|
}),
|
|
339
430
|
).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
|
|
340
431
|
|
|
@@ -83,6 +83,14 @@ export interface BrowserRunProtectedSession {
|
|
|
83
83
|
export class BrowserRunProtectedHost extends Context.Service<
|
|
84
84
|
BrowserRunProtectedHost,
|
|
85
85
|
{
|
|
86
|
+
/**
|
|
87
|
+
* Refresh provider inactivity on an exact retained session with one browser-level command.
|
|
88
|
+
* Internally scoped and bounded to ten seconds; releases only its own connection on every exit.
|
|
89
|
+
* Does not acquire a page, transfer control, extend host expiry, or close the provider on failure.
|
|
90
|
+
*/
|
|
91
|
+
readonly keepAlive: (
|
|
92
|
+
sessionId: Redacted.Redacted<string>,
|
|
93
|
+
) => Effect.Effect<void, ProtectedBrowserError>;
|
|
86
94
|
readonly open: (
|
|
87
95
|
policy: InteractiveBrowserPolicy,
|
|
88
96
|
) => Effect.Effect<
|
|
@@ -90,6 +98,13 @@ export class BrowserRunProtectedHost extends Context.Service<
|
|
|
90
98
|
ProtectedBrowserError,
|
|
91
99
|
Scope.Scope | BrowserCredentialAccess
|
|
92
100
|
>;
|
|
101
|
+
/**
|
|
102
|
+
* A pure pre-handle provider failure with not-dispatched/none/not-requested evidence
|
|
103
|
+
* retires this attempt's SDK callbacks and confirms local socket closure before returning
|
|
104
|
+
* (or never starts SDK initialization). Raw retirement failure remains a mixed defect.
|
|
105
|
+
* This does not confirm provider health or undo already-sent CDP; the host still owns
|
|
106
|
+
* atomic claim restoration and must exclude defects, interruption, and post-handle errors.
|
|
107
|
+
*/
|
|
93
108
|
readonly resume: (
|
|
94
109
|
checkpoint: BrowserRunProtectedCheckpoint,
|
|
95
110
|
) => Effect.Effect<
|
|
@@ -362,6 +377,7 @@ export const browserRunProtectedHostLayer = () =>
|
|
|
362
377
|
}, Effect.withTracerEnabled(false));
|
|
363
378
|
|
|
364
379
|
return {
|
|
380
|
+
keepAlive: binding.keepAlive,
|
|
365
381
|
open: (policy: InteractiveBrowserPolicy) => open(policy),
|
|
366
382
|
resume: (checkpoint: BrowserRunProtectedCheckpoint) =>
|
|
367
383
|
Schema.decodeEffect(BrowserRunProtectedCheckpoint)(checkpoint).pipe(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
-
|
|
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,
|