@yielded/agent-platform-cloudflare 0.1.0-beta.173 → 0.1.0-beta.175

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-B44efvB1.mjs} +162 -103
  2. package/dist/Alarm-B44efvB1.mjs.map +1 -0
  3. package/dist/Alarm.d.mts +2 -0
  4. package/dist/Alarm.mjs +1 -1
  5. package/dist/BrowserRestCapture.mjs.map +1 -1
  6. package/dist/BrowserRestCrawl.mjs.map +1 -1
  7. package/dist/{BrowserSession-Bx0V1-6_.d.mts → BrowserSession-Cojp7nth.d.mts} +3 -2
  8. package/dist/{BrowserSession-BfNmyayH.mjs → BrowserSession-aovMjfL4.mjs} +14 -14
  9. package/dist/BrowserSession-aovMjfL4.mjs.map +1 -0
  10. package/dist/BrowserSession.d.mts +1 -1
  11. package/dist/BrowserSession.mjs +2 -2
  12. package/dist/BrowserUse.d.mts +40 -64
  13. package/dist/BrowserUse.mjs +223 -2
  14. package/dist/BrowserUse.mjs.map +1 -0
  15. package/dist/CloudflareBrowser-Vt8qTdYW.mjs.map +1 -1
  16. package/dist/CloudflareCodeMode.mjs.map +1 -1
  17. package/dist/CloudflareScheduling.mjs +9 -9
  18. package/dist/CloudflareScheduling.mjs.map +1 -1
  19. package/dist/CloudflareSubscriptions.mjs +15 -15
  20. package/dist/CloudflareSubscriptions.mjs.map +1 -1
  21. package/dist/{CloudflareThreadClient-DgZVrZzF.mjs → CloudflareThreadClient-Ai3MTs3X.mjs} +43 -40
  22. package/dist/CloudflareThreadClient-Ai3MTs3X.mjs.map +1 -0
  23. package/dist/CloudflareThreadClient.mjs +1 -1
  24. package/dist/{InteractiveBrowser-LdKzQeLR.mjs → InteractiveBrowser-BHNlwjZL.mjs} +40 -31
  25. package/dist/InteractiveBrowser-BHNlwjZL.mjs.map +1 -0
  26. package/dist/InteractiveBrowser.mjs +1 -1
  27. package/dist/{ThreadObject-CRbLbdXM.d.mts → ThreadObject-CxNlhoPL.d.mts} +3 -3
  28. package/dist/{ThreadObject-CGIaz6Jv.mjs → ThreadObject-DGvlHWYi.mjs} +50 -52
  29. package/dist/ThreadObject-DGvlHWYi.mjs.map +1 -0
  30. package/dist/ThreadObject.d.mts +1 -1
  31. package/dist/ThreadObject.mjs +1 -1
  32. package/dist/WakeScheduler.d.mts +1 -1
  33. package/dist/WakeScheduler.mjs +8 -3
  34. package/dist/WakeScheduler.mjs.map +1 -1
  35. package/dist/index.d.mts +2 -2
  36. package/dist/index.mjs +5 -5
  37. package/dist/{prepared-admission-CSdzFM_v.mjs → prepared-admission-DYdffQ8K.mjs} +2 -2
  38. package/dist/{prepared-admission-CSdzFM_v.mjs.map → prepared-admission-DYdffQ8K.mjs.map} +1 -1
  39. package/package.json +1 -1
  40. package/src/Alarm.ts +293 -155
  41. package/src/BrowserRestCapture.ts +1 -0
  42. package/src/BrowserRestCrawl.ts +1 -0
  43. package/src/BrowserSession.ts +3 -2
  44. package/src/BrowserUse.ts +287 -1504
  45. package/src/CloudflareCodeMode.ts +6 -0
  46. package/src/CloudflareScheduling.ts +103 -103
  47. package/src/CloudflareSubscriptions.ts +192 -199
  48. package/src/CloudflareThreadClient.ts +258 -269
  49. package/src/InteractiveBrowser.ts +43 -17
  50. package/src/ThreadObject.ts +51 -45
  51. package/src/WakeScheduler.ts +12 -9
  52. package/src/internal/browser-binding.ts +8 -0
  53. package/src/internal/browser-credentials.ts +18 -13
  54. package/src/internal/browser-file-selection.ts +13 -2
  55. package/src/internal/browser-quick-action.ts +1 -0
  56. package/src/internal/browser-session-lifecycle.ts +32 -36
  57. package/src/internal/due-queue.ts +38 -19
  58. package/src/internal/layers.ts +108 -87
  59. package/src/internal/message-delivery.ts +61 -37
  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
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { DurableObjectContext } from "./CloudflareBindings.mjs";
2
+ import { DurableObjectContext, ThreadObjectPlacement } from "./CloudflareBindings.mjs";
3
3
  import { AuxiliaryDispatchMillis, CloudflareDurableRuntimeConfig } from "./CloudflareConfig.mjs";
4
4
  import { r as safeCauseMessage } from "./boundary-BguazVkh.mjs";
5
5
  import { CurrentNativeSource } from "@yielded/agent-storage-cloudflare/port-routing";
@@ -108,7 +108,7 @@ const make = (storage) => {
108
108
  }
