@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.
Files changed (70) hide show
  1. package/dist/{Alarm-BMEAZo3p.mjs → Alarm-4J9B8Fg8.mjs} +54 -32
  2. package/dist/Alarm-4J9B8Fg8.mjs.map +1 -0
  3. package/dist/Alarm.mjs +1 -1
  4. package/dist/BrowserRestCapture.mjs.map +1 -1
  5. package/dist/BrowserRestCrawl.mjs.map +1 -1
  6. package/dist/{BrowserSession-Bx0V1-6_.d.mts → BrowserSession-Cojp7nth.d.mts} +3 -2
  7. package/dist/{BrowserSession-BfNmyayH.mjs → BrowserSession-aovMjfL4.mjs} +14 -14
  8. package/dist/BrowserSession-aovMjfL4.mjs.map +1 -0
  9. package/dist/BrowserSession.d.mts +1 -1
  10. package/dist/BrowserSession.mjs +2 -2
  11. package/dist/BrowserUse.d.mts +40 -64
  12. package/dist/BrowserUse.mjs +223 -2
  13. package/dist/BrowserUse.mjs.map +1 -0
  14. package/dist/CloudflareBrowser-Vt8qTdYW.mjs.map +1 -1
  15. package/dist/CloudflareCodeMode.mjs.map +1 -1
  16. package/dist/CloudflareScheduling.mjs +9 -9
  17. package/dist/CloudflareScheduling.mjs.map +1 -1
  18. package/dist/CloudflareSubscriptions.mjs +15 -15
  19. package/dist/CloudflareSubscriptions.mjs.map +1 -1
  20. package/dist/{CloudflareThreadClient-DgZVrZzF.mjs → CloudflareThreadClient-DU548EEj.mjs} +42 -39
  21. package/dist/CloudflareThreadClient-DU548EEj.mjs.map +1 -0
  22. package/dist/{CloudflareThreadClient-DuRG0MLd.d.mts → CloudflareThreadClient-lTm6baDP.d.mts} +353 -353
  23. package/dist/CloudflareThreadClient.d.mts +1 -1
  24. package/dist/CloudflareThreadClient.mjs +1 -1
  25. package/dist/{InteractiveBrowser-LdKzQeLR.mjs → InteractiveBrowser-BHNlwjZL.mjs} +40 -31
  26. package/dist/InteractiveBrowser-BHNlwjZL.mjs.map +1 -0
  27. package/dist/InteractiveBrowser.mjs +1 -1
  28. package/dist/{ThreadObject-CugPnWsJ.d.mts → ThreadObject-BLYgzcdT.d.mts} +6 -6
  29. package/dist/{ThreadObject-CGIaz6Jv.mjs → ThreadObject-C2LF3btz.mjs} +13 -14
  30. package/dist/ThreadObject-C2LF3btz.mjs.map +1 -0
  31. package/dist/ThreadObject.d.mts +1 -1
  32. package/dist/ThreadObject.mjs +1 -1
  33. package/dist/WakeScheduler.d.mts +1 -1
  34. package/dist/WakeScheduler.mjs +8 -3
  35. package/dist/WakeScheduler.mjs.map +1 -1
  36. package/dist/index.d.mts +3 -3
  37. package/dist/index.mjs +5 -5
  38. package/dist/{prepared-admission-CSdzFM_v.mjs → prepared-admission-BoUPHSNQ.mjs} +2 -2
  39. package/dist/{prepared-admission-CSdzFM_v.mjs.map → prepared-admission-BoUPHSNQ.mjs.map} +1 -1
  40. package/package.json +1 -1
  41. package/src/Alarm.ts +82 -39
  42. package/src/BrowserRestCapture.ts +1 -0
  43. package/src/BrowserRestCrawl.ts +1 -0
  44. package/src/BrowserSession.ts +3 -2
  45. package/src/BrowserUse.ts +287 -1504
  46. package/src/CloudflareCodeMode.ts +6 -0
  47. package/src/CloudflareScheduling.ts +103 -103
  48. package/src/CloudflareSubscriptions.ts +192 -199
  49. package/src/CloudflareThreadClient.ts +256 -268
  50. package/src/InteractiveBrowser.ts +43 -17
  51. package/src/ThreadObject.ts +35 -24
  52. package/src/WakeScheduler.ts +12 -9
  53. package/src/internal/browser-binding.ts +8 -0
  54. package/src/internal/browser-credentials.ts +18 -13
  55. package/src/internal/browser-file-selection.ts +13 -2
  56. package/src/internal/browser-quick-action.ts +1 -0
  57. package/src/internal/browser-session-lifecycle.ts +32 -36
  58. package/src/internal/due-queue.ts +38 -19
  59. package/src/internal/layers.ts +53 -53
  60. package/dist/Alarm-BMEAZo3p.mjs.map +0 -1
  61. package/dist/BrowserSession-BfNmyayH.mjs.map +0 -1
  62. package/dist/BrowserUse-Bc2JdLW2.mjs +0 -1528
  63. package/dist/BrowserUse-Bc2JdLW2.mjs.map +0 -1
  64. package/dist/CloudflareThreadClient-DgZVrZzF.mjs.map +0 -1
  65. package/dist/InteractiveBrowser-LdKzQeLR.mjs.map +0 -1
  66. package/dist/ThreadObject-CGIaz6Jv.mjs.map +0 -1
  67. package/dist/page-function-CmUCcyNP.mjs +0 -11
  68. package/dist/page-function-CmUCcyNP.mjs.map +0 -1
  69. package/src/internal/browser-dom.ts +0 -886
  70. 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.tryPromise({
2810
- try: async () => {
2811
- if (browser.reattach === undefined || browser.detach === undefined)
2812
- throw new Error("The browser binding does not support exact reattachment");
2813
-
2814
- return await browser.reattach(resume.page);
2815
- },
2816
- catch: (cause) =>
2817
- protocolError("Reattaching the exact browser page failed", cause),
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.tryPromise({
3036
- try: async () => {
3037
- if (page.identity === undefined)
3038
- throw new Error("The browser binding does not expose page identity");
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
- return await page.identity();
3041
- },
3042
- catch: (cause) =>
3043
- protocolError("Reading the exact browser page identity failed", cause),
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),
@@ -324,24 +324,31 @@ const gateAdmissionLimits = Effect.fnUntraced(function* (
324
324
  const ledger = yield* SubmissionLedger;
325
325
  const { ctx } = yield* DurableObjectContext;
326
326
 
327
- const existing = yield* ledger.lookup(
328
- SubmissionLookupByKey.make({
329
- threadId,
330
- principal: request.principal,
331
- idempotencyKey: request.idempotencyKey,
332
- }),
333
- );
334
-
335
- if (Option.isSome(existing)) return;
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* AdmissionLimitExceeded.make({
341
- limit: "input-bytes",
342
- actual: inputBytes,
343
- maximum: config.limits.maxInputBytes,
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* AdmissionLimitExceeded.make({
354
- limit: "queue-depth",
355
- actual: nonterminal.length,
356
- maximum: config.limits.maxQueueDepthPerLane,
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* AdmissionLimitExceeded.make({
364
- limit: "database-bytes",
365
- actual: databaseBytes,
366
- maximum: config.limits.maxDatabaseBytes,
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
 
@@ -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 only.
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
- (kind === "progress"
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
- ).pipe(
67
- Effect.andThen(PubSub.publish(hints, threadId)),
68
- Effect.andThen(scheduleLocal),
69
- Effect.asVoid,
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
- new CredentialFillError({ reason, dispatch, filled, cleanup: "not-requested" });
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
- throw fail("provider");
167
+
168
+ return undefined;
168
169
  }
169
170
 
170
171
  return handles;
171
- }).pipe(Effect.interruptible),
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
- const url = new URL(value);
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
- if (url.username || url.password) throw fail("denied");
188
+ if (url.username || url.password) return yield* fail("denied");
181
189
 
182
- return url.origin;
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
- await session.detach();
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
- ? reject(new Error("The chooser has no file input"))
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",