@yielded/agent-platform-cloudflare 0.1.0-beta.172 → 0.1.0-beta.174
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/{Alarm-BMEAZo3p.mjs → Alarm-4J9B8Fg8.mjs} +54 -32
- package/dist/Alarm-4J9B8Fg8.mjs.map +1 -0
- package/dist/Alarm.mjs +1 -1
- package/dist/BrowserRestCapture.mjs.map +1 -1
- package/dist/BrowserRestCrawl.mjs.map +1 -1
- package/dist/{BrowserSession-Bx0V1-6_.d.mts → BrowserSession-Cojp7nth.d.mts} +3 -2
- package/dist/{BrowserSession-BfNmyayH.mjs → BrowserSession-aovMjfL4.mjs} +14 -14
- package/dist/BrowserSession-aovMjfL4.mjs.map +1 -0
- package/dist/BrowserSession.d.mts +1 -1
- package/dist/BrowserSession.mjs +2 -2
- package/dist/BrowserUse.d.mts +40 -64
- package/dist/BrowserUse.mjs +223 -2
- package/dist/BrowserUse.mjs.map +1 -0
- package/dist/CloudflareBrowser-Vt8qTdYW.mjs.map +1 -1
- package/dist/CloudflareCodeMode.mjs.map +1 -1
- package/dist/CloudflareScheduling.mjs +9 -9
- package/dist/CloudflareScheduling.mjs.map +1 -1
- package/dist/CloudflareSubscriptions.mjs +15 -15
- package/dist/CloudflareSubscriptions.mjs.map +1 -1
- package/dist/{CloudflareThreadClient-DgZVrZzF.mjs → CloudflareThreadClient-DU548EEj.mjs} +42 -39
- package/dist/CloudflareThreadClient-DU548EEj.mjs.map +1 -0
- package/dist/{CloudflareThreadClient-DuRG0MLd.d.mts → CloudflareThreadClient-lTm6baDP.d.mts} +353 -353
- package/dist/CloudflareThreadClient.d.mts +1 -1
- package/dist/CloudflareThreadClient.mjs +1 -1
- package/dist/{InteractiveBrowser-LdKzQeLR.mjs → InteractiveBrowser-BHNlwjZL.mjs} +40 -31
- package/dist/InteractiveBrowser-BHNlwjZL.mjs.map +1 -0
- package/dist/InteractiveBrowser.mjs +1 -1
- package/dist/{ThreadObject-CugPnWsJ.d.mts → ThreadObject-BLYgzcdT.d.mts} +6 -6
- package/dist/{ThreadObject-CGIaz6Jv.mjs → ThreadObject-C2LF3btz.mjs} +13 -14
- package/dist/ThreadObject-C2LF3btz.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +1 -1
- package/dist/ThreadObject.mjs +1 -1
- package/dist/WakeScheduler.d.mts +1 -1
- package/dist/WakeScheduler.mjs +8 -3
- package/dist/WakeScheduler.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +5 -5
- package/dist/{prepared-admission-CSdzFM_v.mjs → prepared-admission-BoUPHSNQ.mjs} +2 -2
- package/dist/{prepared-admission-CSdzFM_v.mjs.map → prepared-admission-BoUPHSNQ.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/Alarm.ts +82 -39
- package/src/BrowserRestCapture.ts +1 -0
- package/src/BrowserRestCrawl.ts +1 -0
- package/src/BrowserSession.ts +3 -2
- package/src/BrowserUse.ts +287 -1504
- package/src/CloudflareCodeMode.ts +6 -0
- package/src/CloudflareScheduling.ts +103 -103
- package/src/CloudflareSubscriptions.ts +192 -199
- package/src/CloudflareThreadClient.ts +256 -268
- package/src/InteractiveBrowser.ts +43 -17
- package/src/ThreadObject.ts +35 -24
- package/src/WakeScheduler.ts +12 -9
- package/src/internal/browser-binding.ts +8 -0
- package/src/internal/browser-credentials.ts +18 -13
- package/src/internal/browser-file-selection.ts +13 -2
- package/src/internal/browser-quick-action.ts +1 -0
- package/src/internal/browser-session-lifecycle.ts +32 -36
- package/src/internal/due-queue.ts +38 -19
- package/src/internal/layers.ts +53 -53
- package/dist/Alarm-BMEAZo3p.mjs.map +0 -1
- package/dist/BrowserSession-BfNmyayH.mjs.map +0 -1
- package/dist/BrowserUse-Bc2JdLW2.mjs +0 -1528
- package/dist/BrowserUse-Bc2JdLW2.mjs.map +0 -1
- package/dist/CloudflareThreadClient-DgZVrZzF.mjs.map +0 -1
- package/dist/InteractiveBrowser-LdKzQeLR.mjs.map +0 -1
- package/dist/ThreadObject-CGIaz6Jv.mjs.map +0 -1
- package/dist/page-function-CmUCcyNP.mjs +0 -11
- package/dist/page-function-CmUCcyNP.mjs.map +0 -1
- package/src/internal/browser-dom.ts +0 -886
- package/src/internal/browser-jev-dom.ts +0 -362
|
@@ -527,6 +527,8 @@ const makeProductionCdpSession = (session: CDPSession): BrowserRunInteractiveCdp
|
|
|
527
527
|
detach: () => session.detach(),
|
|
528
528
|
});
|
|
529
529
|
|
|
530
|
+
// Native Puppeteer adapter control signal; never enters the Effect failure channel.
|
|
531
|
+
// @effect-diagnostics-next-line extendsNativeError:off
|
|
530
532
|
class BrowserRunActionUndispatched extends Error {
|
|
531
533
|
readonly matchCount: number;
|
|
532
534
|
|
|
@@ -549,8 +551,10 @@ const readActionTarget = (page: Page, selector: string): Promise<unknown> =>
|
|
|
549
551
|
if (cause instanceof Error && cause.name === "SyntaxError") {
|
|
550
552
|
return { matchCount: 0, invalidSelector: true };
|
|
551
553
|
}
|
|
554
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original Chromium/Puppeteer exception at this native SDK callback boundary.
|
|
552
555
|
throw cause;
|
|
553
556
|
}
|
|
557
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Runs in Chromium through page.evaluate; Effect is unavailable in that realm.
|
|
554
558
|
if (typeof matches !== "object" || matches === null) throw new Error("Invalid query result");
|
|
555
559
|
const matchCount = Math.min(10_000, Reflect.get(matches, "length"));
|
|
556
560
|
const element = Reflect.get(matches, 0);
|
|
@@ -726,6 +730,7 @@ const makeActionRequestTracker = (page: Page, signal: AbortSignal) => {
|
|
|
726
730
|
if (signal.aborted) close();
|
|
727
731
|
} catch (cause) {
|
|
728
732
|
close();
|
|
733
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original Chromium/Puppeteer exception at this native SDK callback boundary.
|
|
729
734
|
throw cause;
|
|
730
735
|
}
|
|
731
736
|
|
|
@@ -1151,6 +1156,7 @@ const runObservedPageAction = async (
|
|
|
1151
1156
|
validate?: (element: NonNullable<Awaited<ReturnType<Page["$"]>>>) => Promise<boolean>,
|
|
1152
1157
|
onComplete?: () => void,
|
|
1153
1158
|
): Promise<unknown> => {
|
|
1159
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1154
1160
|
if (signal.aborted) throw new BrowserRunActionUndispatched(0);
|
|
1155
1161
|
|
|
1156
1162
|
const before = Schema.decodeUnknownSync(ActionTargetState)(
|
|
@@ -1158,6 +1164,7 @@ const runObservedPageAction = async (
|
|
|
1158
1164
|
);
|
|
1159
1165
|
|
|
1160
1166
|
if (before.matchCount !== 1 || before.invalidSelector === true) {
|
|
1167
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1161
1168
|
throw new BrowserRunActionUndispatched(before.matchCount);
|
|
1162
1169
|
}
|
|
1163
1170
|
// Resolve one CSS match inside this action's native adapter boundary. The
|
|
@@ -1173,6 +1180,7 @@ const runObservedPageAction = async (
|
|
|
1173
1180
|
requestedSelector,
|
|
1174
1181
|
]);
|
|
1175
1182
|
|
|
1183
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Runs in Chromium through page.evaluate; Effect is unavailable in that realm.
|
|
1176
1184
|
if (typeof matches !== "object" || matches === null) throw new Error("Invalid query result");
|
|
1177
1185
|
const count = Reflect.get(matches, "length");
|
|
1178
1186
|
|
|
@@ -1183,6 +1191,7 @@ const runObservedPageAction = async (
|
|
|
1183
1191
|
|
|
1184
1192
|
try {
|
|
1185
1193
|
if (!(target instanceof ElementHandle)) {
|
|
1194
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1186
1195
|
throw new BrowserRunActionUndispatched(
|
|
1187
1196
|
Schema.decodeUnknownSync(Schema.Natural)(await target.jsonValue()),
|
|
1188
1197
|
);
|
|
@@ -1195,6 +1204,7 @@ const runObservedPageAction = async (
|
|
|
1195
1204
|
}
|
|
1196
1205
|
if (signal.aborted) {
|
|
1197
1206
|
await disposeActionHandles([element]);
|
|
1207
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1198
1208
|
throw new BrowserRunActionUndispatched(1);
|
|
1199
1209
|
}
|
|
1200
1210
|
|
|
@@ -1209,10 +1219,12 @@ const runObservedPageAction = async (
|
|
|
1209
1219
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1210
1220
|
try {
|
|
1211
1221
|
if (validate !== undefined && !(await validate(element)))
|
|
1222
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1212
1223
|
throw new BrowserRunActionUndispatched(1);
|
|
1213
1224
|
tracker = makeActionRequestTracker(page, signal);
|
|
1214
1225
|
// No await between the final cancellation fence and SDK dispatch. Once
|
|
1215
1226
|
// dispatched, interruption is uncertain, even if the SDK later resolves.
|
|
1227
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1216
1228
|
if (signal.aborted) throw new BrowserRunActionUndispatched(1);
|
|
1217
1229
|
onDispatch();
|
|
1218
1230
|
const inputStarted = performance.now();
|
|
@@ -1254,6 +1266,7 @@ const guardedPageInput = async (
|
|
|
1254
1266
|
}),
|
|
1255
1267
|
);
|
|
1256
1268
|
|
|
1269
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer input port reports non-dispatch before the Effect owner classifies the result.
|
|
1257
1270
|
if (observed._tag !== "Text") throw new BrowserRunActionUndispatched(1);
|
|
1258
1271
|
};
|
|
1259
1272
|
|
|
@@ -1281,6 +1294,7 @@ const makeProductionPage = (page: Page): BrowserRunInteractivePage => {
|
|
|
1281
1294
|
try {
|
|
1282
1295
|
await page.close();
|
|
1283
1296
|
} catch (cause) {
|
|
1297
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original Chromium/Puppeteer exception at this native SDK callback boundary.
|
|
1284
1298
|
if (!isRemoteClosure(cause)) throw cause;
|
|
1285
1299
|
}
|
|
1286
1300
|
},
|
|
@@ -1338,6 +1352,7 @@ const makeProductionPage = (page: Page): BrowserRunInteractivePage => {
|
|
|
1338
1352
|
prototype = Reflect.getPrototypeOf(prototype);
|
|
1339
1353
|
}
|
|
1340
1354
|
if (setValue === undefined) {
|
|
1355
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Runs inside the Chromium evaluate callback, whose failures cross CDP as native exceptions.
|
|
1341
1356
|
throw new Error("The selector did not resolve to a fillable field");
|
|
1342
1357
|
}
|
|
1343
1358
|
const focus = Reflect.get(element, "focus");
|
|
@@ -1418,6 +1433,7 @@ const makeProductionContext = (
|
|
|
1418
1433
|
try {
|
|
1419
1434
|
await context.close();
|
|
1420
1435
|
} catch (cause) {
|
|
1436
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original Chromium/Puppeteer exception at this native SDK callback boundary.
|
|
1421
1437
|
if (!isRemoteClosure(cause)) throw cause;
|
|
1422
1438
|
}
|
|
1423
1439
|
},
|
|
@@ -1437,6 +1453,7 @@ const makeProductionBrowser = (
|
|
|
1437
1453
|
if (context === undefined) return undefined;
|
|
1438
1454
|
const pages = await context.pages();
|
|
1439
1455
|
|
|
1456
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Native browser reattachment port rejects an oversized SDK inventory; the Effect caller maps the failure.
|
|
1440
1457
|
if (pages.length > 64) throw new Error("The browser target inventory exceeds its bound");
|
|
1441
1458
|
for (const candidate of pages) {
|
|
1442
1459
|
const page = makeProductionPage(candidate);
|
|
@@ -1455,6 +1472,7 @@ const makeProductionBrowser = (
|
|
|
1455
1472
|
try {
|
|
1456
1473
|
await browser.close();
|
|
1457
1474
|
} catch (cause) {
|
|
1475
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original Chromium/Puppeteer exception at this native SDK callback boundary.
|
|
1458
1476
|
if (!isRemoteClosure(cause)) throw cause;
|
|
1459
1477
|
}
|
|
1460
1478
|
},
|
|
@@ -1605,6 +1623,8 @@ const hostAllowed = (policy: InteractiveBrowserPolicySnapshot, value: string): b
|
|
|
1605
1623
|
const keepAliveMillis = (policy: InteractiveBrowserPolicySnapshot): number =>
|
|
1606
1624
|
Math.max(MIN_KEEP_ALIVE_MILLIS, Math.min(MAX_KEEP_ALIVE_MILLIS, policy.maxElapsedMillis));
|
|
1607
1625
|
|
|
1626
|
+
// Native acquisition cancellation signal, intercepted before mapping to an Effect failure.
|
|
1627
|
+
// @effect-diagnostics-next-line extendsNativeError:off
|
|
1608
1628
|
class LateBrowserAcquisition extends Error {}
|
|
1609
1629
|
|
|
1610
1630
|
const closeLateAcquisition = async <A>(
|
|
@@ -1623,6 +1643,7 @@ const closeLateAcquisition = async <A>(
|
|
|
1623
1643
|
} catch {
|
|
1624
1644
|
// The captured runtime reports cleanup failures before this callback absorbs rejection.
|
|
1625
1645
|
}
|
|
1646
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Reject a late native SDK acquisition after cleanup; the interrupted Effect no longer owns it.
|
|
1626
1647
|
throw new LateBrowserAcquisition("The interrupted browser acquisition completed late");
|
|
1627
1648
|
};
|
|
1628
1649
|
|
|
@@ -2806,15 +2827,19 @@ const makeHostService = (
|
|
|
2806
2827
|
resume === undefined
|
|
2807
2828
|
? undefined
|
|
2808
2829
|
: yield* withinDeadline(
|
|
2809
|
-
Effect.
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2830
|
+
Effect.gen(function* () {
|
|
2831
|
+
if (browser.reattach === undefined || browser.detach === undefined)
|
|
2832
|
+
return yield* protocolError(
|
|
2833
|
+
"The browser binding does not support exact reattachment",
|
|
2834
|
+
);
|
|
2835
|
+
|
|
2836
|
+
const reattach = browser.reattach.bind(browser);
|
|
2837
|
+
|
|
2838
|
+
return yield* Effect.tryPromise({
|
|
2839
|
+
try: () => reattach(resume.page),
|
|
2840
|
+
catch: (cause) =>
|
|
2841
|
+
protocolError("Reattaching the exact browser page failed", cause),
|
|
2842
|
+
});
|
|
2818
2843
|
}).pipe(
|
|
2819
2844
|
Effect.timeoutOrElse({
|
|
2820
2845
|
duration: "10 seconds",
|
|
@@ -3032,15 +3057,16 @@ const makeHostService = (
|
|
|
3032
3057
|
);
|
|
3033
3058
|
|
|
3034
3059
|
const pageIdentity = yield* Effect.cached(
|
|
3035
|
-
Effect.
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3060
|
+
Effect.gen(function* () {
|
|
3061
|
+
if (page.identity === undefined)
|
|
3062
|
+
return yield* protocolError("The browser binding does not expose page identity");
|
|
3063
|
+
const identity = page.identity.bind(page);
|
|
3039
3064
|
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3065
|
+
return yield* Effect.tryPromise({
|
|
3066
|
+
try: () => identity(),
|
|
3067
|
+
catch: (cause) =>
|
|
3068
|
+
protocolError("Reading the exact browser page identity failed", cause),
|
|
3069
|
+
});
|
|
3044
3070
|
}).pipe(
|
|
3045
3071
|
Effect.catchTag("InteractiveBrowserProtocolError", (error) =>
|
|
3046
3072
|
failReportedProtocol("interactive.checkpoint", error),
|
package/src/ThreadObject.ts
CHANGED
|
@@ -324,24 +324,31 @@ const gateAdmissionLimits = Effect.fnUntraced(function* (
|
|
|
324
324
|
const ledger = yield* SubmissionLedger;
|
|
325
325
|
const { ctx } = yield* DurableObjectContext;
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
// Admission checks the key durably. The host needs an extra lookup only to
|
|
328
|
+
// exempt a retained receipt when a current limit would otherwise refuse it.
|
|
329
|
+
const refuseFresh = (error: AdmissionLimitExceeded) =>
|
|
330
|
+
ledger
|
|
331
|
+
.lookup(
|
|
332
|
+
SubmissionLookupByKey.make({
|
|
333
|
+
threadId,
|
|
334
|
+
principal: request.principal,
|
|
335
|
+
idempotencyKey: request.idempotencyKey,
|
|
336
|
+
}),
|
|
337
|
+
)
|
|
338
|
+
.pipe(
|
|
339
|
+
Effect.flatMap((existing) => (Option.isSome(existing) ? Effect.void : Effect.fail(error))),
|
|
340
|
+
);
|
|
336
341
|
|
|
337
342
|
const inputBytes = utf8Bytes(request.inputPayload);
|
|
338
343
|
|
|
339
344
|
if (inputBytes > config.limits.maxInputBytes) {
|
|
340
|
-
return yield*
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
+
return yield* refuseFresh(
|
|
346
|
+
AdmissionLimitExceeded.make({
|
|
347
|
+
limit: "input-bytes",
|
|
348
|
+
actual: inputBytes,
|
|
349
|
+
maximum: config.limits.maxInputBytes,
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
345
352
|
}
|
|
346
353
|
|
|
347
354
|
const nonterminal = yield* ledger.scanNonterminal.pipe(
|
|
@@ -350,21 +357,25 @@ const gateAdmissionLimits = Effect.fnUntraced(function* (
|
|
|
350
357
|
);
|
|
351
358
|
|
|
352
359
|
if (nonterminal.length >= config.limits.maxQueueDepthPerLane) {
|
|
353
|
-
return yield*
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
return yield* refuseFresh(
|
|
361
|
+
AdmissionLimitExceeded.make({
|
|
362
|
+
limit: "queue-depth",
|
|
363
|
+
actual: nonterminal.length,
|
|
364
|
+
maximum: config.limits.maxQueueDepthPerLane,
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
358
367
|
}
|
|
359
368
|
|
|
360
369
|
const databaseBytes = yield* Effect.sync(() => ctx.storage.sql.databaseSize);
|
|
361
370
|
|
|
362
371
|
if (databaseBytes > config.limits.maxDatabaseBytes) {
|
|
363
|
-
return yield*
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
return yield* refuseFresh(
|
|
373
|
+
AdmissionLimitExceeded.make({
|
|
374
|
+
limit: "database-bytes",
|
|
375
|
+
actual: databaseBytes,
|
|
376
|
+
maximum: config.limits.maxDatabaseBytes,
|
|
377
|
+
}),
|
|
378
|
+
);
|
|
368
379
|
}
|
|
369
380
|
});
|
|
370
381
|
|
package/src/WakeScheduler.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CurrentNativeSource } from "@yielded/agent-storage-cloudflare/port-routing";
|
|
1
2
|
import { type ThreadId } from "@yielded/agent/identifiers";
|
|
2
3
|
import { makeWakeSubscriptionHub, WakeScheduler } from "@yielded/agent/wake-scheduler";
|
|
3
4
|
import { Effect, Layer, PubSub, Schema, Stream } from "effect";
|
|
@@ -31,7 +32,7 @@ class RemoteWakeDropped extends Schema.TaggedError<RemoteWakeDropped>()("RemoteW
|
|
|
31
32
|
* - `notify(local)` wakes progress and settlement registrations, publishes a worker hint, and
|
|
32
33
|
* schedules the earliest alarm. Processing an owned Thread defers immediate alarms until
|
|
33
34
|
* processing exits; pre-armed recovery deadlines remain active. Progress hints skip settlement
|
|
34
|
-
* registrations
|
|
35
|
+
* registrations; single-Thread native progress also omits the redundant worker hint.
|
|
35
36
|
* - `notify(remote)` → fire-and-forget `wake()` on the owning Object's stub with every error
|
|
36
37
|
* swallowed and logged: hints are droppable, and the target's own alarm/scan pairing (the
|
|
37
38
|
* maintenance pass re-polls unsettled children) guarantees liveness without this call.
|
|
@@ -59,15 +60,17 @@ export const cloudflareWakeSchedulerLayer: Layer.Layer<
|
|
|
59
60
|
),
|
|
60
61
|
);
|
|
61
62
|
|
|
62
|
-
const notifyLocal = (threadId: ThreadId, kind?: "progress")
|
|
63
|
-
|
|
63
|
+
const notifyLocal = Effect.fnUntraced(function* (threadId: ThreadId, kind?: "progress") {
|
|
64
|
+
yield* kind === "progress"
|
|
64
65
|
? progress.notify(threadId)
|
|
65
|
-
: settlements.notify(threadId).pipe(Effect.andThen(progress.notify(threadId)))
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
: settlements.notify(threadId).pipe(Effect.andThen(progress.notify(threadId)));
|
|
67
|
+
|
|
68
|
+
// Progress waiters still observe every hint. The single-Thread maintenance loop
|
|
69
|
+
// already awaits the running native fiber; its source commits enroll auxiliary work.
|
|
70
|
+
if (kind !== "progress" || placement.threadId === undefined || !(yield* CurrentNativeSource))
|
|
71
|
+
yield* PubSub.publish(hints, threadId);
|
|
72
|
+
yield* scheduleLocal;
|
|
73
|
+
});
|
|
71
74
|
|
|
72
75
|
const notifyRemote = (threadId: ThreadId) =>
|
|
73
76
|
callThreadObject(
|
|
@@ -69,10 +69,12 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
69
69
|
} catch {
|
|
70
70
|
// Preserve the provider refusal when local response release also fails.
|
|
71
71
|
}
|
|
72
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native BrowserRun acquisition Promise preserves the provider refusal after body cleanup.
|
|
72
73
|
throw failure;
|
|
73
74
|
}
|
|
74
75
|
const socket = response.webSocket;
|
|
75
76
|
|
|
77
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native BrowserRun upgrade Promise must reject a malformed WebSocket response.
|
|
76
78
|
if (socket === null) throw new BrowserRunFailure({ operation, reason: "malformed" });
|
|
77
79
|
|
|
78
80
|
return socket;
|
|
@@ -98,6 +100,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
98
100
|
} catch {
|
|
99
101
|
// Body cancellation cannot replace the acquisition refusal.
|
|
100
102
|
}
|
|
103
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native BrowserRun acquisition Promise preserves the provider refusal after body cleanup.
|
|
101
104
|
throw failure;
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -115,6 +118,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
115
118
|
const transport: ConnectionTransport = {
|
|
116
119
|
send: (message) => {
|
|
117
120
|
if (retired || socket === undefined)
|
|
121
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer transport rejects fenced dispatch or initialization; its Effect owner maps the failure.
|
|
118
122
|
throw new BrowserRunFailure({ operation, reason: "provider" });
|
|
119
123
|
socket.send(message);
|
|
120
124
|
},
|
|
@@ -153,6 +157,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
153
157
|
} catch (cause) {
|
|
154
158
|
retirementFailure ??= browserFailure(disconnectOperation, cause);
|
|
155
159
|
}
|
|
160
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native transport close callback preserves the first retirement failure for its Effect owner.
|
|
156
161
|
if (retirementFailure !== undefined) throw retirementFailure;
|
|
157
162
|
};
|
|
158
163
|
|
|
@@ -177,6 +182,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
177
182
|
// failure independently of the cancellation already returned to the owner.
|
|
178
183
|
retireNow();
|
|
179
184
|
signal?.throwIfAborted();
|
|
185
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native Puppeteer transport rejects fenced dispatch or initialization; its Effect owner maps the failure.
|
|
180
186
|
throw new BrowserRunFailure({ operation, reason: "provider" });
|
|
181
187
|
}
|
|
182
188
|
socket.addEventListener("message", onMessage);
|
|
@@ -207,6 +213,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
207
213
|
signal?.aborted === true &&
|
|
208
214
|
(cause === signal.reason || (closedByAbort && cause.name === "TargetCloseError"));
|
|
209
215
|
|
|
216
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Native SDK initialization Promise preserves cancellation provenance after transport retirement.
|
|
210
217
|
throw localCancellation ? reportedBrowserError(failure) : failure;
|
|
211
218
|
} finally {
|
|
212
219
|
signal?.removeEventListener("abort", abort);
|
|
@@ -257,6 +264,7 @@ export class BrowserRunBinding extends Context.Service<
|
|
|
257
264
|
if (acquired !== undefined) await runReport(release(acquired));
|
|
258
265
|
if (signal.aborted && cause !== signal.reason)
|
|
259
266
|
await runReport(reportBrowserCause(operation, Cause.fail(cause)));
|
|
267
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Rethrow the foreign upgrade failure after late-acquisition cleanup, preserving abort identity.
|
|
260
268
|
throw cause;
|
|
261
269
|
}
|
|
262
270
|
},
|
|
@@ -134,7 +134,7 @@ export const fillCredential = Effect.fnUntraced(function* (
|
|
|
134
134
|
let filled = 0;
|
|
135
135
|
|
|
136
136
|
const fail = (reason: CredentialFillError["reason"]) =>
|
|
137
|
-
|
|
137
|
+
CredentialFillError.make({ reason, dispatch, filled, cleanup: "not-requested" });
|
|
138
138
|
|
|
139
139
|
const remote = <A>(run: (signal: AbortSignal) => Promise<A>) =>
|
|
140
140
|
Effect.tryPromise({
|
|
@@ -164,28 +164,33 @@ export const fillCredential = Effect.fnUntraced(function* (
|
|
|
164
164
|
|
|
165
165
|
if (signal.aborted) {
|
|
166
166
|
await Promise.all(handles.map((handle) => handle.dispose().catch(() => {})));
|
|
167
|
-
|
|
167
|
+
|
|
168
|
+
return undefined;
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
return handles;
|
|
171
|
-
}).pipe(
|
|
172
|
+
}).pipe(
|
|
173
|
+
Effect.filterOrFail(
|
|
174
|
+
(handles) => handles !== undefined,
|
|
175
|
+
() => fail("provider"),
|
|
176
|
+
),
|
|
177
|
+
Effect.interruptible,
|
|
178
|
+
),
|
|
172
179
|
release,
|
|
173
180
|
);
|
|
174
181
|
|
|
175
|
-
const origin = (value: string)
|
|
176
|
-
Effect.try({
|
|
177
|
-
try: () =>
|
|
178
|
-
|
|
182
|
+
const origin = Effect.fnUntraced(function* (value: string) {
|
|
183
|
+
const url = yield* Effect.try({
|
|
184
|
+
try: () => new URL(value),
|
|
185
|
+
catch: () => fail("denied"),
|
|
186
|
+
});
|
|
179
187
|
|
|
180
|
-
|
|
188
|
+
if (url.username || url.password) return yield* fail("denied");
|
|
181
189
|
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
catch: () => fail("denied"),
|
|
185
|
-
}).pipe(
|
|
186
|
-
Effect.flatMap(Schema.decodeUnknownEffect(CredentialOrigin)),
|
|
190
|
+
return yield* Schema.decodeEffect(CredentialOrigin)(url.origin).pipe(
|
|
187
191
|
Effect.mapError(() => fail("denied")),
|
|
188
192
|
);
|
|
193
|
+
});
|
|
189
194
|
|
|
190
195
|
return yield* Effect.gen(function* () {
|
|
191
196
|
const request = yield* Schema.decodeEffect(FillCredentialRequest, {
|
|
@@ -14,6 +14,7 @@ function setFileSelection(element: object, encoded: string, name: string, type:
|
|
|
14
14
|
Reflect.get(element, "disabled") ||
|
|
15
15
|
Reflect.get(element, "webkitdirectory")
|
|
16
16
|
) {
|
|
17
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Runs in Chromium; a native exception is the CDP evaluation failure protocol.
|
|
17
18
|
throw new Error("The target is not an enabled file input");
|
|
18
19
|
}
|
|
19
20
|
const bytes = Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0));
|
|
@@ -30,6 +31,7 @@ function setFileSelection(element: object, encoded: string, name: string, type:
|
|
|
30
31
|
selected[0]?.name !== name ||
|
|
31
32
|
selected[0]?.type !== type
|
|
32
33
|
) {
|
|
34
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Runs in Chromium; a native exception is the CDP evaluation failure protocol.
|
|
33
35
|
throw new Error("The input did not retain the selected file");
|
|
34
36
|
}
|
|
35
37
|
const dispatch = Reflect.get(element, "dispatchEvent");
|
|
@@ -48,7 +50,9 @@ export const makeFileSelection =
|
|
|
48
50
|
await session.send("Page.enable");
|
|
49
51
|
await session.send("Page.setInterceptFileChooserDialog", { enabled: true });
|
|
50
52
|
} catch (cause) {
|
|
51
|
-
|
|
53
|
+
// Initialization remains the primary failure if best-effort cleanup also rejects.
|
|
54
|
+
await session.detach().catch(() => {});
|
|
55
|
+
// oxlint-disable-next-line effect/no-untyped-throw -- Preserve the original CDP initialization failure after detaching its session.
|
|
52
56
|
throw cause;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -68,11 +72,14 @@ export const makeFileSelection =
|
|
|
68
72
|
const opened = new Promise<number>((resolve, reject) => {
|
|
69
73
|
onOpened = (event) =>
|
|
70
74
|
event.backendNodeId === undefined
|
|
71
|
-
?
|
|
75
|
+
? // oxlint-disable-next-line effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
76
|
+
reject(new Error("The chooser has no file input"))
|
|
72
77
|
: resolve(event.backendNodeId);
|
|
78
|
+
// oxlint-disable-next-line effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
73
79
|
onAbort = () => reject(new Error("File selection interrupted"));
|
|
74
80
|
session.on("Page.fileChooserOpened", onOpened);
|
|
75
81
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
82
|
+
// oxlint-disable-next-line effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
76
83
|
timer = setTimeout(() => reject(new Error("No file chooser opened")), 5_000);
|
|
77
84
|
if (signal.aborted) onAbort();
|
|
78
85
|
});
|
|
@@ -80,12 +87,15 @@ export const makeFileSelection =
|
|
|
80
87
|
try {
|
|
81
88
|
const [backendNodeId] = await Promise.all([opened, element.click()]);
|
|
82
89
|
|
|
90
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
83
91
|
if (signal.aborted) throw new Error("File selection interrupted");
|
|
84
92
|
const resolved = await session.send("DOM.resolveNode", { backendNodeId });
|
|
85
93
|
|
|
86
94
|
const objectId = resolved.object.objectId;
|
|
87
95
|
|
|
96
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
88
97
|
if (objectId === undefined) throw new Error("The chooser has no file input");
|
|
98
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
89
99
|
if (signal.aborted) throw new Error("File selection interrupted");
|
|
90
100
|
|
|
91
101
|
const result = await session.send("Runtime.callFunctionOn", {
|
|
@@ -100,6 +110,7 @@ export const makeFileSelection =
|
|
|
100
110
|
});
|
|
101
111
|
|
|
102
112
|
if (result.exceptionDetails !== undefined)
|
|
113
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Native CDP file-chooser port rejects through its Promise; its Effect caller owns typed error mapping.
|
|
103
114
|
throw new Error("The chooser did not select the file");
|
|
104
115
|
} finally {
|
|
105
116
|
clearTimeout(timer);
|
|
@@ -290,6 +290,7 @@ const navigationError = (message: string, cause?: unknown): PageCaptureNavigatio
|
|
|
290
290
|
|
|
291
291
|
/** Preserve bounded remote diagnostics for the host without exposing their text to a model. */
|
|
292
292
|
const privateResponseCause = (bodyText: string, response: Response): Error =>
|
|
293
|
+
// oxlint-disable-next-line effect/no-native-error -- Bounded foreign response diagnostic retains host metadata as cause, never as the Effect failure.
|
|
293
294
|
new Error(boundedDiagnostic(bodyText), {
|
|
294
295
|
cause: {
|
|
295
296
|
provider: "cloudflare-browser-run",
|