109
109
  };
110
110
  const write = (changes) => {
111
- if (decode(sql.exec(`WITH changes (id, revision, dueAt, stalls, progressKey, notBefore, state, reported, expected_revision) AS (
111
+ if (sql.exec(`WITH changes (id, revision, dueAt, stalls, progressKey, notBefore, state, reported, expected_revision) AS (
112
112
  VALUES ${changes.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ")}
113
113
  ) INSERT INTO platform_cloudflare_due_queue
114
114
  (id, revision, dueAt, stalls, progressKey, notBefore, state, reported)
@@ -118,7 +118,7 @@ const make = (storage) => {
118
118
  notBefore = excluded.notBefore, state = excluded.state, reported = excluded.reported
119
119
  WHERE platform_cloudflare_due_queue.revision = (
120
120
  SELECT expected_revision FROM changes WHERE changes.id = excluded.id
121
- ) RETURNING id, revision, dueAt, stalls, progressKey, notBefore, state, reported`, ...changes.flatMap(({ before, after }) => [
121
+ ) RETURNING 1`, ...changes.flatMap(({ before, after }) => [
122
122
  after.id,
123
123
  after.revision,
124
124
  after.dueAt,
@@ -128,7 +128,7 @@ const make = (storage) => {
128
128
  after.state,
129
129
  after.reported,
130
130
  before?.revision ?? -1
131
- ])).toArray()).length !== changes.length) throw new Error("Maintenance due revision changed during transaction");
131
+ ])).toArray().length !== changes.length) throw new Error("Maintenance due revision changed during transaction");
132
132
  };
133
133
  const flush = (frame) => {
134
134
  if (frame.parent !== void 0) return;
@@ -205,6 +205,14 @@ const make = (storage) => {
205
205
  progressKey: progressKey ?? row?.progressKey ?? null
206
206
  }));
207
207
  };
208
+ /** Native checkpoints fence the generation, not each source commit. Once progress
209
+ * resets a claimed lane, further native commits owe the same recovery opportunity.
210
+ * A new claim, deadline, or retry budget makes the next commit advance it again. */
211
+ const progressNative = (dueAt) => {
212
+ const row = read().find((row) => row.id === Native);
213
+ if (row?.state === "pending" && row.dueAt !== null && row.dueAt <= dueAt && row.stalls === 0 && row.notBefore === 0 && row.reported === 0) return;
214
+ dirty(Native, dueAt, true);
215
+ };
208
216
  /** A strictly increasing source cursor; old or repeated notices cannot renew a budget. */
209
217
  const progress = (id, dueAt, cursor) => {
210
218
  Schema.decodeSync(Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(0n)))(cursor);
@@ -287,6 +295,7 @@ const make = (storage) => {
287
295
  transaction,
288
296
  read,
289
297
  dirty,
298
+ progressNative,
290
299
  progress,
291
300
  register,
292
301
  claim,
@@ -384,8 +393,8 @@ var DurableAlarmService = class DurableAlarmService extends Context.Service()("@
384
393
  ensureScheduledBy: (at) => wrap("advance maintenance checkpoint", alarms.scheduleAlarmEarlier(maintenanceAlarmInput(at))),
385
394
  scheduleNow: Ref.get(inlineWork).pipe(Effect.flatMap((active) => active > 0 ? Effect.void : wrap("hint maintenance checkpoint", alarms.transaction((tx) => Effect.gen(function* () {
386
395
  const now = yield* Clock.currentTimeMillis;
387
- const next$4 = next(make(ctx.storage).read());
388
- if (Number.isFinite(next$4)) yield* tx.scheduleAlarmEarlier(maintenanceAlarmInput(Math.max(now, next$4)));
396
+ const next$6 = next(make(ctx.storage).read());
397
+ if (Number.isFinite(next$6)) yield* tx.scheduleAlarmEarlier(maintenanceAlarmInput(Math.max(now, next$6)));
389
398
  }))))),
390
399
  withHintsDeferred: (body) => Effect.acquireUseRelease(Ref.update(inlineWork, (active) => active + 1), () => body, () => Ref.update(inlineWork, (active) => active - 1)),
391
400
  cancel: wrap("cancel maintenance checkpoint", alarms.cancelAlarm(maintenanceAlarmRef))
@@ -420,12 +429,16 @@ const makeAlarmOperation = Effect.gen(function* () {
420
429
  if (ambient._tag === "Some" && !ownsTransaction(ctx.storage, ambient.value[1])) return Effect.fail(SqlError.make({ reason: UnknownError.make({
421
430
  operation: "begin maintenance source transaction",
422
431
  message: "Use ThreadMutationGate.withTransaction at the outermost source transaction",
423
- cause: /* @__PURE__ */ new Error("Unwrapped SQL transaction cannot own buffered maintenance intent")
432
+ cause: "Unwrapped SQL transaction cannot own buffered maintenance intent"
424
433
  }) }));
425
434
  return Effect.uninterruptibleMask((restore) => Effect.suspend(() => {
426
435
  let frame;
427
- return transaction(Effect.flatMap(Effect.serviceOption(sql.transactionService), (transaction) => Effect.sync(() => {
428
- if (transaction._tag === "None") throw new Error("SQL transaction unavailable");
436
+ return transaction(Effect.flatMap(Effect.serviceOption(sql.transactionService), (transaction) => Effect.gen(function* () {
437
+ if (transaction._tag === "None") return yield* SqlError.make({ reason: UnknownError.make({
438
+ operation: "begin maintenance source transaction",
439
+ message: "SQL transaction unavailable",
440
+ cause: "SQL transaction unavailable"
441
+ }) });
429
442
  frame = queue.begin(transaction.value[1]);
430
443
  })).pipe(Effect.andThen(restore(body)), Effect.tap(() => Effect.try({
431
444
  try: () => {
@@ -442,8 +455,12 @@ const makeAlarmOperation = Effect.gen(function* () {
442
455
  }));
443
456
  }).pipe(Effect.provideService(SqlClient, sql));
444
457
  const run = (operation, execute) => {
445
- const commit = Effect.flatMap(CurrentCheckpoint, (tx) => {
446
- if (tx === void 0) return Effect.die(/* @__PURE__ */ new Error("Maintenance checkpoint transaction unavailable"));
458
+ const commit = Effect.withFiber((fiber) => {
459
+ const tx = fiber.getRef(CurrentCheckpoint);
460
+ if (tx === void 0) return Effect.die(DurableAlarmError.make({
461
+ operation: "checkpoint",
462
+ message: "Maintenance checkpoint transaction unavailable"
463
+ }));
447
464
  return tx.requireActive.pipe(Effect.andThen(Effect.suspend(() => {
448
465
  let checkpoint = Effect.void;
449
466
  const intent = {
@@ -463,9 +480,9 @@ const makeAlarmOperation = Effect.gen(function* () {
463
480
  }).pipe(Effect.tap(() => checkpoint));
464
481
  })));
465
482
  });
466
- return Effect.gen(function* () {
467
- if ((yield* CurrentCheckpoint) !== void 0) return yield* commit;
468
- return yield* (yield* Effect.serviceOption(sql.transactionService))._tag === "Some" && !buffering(ctx.storage) ? transaction(commit).pipe(Effect.ensuring(Effect.sync(() => invalidate(ctx.storage)))) : withTransaction(commit);
483
+ return Effect.withFiber((fiber) => {
484
+ if (fiber.getRef(CurrentCheckpoint) !== void 0) return commit;
485
+ return Context.getOption(fiber.context, sql.transactionService)._tag === "Some" && !buffering(ctx.storage) ? transaction(commit).pipe(Effect.ensuring(Effect.sync(() => invalidate(ctx.storage)))) : withTransaction(commit);
469
486
  }).pipe(Effect.uninterruptible, Effect.mapError(alarmFailure(operation)));
470
487
  };
471
488
  return {
@@ -607,6 +624,14 @@ const recoveryEvent = (fault, submissionId, transition, occurredAt) => ({
607
624
  firstFailedAt: fault.firstFailedAt,
608
625
  failure: fault.failure
609
626
  });
627
+ /** Observe already-owned waves without creating one waiter fiber for each wave. */
628
+ const awaitAnyCompletion = Effect.fnUntraced(function* (fibers, revision) {
629
+ const mutations = yield* ThreadMutationGate;
630
+ return yield* Effect.acquireUseRelease(Effect.sync(() => []), (observers) => Effect.callback((resume) => {
631
+ observers.push(mutations.observeChange(revision, () => resume(Effect.succeed("scan"))));
632
+ for (const fiber of fibers) observers.push(fiber.addObserver(() => resume(Effect.succeed("completed"))));
633
+ }), (observers) => Effect.sync(() => observers.forEach((remove) => remove())));
634
+ });
610
635
  /** Post-native delivery shares a finite connection budget across independent host lanes. */
611
636
  const afterNativeDispatchConcurrency = 2;
612
637
  const NativeWorkCursor = Schema.Struct({
@@ -684,18 +709,30 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
684
709
  const { ctx } = yield* DurableObjectContext;
685
710
  const sql = yield* SqlClient;
686
711
  const dueQueue = make(ctx.storage);
712
+ const placement = yield* Effect.serviceOption(ThreadObjectPlacement);
713
+ const singleThread = Option.isSome(placement) && placement.value.threadId !== void 0;
687
714
  const config = yield* CloudflareDurableRuntimeConfig;
688
715
  const failpoint = yield* ThreadMaintenanceFailpoint;
689
716
  const activeMutations = yield* Ref.make(0);
690
717
  const activeLanes = /* @__PURE__ */ new Map();
691
718
  const generationGate = yield* Semaphore.make(1);
692
719
  let revision = 0;
693
- let changed = yield* Deferred.make();
694
- const notify = Effect.gen(function* () {
695
- const previous = changed;
696
- changed = yield* Deferred.make();
720
+ const listeners = /* @__PURE__ */ new Set();
721
+ const observeChange = (observed, listener) => {
722
+ if (observed !== revision) {
723
+ listener();
724
+ return () => {};
725
+ }
726
+ listeners.add(listener);
727
+ return () => {
728
+ listeners.delete(listener);
729
+ };
730
+ };
731
+ const notify = Effect.sync(() => {
697
732
  revision++;
698
- yield* Deferred.succeed(previous, void 0);
733
+ const pending = [...listeners];
734
+ listeners.clear();
735
+ for (const listener of pending) listener();
699
736
  });
700
737
  const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
701
738
  const runTransaction = yield* makeStorageOperation;
@@ -721,8 +758,8 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
721
758
  dirty: state.dirty + 1n
722
759
  })));
723
760
  }
724
- const next$6 = next(dueQueue.read());
725
- if (Number.isFinite(next$6)) checkpoint.earlier(Math.max(next$6, now + minimumAlarmDelay));
761
+ const next$2 = next(dueQueue.read());
762
+ if (Number.isFinite(next$2)) checkpoint.earlier(Math.max(next$2, now + minimumAlarmDelay));
726
763
  };
727
764
  yield* runAlarmTransaction("schedule maintenance lane", enroll);
728
765
  yield* notify;
@@ -779,7 +816,8 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
779
816
  const now = yield* Clock.currentTimeMillis;
780
817
  const nativeSource = yield* CurrentNativeSource;
781
818
  yield* runAlarmTransaction("record maintenance producer progress", async (transaction, checkpoint) => {
782
- for (const id of affected) dueQueue.dirty(id, now, true);
819
+ for (const id of affected) if (singleThread && nativeSource && id === "effect-agent:native") dueQueue.progressNative(now);
820
+ else dueQueue.dirty(id, now, true);
783
821
  if (affected.has("effect-agent:native")) {
784
822
  const { state, initialized } = await readMaintenanceState(transaction);
785
823
  if (!initialized || !nativeSource || state.retry !== void 0) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(ThreadMaintenanceState.make({
@@ -789,7 +827,7 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
789
827
  }
790
828
  checkpoint.earlier(now + minimumAlarmDelay);
791
829
  });
792
- yield* notify;
830
+ if (!(singleThread && nativeSource && affected.size === 1 && affected.has("effect-agent:native"))) yield* notify;
793
831
  });
794
832
  return ThreadMutationGate.of({
795
833
  withTransaction,
@@ -798,7 +836,11 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
798
836
  recordProgress,
799
837
  activeLanes: Effect.sync(() => new Set(activeLanes.keys())),
800
838
  revision: Effect.sync(() => revision),
801
- awaitChange: (observed) => Effect.suspend(() => observed === revision ? Deferred.await(changed) : Effect.void),
839
+ observeChange,
840
+ awaitChange: (observed) => Effect.callback((resume) => {
841
+ const remove = observeChange(observed, () => resume(Effect.void));
842
+ return Effect.sync(remove);
843
+ }),
802
844
  withSnapshot: (body) => generationGate.withPermit(Effect.flatMap(Ref.get(activeMutations), body))
803
845
  });
804
846
  }));
@@ -829,6 +871,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
829
871
  const runtime = yield* DurableAgentRuntime;
830
872
  const recoveryConfig = yield* DurableRuntimeConfig;
831
873
  const ledger = yield* SubmissionLedger;
874
+ const listNonterminal = ledger.listNonterminal ?? Stream.runCollect(ledger.scanNonterminal);
832
875
  const wakes = yield* WakeScheduler;
833
876
  const hints = yield* DurableAlarmService;
834
877
  const alarms = yield* ThreadMaintenanceAlarm;
@@ -845,6 +888,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
845
888
  const messages = yield* ThreadMessageDelivery;
846
889
  const host = yield* ThreadHostMaintenance;
847
890
  const framework = yield* ThreadNativeMaintenance;
891
+ const placement = yield* Effect.serviceOption(ThreadObjectPlacement);
892
+ const nativeThread = Option.isSome(placement) ? placement.value.threadId : void 0;
848
893
  const nativeDispatchConcurrency = () => framework.singleThreadRuntime === runtime ? 1 : 2;
849
894
  for (const lane of host.lanes) yield* Schema.decodeEffect(HostLaneId)(lane.id).pipe(Effect.mapError(alarmFailure("maintenance lane ID")));
850
895
  const recoveryEvents = Option.getOrElse(yield* Effect.serviceOption(ThreadRecoveryEvents), () => ThreadRecoveryEvents.of({ publish: () => Effect.void }));
@@ -894,8 +939,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
894
939
  const now = yield* Clock.currentTimeMillis;
895
940
  const claimed = yield* runAlarmTransaction("charge maintenance attempt", async (_transaction, checkpoint) => {
896
941
  const claimed = dueQueue.claim(selected, now);
897
- const next$1 = next(dueQueue.read());
898
- if (Number.isFinite(next$1)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$1));
942
+ const next$3 = next(dueQueue.read());
943
+ if (Number.isFinite(next$3)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$3));
899
944
  else checkpoint.cancel();
900
945
  return claimed;
901
946
  });
@@ -956,10 +1001,10 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
956
1001
  };
957
1002
  }));
958
1003
  for (let sequence = range.next; sequence <= range.through; sequence++) {
959
- const event = yield* runTransaction("read recovery event", async () => {
960
- const event = decodeRecoveryEvent(await ctx.storage.get(recoveryEventKey(sequence)));
961
- if (event.sequence !== sequence) throw new Error("Recovery event does not match its sequence key");
962
- return event;
1004
+ const event = yield* runTransaction("read recovery event", async () => decodeRecoveryEvent(await ctx.storage.get(recoveryEventKey(sequence))));
1005
+ if (event.sequence !== sequence) return yield* DurableAlarmError.make({
1006
+ operation: "read recovery event",
1007
+ message: "Recovery event does not match its sequence key"
963
1008
  });
964
1009
  yield* recoveryEvents.publish(event);
965
1010
  const acknowledgedAt = yield* Clock.currentTimeMillis;
@@ -986,9 +1031,9 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
986
1031
  for (const report of result.reports) threads.set(report.threadId, void 0);
987
1032
  for (const report of result.workReports ?? []) threads.set(report.threadId, void 0);
988
1033
  for (const blocked of result.blocked) threads.set(blocked.threadId, blocked.failure);
989
- if (threads.size === 0) return /* @__PURE__ */ new Map();
1034
+ if (threads.size === 0 || [...threads].every(([threadId, failure]) => failure === void 0 && recovery.loaded.has(threadId) && !recovery.faults.has(threadId))) return /* @__PURE__ */ new Map();
990
1035
  const now = yield* Clock.currentTimeMillis;
991
- const submissions = current ?? (result.blocked.length === 0 ? [] : yield* Stream.runCollect(ledger.scanNonterminal));
1036
+ const submissions = current ?? (result.blocked.length === 0 ? [] : yield* listNonterminal);
992
1037
  yield* failpoint.hit("maintenance:recovery-status:before");
993
1038
  const retained = yield* runTransaction("record Thread recovery faults", () => dueQueue.transaction(async (transaction) => {
994
1039
  const events = [];
@@ -1046,20 +1091,23 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1046
1091
  return retained.faults;
1047
1092
  });
1048
1093
  const recoverThread = Effect.fnUntraced(function* (threadId, recovery) {
1049
- let cursor = yield* runTransaction("read work recovery cursor", async () => {
1050
- const retained = await ctx.storage.get(workCursorKey(threadId));
1051
- if (retained === void 0) return void 0;
1052
- const decoded = Schema.decodeUnknownSync(NativeWorkCursor)(retained);
1053
- if (decoded.threadId !== threadId) throw new Error("Work cursor belongs to another Thread");
1054
- return decoded.cursor;
1094
+ const retained = yield* runTransaction("read work recovery cursor", async () => {
1095
+ const value = await ctx.storage.get(workCursorKey(threadId));
1096
+ return value === void 0 ? void 0 : Schema.decodeUnknownSync(NativeWorkCursor)(value);
1097
+ });
1098
+ if (retained !== void 0 && retained.threadId !== threadId) return yield* DurableAlarmError.make({
1099
+ operation: "read work recovery cursor",
1100
+ message: "Work cursor belongs to another Thread"
1055
1101
  });
1102
+ let cursor = retained?.cursor;
1056
1103
  do {
1057
1104
  const result = yield* runtime.runRecovery({
1058
1105
  threadId,
1106
+ deferUnclaimedAdmissions: true,
1059
1107
  ...cursor === void 0 ? {} : { cursor }
1060
1108
  }).pipe(Effect.provideService(CurrentNativeSource, true));
1061
1109
  const faults = yield* recordRecoveryFaults(result, recovery, void 0, threadId);
1062
- yield* runTransaction("checkpoint work recovery cursor", async () => {
1110
+ if (cursor !== void 0 || result.cursor !== void 0) yield* runTransaction("checkpoint work recovery cursor", async () => {
1063
1111
  if (result.cursor === void 0) await ctx.storage.delete(workCursorKey(threadId));
1064
1112
  else await ctx.storage.put(workCursorKey(threadId), Schema.encodeSync(NativeWorkCursor)({
1065
1113
  threadId,
@@ -1102,8 +1150,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1102
1150
  if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
1103
1151
  const native = state.dirty > state.processed ? state.retry?.generation === state.dirty ? state.retry.notBefore : rows.find((row) => row.id === "effect-agent:native")?.dueAt ?? now + minimumAlarmDelay : null;
1104
1152
  dueQueue.checkpointNative(native);
1105
- const next$3 = next(dueQueue.read());
1106
- if (Number.isFinite(next$3)) checkpoint.earlier(Math.max(now + minimumAlarmDelay, next$3));
1153
+ const next$1 = next(dueQueue.read());
1154
+ if (Number.isFinite(next$1)) checkpoint.earlier(Math.max(now + minimumAlarmDelay, next$1));
1107
1155
  });
1108
1156
  yield* failpoint.hit("maintenance:ensure:after");
1109
1157
  });
@@ -1177,8 +1225,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1177
1225
  const charged = observed.charged.get(row.id) === row.revision ? row : dueQueue.claim(row, now);
1178
1226
  if (charged !== void 0) dueQueue.complete(charged, now + backoffDelay(charged.stalls, jitter));
1179
1227
  }
1180
- const next$5 = next(dueQueue.read());
1181
- if (Number.isFinite(next$5)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$5));
1228
+ const next$4 = next(dueQueue.read());
1229
+ if (Number.isFinite(next$4)) checkpoint.at(Math.max(now + minimumAlarmDelay, next$4));
1182
1230
  else checkpoint.cancel();
1183
1231
  });
1184
1232
  }));
@@ -1269,11 +1317,14 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1269
1317
  const { state } = await readMaintenanceState(ctx.storage);
1270
1318
  return {
1271
1319
  scannedGeneration: state.dirty,
1272
- afterWorkThread: state.lastRecoveredThreadId
1320
+ afterWorkThread: framework.singleThreadRuntime === runtime && state.lastRecoveredThreadId === nativeThread ? void 0 : state.lastRecoveredThreadId
1273
1321
  };
1274
1322
  });
1275
- const current = yield* Stream.runCollect(ledger.scanNonterminal);
1276
- let workOwners = yield* runtime.discoverWorkThreads({
1323
+ const current = yield* listNonterminal;
1324
+ let workOwners = framework.singleThreadRuntime === runtime && nativeThread !== void 0 && current.some((submission) => submission.threadId === nativeThread) ? {
1325
+ threadIds: [nativeThread],
1326
+ afterThreadId: void 0
1327
+ } : yield* runtime.discoverWorkThreads({
1277
1328
  limit: 32,
1278
1329
  ...afterWorkThread === void 0 ? {} : { afterThreadId: afterWorkThread }
1279
1330
  }).pipe(Effect.provideService(CurrentNativeSource, true));
@@ -1360,7 +1411,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1360
1411
  const pivot = Math.max(0, runnable.findIndex((threadId) => state.lastServedThreadId === void 0 || threadId > state.lastServedThreadId));
1361
1412
  const selected = dispatch ? [...runnable.slice(pivot), ...runnable.slice(0, pivot)].slice(0, nativeDispatchConcurrency() - reserved.size) : [];
1362
1413
  const next = selected.at(-1);
1363
- if (next !== void 0) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(ThreadMaintenanceState.make({
1414
+ if (next !== void 0 && next !== state.lastServedThreadId) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(ThreadMaintenanceState.make({
1364
1415
  ...state,
1365
1416
  lastServedThreadId: next
1366
1417
  })));
@@ -1397,7 +1448,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1397
1448
  nextAttemptAt: void 0,
1398
1449
  dispatched: selection.selected.length > 0
1399
1450
  };
1400
- const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
1451
+ const remaining = yield* listNonterminal;
1401
1452
  const waitingHeads = /* @__PURE__ */ new Map();
1402
1453
  const autonomous = recovery.pending.size > 0 || recovery.discoveryPending || recoveryFaults.size > 0 || recovery.retryAtMillis !== void 0 || remaining.some((snapshot) => {
1403
1454
  if (snapshot.state === "unknown" && waiting(snapshot)) return false;
@@ -1472,7 +1523,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1472
1523
  });
1473
1524
  });
1474
1525
  const dispatch = Effect.fnUntraced(function* (yieldAfter, dispatchUntil, observed) {
1475
- const notified = (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
1526
+ const leanSelection = framework.singleThreadRuntime === runtime && publication === emptyPublication && host.lanes.length === 0 && framework.lanes.length === 0;
1527
+ const notified = leanSelection ? Effect.never : (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
1476
1528
  const started = yield* beginNative(observed);
1477
1529
  yield* mutations.withSnapshot(() => Effect.gen(function* () {
1478
1530
  const queued = yield* queueSnapshot;
@@ -1509,8 +1561,35 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1509
1561
  repaired: false,
1510
1562
  discoveryPending: false
1511
1563
  };
1512
- const recoveryFiber = yield* fork(Effect.gen(function* () {
1513
- for (const threadId of yield* Deferred.await(recovery.queue)) {
1564
+ let recoveryFiber;
1565
+ let backfill;
1566
+ const dispatch = {
1567
+ scope: auxiliaryScope,
1568
+ active: /* @__PURE__ */ new Map(),
1569
+ deferred: /* @__PURE__ */ new Set(),
1570
+ dispatched: 0,
1571
+ settled: 0,
1572
+ progressed: false,
1573
+ needsCheckpoint: false
1574
+ };
1575
+ let native;
1576
+ let result;
1577
+ let backlog;
1578
+ if (leanSelection) {
1579
+ result = yield* advance(started, yieldAfter, observed, recovery, dispatch);
1580
+ recovery.started = true;
1581
+ yield* Deferred.succeed(recovery.queue, []);
1582
+ backlog = yield* Deferred.await(recovery.queue);
1583
+ } else {
1584
+ native = yield* fork(advance(started, yieldAfter, observed, recovery, dispatch));
1585
+ result = {
1586
+ phase: "caught-up",
1587
+ nonterminal: started.nonterminal,
1588
+ nextAttemptAt: void 0
1589
+ };
1590
+ }
1591
+ if (backlog === void 0 || backlog.length > 0) recoveryFiber = yield* fork(Effect.gen(function* () {
1592
+ for (const threadId of backlog ?? (yield* Deferred.await(recovery.queue))) {
1514
1593
  yield* failpoint.hit("maintenance:select:before");
1515
1594
  yield* runTransaction("select old recovery lane", () => dueQueue.transaction(async (transaction) => {
1516
1595
  const { state } = await readMaintenanceState(transaction);
@@ -1525,31 +1604,11 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1525
1604
  if (!recovery.faults.has(threadId)) yield* wakes.notify(threadId);
1526
1605
  }
1527
1606
  }));
1528
- const backfill = yield* fork(Effect.gen(function* () {
1529
- if ((yield* mutations.activeLanes).has("effect-agent:projection")) return;
1530
- const row = (yield* queueSnapshot).find((row) => row.id === Projection);
1531
- if (row?.dueAt === null || row === void 0 || row.dueAt > (yield* Clock.currentTimeMillis)) return;
1532
- return yield* runQueued(row, projection.drain.pipe(Effect.andThen(projection.pendingDeadline)), observed).pipe(Effect.timeoutOption(config.projectionDispatchTimeoutMillis));
1533
- }));
1534
- let backfillObserved = false;
1535
- let recoveryObserved = false;
1536
- const dispatch = {
1537
- scope: auxiliaryScope,
1538
- active: /* @__PURE__ */ new Map(),
1539
- deferred: /* @__PURE__ */ new Set(),
1540
- dispatched: 0,
1541
- settled: 0,
1542
- progressed: false,
1543
- needsCheckpoint: false
1544
- };
1545
- let native = yield* fork(advance(started, yieldAfter, observed, recovery, dispatch));
1546
- let result = {
1547
- phase: "caught-up",
1548
- nonterminal: started.nonterminal,
1549
- nextAttemptAt: void 0
1550
- };
1607
+ const projectionRow = (yield* queueSnapshot).find((row) => row.id === Projection);
1608
+ if (projectionRow !== void 0 && projectionRow.dueAt !== null && projectionRow.dueAt <= (yield* Clock.currentTimeMillis) && !(yield* mutations.activeLanes).has("effect-agent:projection")) backfill = yield* fork(runQueued(projectionRow, projection.drain.pipe(Effect.andThen(projection.pendingDeadline)), observed).pipe(Effect.timeoutOption(config.projectionDispatchTimeoutMillis), Effect.asVoid));
1551
1609
  let phase = result.phase;
1552
- let nativeCheck = false;
1610
+ let nativeCheck = result.dispatched === true && dispatch.active.size < nativeDispatchConcurrency();
1611
+ observed.nativeOnly = dispatch.active.size > 0;
1553
1612
  const until = DateTime.toEpochMillis(yieldAfter);
1554
1613
  const dispatchEnd = DateTime.toEpochMillis(dispatchUntil);
1555
1614
  while (true) {
@@ -1577,18 +1636,16 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1577
1636
  if (exit.value === 0) dispatch.deferred.add(threadId);
1578
1637
  nativeCheck = true;
1579
1638
  }
1580
- if (!recoveryObserved && recoveryFiber.pollUnsafe() !== void 0) {
1639
+ if (recoveryFiber?.pollUnsafe() !== void 0) {
1581
1640
  yield* Fiber.join(recoveryFiber);
1582
- recoveryObserved = true;
1641
+ recoveryFiber = void 0;
1583
1642
  dispatch.scanGeneration = void 0;
1584
1643
  nativeCheck ||= recovery.needsCheckpoint;
1585
1644
  }
1586
- if (!backfillObserved) {
1587
- const exit = backfill.pollUnsafe();
1588
- if (exit !== void 0) {
1589
- backfillObserved = true;
1590
- if (Exit.isFailure(exit)) failure ??= exit.cause;
1591
- }
1645
+ const backfillExit = backfill?.pollUnsafe();
1646
+ if (backfillExit !== void 0) {
1647
+ backfill = void 0;
1648
+ if (Exit.isFailure(backfillExit)) failure ??= backfillExit.cause;
1592
1649
  }
1593
1650
  for (const lane of lanes) {
1594
1651
  const exit = lane.fiber?.pollUnsafe();
@@ -1646,11 +1703,11 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1646
1703
  return yield* wave.run;
1647
1704
  }), observed).pipe(Effect.asVoid));
1648
1705
  }
1649
- if (native === void 0 && dispatch.active.size === 0 && recoveryObserved && !recovery.needsCheckpoint && backfillObserved && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
1706
+ if (native === void 0 && dispatch.active.size === 0 && recoveryFiber === void 0 && !recovery.needsCheckpoint && backfill === void 0 && delivery === void 0 && lanes.every((lane) => lane.fiber === void 0)) break;
1650
1707
  if (native === void 0 && nativeCheck && dispatch.active.size < nativeDispatchConcurrency()) {
1651
1708
  nativeCheck = false;
1652
1709
  const nativeCheckpoint = dispatch.needsCheckpoint && dispatch.active.size === 0;
1653
- const checkpoint = recoveryObserved && recovery.needsCheckpoint || nativeCheckpoint;
1710
+ const checkpoint = recoveryFiber === void 0 && recovery.needsCheckpoint || nativeCheckpoint;
1654
1711
  if (checkpoint) recovery.needsCheckpoint = false;
1655
1712
  const reserved = new Set(dispatch.active.keys());
1656
1713
  native = yield* fork(Effect.gen(function* () {
@@ -1660,18 +1717,19 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1660
1717
  }
1661
1718
  const next = Math.min(native === void 0 ? result.nextAttemptAt ?? Infinity : Infinity, ...queued.flatMap((row) => row.id !== "effect-agent:native" && row.dueAt !== null && row.dueAt > now ? [row.dueAt] : []), until);
1662
1719
  const completing = [
1663
- ...native === void 0 ? [] : [native],
1720
+ native,
1664
1721
  ...dispatch.active.values(),
1665
- ...recoveryObserved ? [] : [recoveryFiber],
1666
- ...backfillObserved ? [] : [backfill],
1667
- ...delivery === void 0 ? [] : [delivery],
1668
- ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
1669
- ];
1670
- if ((yield* Effect.raceAllFirst([...completing.map((fiber) => Fiber.await(fiber).pipe(Effect.as("completed"))), Effect.raceAllFirst([
1671
- notified,
1672
- mutations.awaitChange(queueRevision),
1673
- Effect.sleep(Math.max(0, next - now))
1674
- ]).pipe(Effect.as("scan"))])) === "scan") nativeCheck = true;
1722
+ recoveryFiber,
1723
+ backfill,
1724
+ delivery,
1725
+ ...lanes.map((lane) => lane.fiber)
1726
+ ].filter((fiber) => fiber !== void 0);
1727
+ const changed = awaitAnyCompletion(completing, queueRevision);
1728
+ if ((yield* leanSelection && native === void 0 && dispatch.active.size === 1 && completing.length === 1 && !queued.some((row) => row.id !== "effect-agent:native" && row.dueAt !== null && row.dueAt > now && row.dueAt < until) ? changed : Effect.raceAllFirst([
1729
+ changed,
1730
+ ...leanSelection ? [] : [notified.pipe(Effect.as("scan"))],
1731
+ Effect.sleep(Math.max(0, next - now)).pipe(Effect.as("scan"))
1732
+ ])) === "scan") nativeCheck = true;
1675
1733
  }
1676
1734
  if (native !== void 0) {
1677
1735
  result = yield* Fiber.join(native);
@@ -1690,9 +1748,10 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1690
1748
  for (const fiber of [
1691
1749
  recoveryFiber,
1692
1750
  backfill,
1693
- ...delivery === void 0 ? [] : [delivery],
1694
- ...lanes.flatMap((lane) => lane.fiber === void 0 ? [] : [lane.fiber])
1751
+ delivery,
1752
+ ...lanes.map((lane) => lane.fiber)
1695
1753
  ]) {
1754
+ if (fiber === void 0) continue;
1696
1755
  const exit = yield* Fiber.await(fiber);
1697
1756
  if (Exit.isFailure(exit)) failure ??= exit.cause;
1698
1757
  }
@@ -1710,7 +1769,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1710
1769
  };
1711
1770
  });
1712
1771
  const pass = Effect.fnUntraced(function* (yieldAfter, dispatchUntil, observed) {
1713
- const concurrent = yield* Effect.exit(Effect.scoped(dispatch(yieldAfter, dispatchUntil, observed)));
1772
+ const concurrent = yield* Effect.exit(Effect.scoped(dispatch(yieldAfter, dispatchUntil, observed)).pipe(Effect.provideService(ThreadMutationGate, mutations)));
1714
1773
  const afterNative = yield* Effect.gen(function* () {
1715
1774
  const lanes = [
1716
1775
  recoveryEventLane,
@@ -1781,9 +1840,9 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1781
1840
  const { state } = await readMaintenanceState(transaction);
1782
1841
  const native = active > 0 || state.dirty > state.processed ? state.retry?.generation === state.dirty && active === 0 ? Math.max(now + minimumAlarmDelay, state.retry.notBefore) : state.dirty === observed.generation && active === 0 ? result.nextAttemptAt ?? now + minimumAlarmDelay : now + minimumAlarmDelay : Infinity;
1783
1842
  dueQueue.checkpointNative(Number.isFinite(native) ? native : null);
1784
- const next$2 = next(dueQueue.read());
1785
- if (Number.isFinite(next$2)) {
1786
- checkpoint.at(Math.max(now + minimumAlarmDelay, next$2));
1843
+ const next$5 = next(dueQueue.read());
1844
+ if (Number.isFinite(next$5)) {
1845
+ checkpoint.at(Math.max(now + minimumAlarmDelay, next$5));
1787
1846
  return "rearmed";
1788
1847
  }
1789
1848
  checkpoint.cancel();
@@ -1837,4 +1896,4 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
1837
1896
  //#endregion
1838
1897
  export { Lifecycle as _, ThreadHostMaintenance as a, Projection as b, ThreadMaintenanceFailpoint as c, ThreadNativeMaintenance as d, ThreadPublication as f, HostLaneId as g, publishCommitted as h, MaintenancePassReport as i, ThreadMessageDelivery as l, ThreadRecoveryFaultEvent as m, DurableAlarmError as n, ThreadMaintenance as o, ThreadRecoveryEvents as p, DurableAlarmService as r, ThreadMaintenanceAlarm as s, Alarm_exports as t, ThreadMutationGate as u, Messages as v, Publication as x, Native as y };
1839
1898
 
1840
- //# sourceMappingURL=Alarm-BMEAZo3p.mjs.map
1899
+ //# sourceMappingURL=Alarm-B44efvB1.mjs.map