@effect-agent/platform-cloudflare 0.1.0-beta.112 → 0.1.0-beta.114

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.d.mts CHANGED
@@ -10,7 +10,7 @@ import { ThreadProjectionError, ThreadProjectionMaintenance } from "effect-agent
10
10
  import { WakeScheduler } from "effect-agent/wake-scheduler";
11
11
  import { SqlClient } from "effect/unstable/sql/SqlClient";
12
12
  declare namespace Alarm_d_exports {
13
- export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMaintenanceFailpointLocation, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadPublicationService, ThreadRecoveryFault, publishCommitted };
13
+ export { DurableAlarmError, DurableAlarmService, MaintenancePassFailure, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceActivity, ThreadMaintenanceFailpoint, ThreadMaintenanceFailpointHandler, ThreadMaintenanceFailpointLocation, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadPublicationService, ThreadRecoveryFault, publishCommitted };
14
14
  }
15
15
  declare const DurableAlarmError_base: Schema.Class<DurableAlarmError, Schema.TaggedStruct<"DurableAlarmError", {
16
16
  readonly operation: Schema.String;
@@ -114,25 +114,49 @@ declare const ThreadPublication_base: Context.ServiceClass<ThreadPublication, "@
114
114
  export declare class ThreadPublication extends ThreadPublication_base {
115
115
  static readonly layer: Layer.Layer<ThreadPublication, never, never>;
116
116
  }
117
+ declare const ThreadMaintenanceActivity_base: Context.ServiceClass<ThreadMaintenanceActivity, "@effect-agent/platform-cloudflare/ThreadMaintenanceActivity", {
118
+ /**
119
+ * Account for one finite selection/dispatch, through joined completion or interruption.
120
+ * Register before reading/claiming work. After dispatch closes the body is not started.
121
+ * Host waves retain their declared allowance from this call; message Claims keep the driver's
122
+ * own deadline. Waiting for notifications belongs outside this bracket.
123
+ */
124
+ readonly run: <R>(wave: Effect.Effect<void, DurableAlarmError, R>) => Effect.Effect<void, DurableAlarmError, R>;
125
+ /** Acknowledge all initial subscriptions/scans. Composite hosts use `all` below. */
126
+ readonly ready: Effect.Effect<void>;
127
+ /**
128
+ * Acquire an independent scoped subscription before the initial scan and `ready`.
129
+ * The returned wait observes coalesced native scan hints, including those published before
130
+ * its first execution. Each hint requests a current local due-work check.
131
+ */
132
+ readonly subscribeChanges: Effect.Effect<Effect.Effect<void>, never, Scope.Scope>;
133
+ }>;
134
+ /** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
135
+ export declare class ThreadMaintenanceActivity extends ThreadMaintenanceActivity_base {
136
+ /** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
137
+ static readonly all: <A, E, R>(lanes: readonly Effect.Effect<A, E, R>[]) => Effect.Effect<readonly A[], E, R | ThreadMaintenanceActivity>;
138
+ }
117
139
  /**
118
140
  * Host-assembled native message recovery. The driver bounds each actual Claim and persists its
119
141
  * timeout/retry before this pump returns. Do not add a second timer starting at batch selection:
120
142
  * local Claim setup may take time, and expiration still owes the driver's local retry commit.
121
143
  */
122
144
  export declare const ThreadMessageDelivery: Context.Reference<{
123
- readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope>;
145
+ readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope | ThreadMaintenanceActivity>;
124
146
  readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
125
147
  }>;
126
148
  /**
127
149
  * Application obligations sharing this Object's alarm. Admit one initial external wave even on
128
- * a caught-up pass, then respond to wakes until dispatchClosed. This closes only admission
129
- * of NEW external waves; native work can continue while admitted waves finish. Keep local
130
- * admission/hub subscriptions in the event Scope until maintenance tears it down.
150
+ * a caught-up pass, then respond to wakes and ThreadMaintenanceActivity.subscribeChanges until
151
+ * dispatchClosed. Yield the event's ThreadMaintenanceActivity to bracket each finite lane with
152
+ * run and acknowledge initial setup with ready.
153
+ * Passive subscriptions do not count as active work. Closure stops both new waves and native
154
+ * Attempts; local admission/control subscriptions belong to the event Scope until teardown.
131
155
  * No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
132
156
  *
133
157
  * Declare a finite whole-wave allowance (1..300000ms): maximum for parallel lanes, sum for
134
158
  * sequential operations. Admit a wave only if its allowance fits before dispatchUntil. Later
135
- * arrivals cannot renew the retirement window. Maintenance bounds the join after dispatchClosed,
159
+ * arrivals cannot renew an admitted wave's allowance. Maintenance bounds each registered wave,
136
160
  * interrupts and joins event Scope, then reads local deadlines under the mutation gate.
137
161
  *
138
162
  * Setup and pendingDeadline are bounded local operations. Persist claims/envelopes before
@@ -143,7 +167,7 @@ export declare const ThreadMessageDelivery: Context.Reference<{
143
167
  */
144
168
  export declare const ThreadHostMaintenance: Context.Reference<{
145
169
  readonly dispatchTimeoutMillis: number;
146
- readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope>;
170
+ readonly drainUntil: (dispatchClosed: Effect.Effect<void>, dispatchUntil: DateTime.Utc) => Effect.Effect<void, DurableAlarmError, Scope.Scope | ThreadMaintenanceActivity>;
147
171
  readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;
148
172
  }>;
149
173
  /** @internal A committed source operation must not become a failed operation because delivery failed. */
@@ -216,11 +240,12 @@ declare const ThreadMaintenance_base: Context.ServiceClass<ThreadMaintenance, "@
216
240
  * 1. Prearm before any work. A caught-up native step uses the O(1) generation record without
217
241
  * recovery, ledger scans or canonical-history reads.
218
242
  * 2. Reconcile before each head Attempt, then checkpoint only the observed generation. A racing
219
- * producer keeps its newer generation dirty. Native retries retain their durable backoff.
220
- * 3. Keep delivery admission open while old recovery is pending, so fresh native replies can
221
- * publish in this event. Close once, at latest at the original ten-minute yield deadline;
222
- * native wakes can still advance heads while admitted deliveries retire.
223
- * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill joins are
243
+ * producer keeps its generation dirty and immediately eligible. Quiescent native retries
244
+ * retain their durable backoff.
245
+ * 3. Keep native and delivery admission open together while finite waves remain active, so
246
+ * fresh replies and abort controls can progress during unrelated cleanup. Close atomically
247
+ * at quiescence, or at the original ten-minute yield deadline, before retiring listeners.
248
+ * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
224
249
  * bounded independently; incoming native work never restarts or cancels their attempts.
225
250
  * Auxiliary failures are reported after the current native opportunity.
226
251
  * 5. Close every event resource before the final gated deadline snapshot and alarm decision.
package/dist/Alarm.mjs CHANGED
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { DurableObjectContext } from "./CloudflareBindings.mjs";
3
3
  import { AuxiliaryDispatchMillis, CloudflareDurableRuntimeConfig } from "./CloudflareConfig.mjs";
4
4
  import { r as safeCauseMessage } from "./boundary-BguazVkh.mjs";
5
- import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Random, Ref, Result, Schema, Scope, Semaphore, Stream, Struct } from "effect";
5
+ import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, PubSub, Random, Ref, Result, Schema, Scope, Semaphore, Stream, Struct } from "effect";
6
6
  import "effect-agent/agent-registration";
7
7
  import { DurableAgentRuntime, DurableRuntimeConfig, RecoveryBlocked, RecoveryFailure, RecoverySweepResult, isolateRecovery } from "effect-agent/durable-agent-runtime";
8
8
  import { SubmissionId, ThreadId } from "effect-agent/identifiers";
@@ -18,6 +18,7 @@ var Alarm_exports = /* @__PURE__ */ __exportAll({
18
18
  MaintenancePassReport: () => MaintenancePassReport,
19
19
  ThreadHostMaintenance: () => ThreadHostMaintenance,
20
20
  ThreadMaintenance: () => ThreadMaintenance,
21
+ ThreadMaintenanceActivity: () => ThreadMaintenanceActivity,
21
22
  ThreadMaintenanceFailpoint: () => ThreadMaintenanceFailpoint,
22
23
  ThreadMessageDelivery: () => ThreadMessageDelivery,
23
24
  ThreadMutationGate: () => ThreadMutationGate,
@@ -111,6 +112,27 @@ var ThreadPublication = class extends Context.Service()("@effect-agent/platform-
111
112
  pendingDeadline: Effect.succeed(Option.none())
112
113
  });
113
114
  };
115
+ /** Event-local accounting shared by the existing maintenance pumps and native scheduler. */
116
+ var ThreadMaintenanceActivity = class ThreadMaintenanceActivity extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenanceActivity") {
117
+ /** Join child pumps; acknowledge the parent only after every child is ready or has exited. */
118
+ static all = Effect.fnUntraced(function* (lanes) {
119
+ const activity = yield* ThreadMaintenanceActivity;
120
+ let remaining = lanes.length;
121
+ if (remaining === 0) return yield* activity.ready.pipe(Effect.as([]));
122
+ return yield* Effect.forEach(lanes, (lane) => {
123
+ let ready = false;
124
+ const child = {
125
+ ...activity,
126
+ ready: Effect.suspend(() => {
127
+ if (ready) return Effect.void;
128
+ ready = true;
129
+ return --remaining === 0 ? activity.ready : Effect.void;
130
+ }).pipe(Effect.uninterruptible)
131
+ };
132
+ return lane.pipe(Effect.provideService(ThreadMaintenanceActivity, child), Effect.onExit(() => child.ready));
133
+ }, { concurrency: "unbounded" });
134
+ });
135
+ };
114
136
  /**
115
137
  * Host-assembled native message recovery. The driver bounds each actual Claim and persists its
116
138
  * timeout/retry before this pump returns. Do not add a second timer starting at batch selection:
@@ -122,14 +144,16 @@ const ThreadMessageDelivery = Context.Reference("@effect-agent/platform-cloudfla
122
144
  }) });
123
145
  /**
124
146
  * Application obligations sharing this Object's alarm. Admit one initial external wave even on
125
- * a caught-up pass, then respond to wakes until dispatchClosed. This closes only admission
126
- * of NEW external waves; native work can continue while admitted waves finish. Keep local
127
- * admission/hub subscriptions in the event Scope until maintenance tears it down.
147
+ * a caught-up pass, then respond to wakes and ThreadMaintenanceActivity.subscribeChanges until
148
+ * dispatchClosed. Yield the event's ThreadMaintenanceActivity to bracket each finite lane with
149
+ * run and acknowledge initial setup with ready.
150
+ * Passive subscriptions do not count as active work. Closure stops both new waves and native
151
+ * Attempts; local admission/control subscriptions belong to the event Scope until teardown.
128
152
  * No deadline sleeps or automatic retry loops. Return after already-admitted waves finish.
129
153
  *
130
154
  * Declare a finite whole-wave allowance (1..300000ms): maximum for parallel lanes, sum for
131
155
  * sequential operations. Admit a wave only if its allowance fits before dispatchUntil. Later
132
- * arrivals cannot renew the retirement window. Maintenance bounds the join after dispatchClosed,
156
+ * arrivals cannot renew an admitted wave's allowance. Maintenance bounds each registered wave,
133
157
  * interrupts and joins event Scope, then reads local deadlines under the mutation gate.
134
158
  *
135
159
  * Setup and pendingDeadline are bounded local operations. Persist claims/envelopes before
@@ -285,11 +309,12 @@ var ThreadMutationGate = class ThreadMutationGate extends Context.Service()("@ef
285
309
  * 1. Prearm before any work. A caught-up native step uses the O(1) generation record without
286
310
  * recovery, ledger scans or canonical-history reads.
287
311
  * 2. Reconcile before each head Attempt, then checkpoint only the observed generation. A racing
288
- * producer keeps its newer generation dirty. Native retries retain their durable backoff.
289
- * 3. Keep delivery admission open while old recovery is pending, so fresh native replies can
290
- * publish in this event. Close once, at latest at the original ten-minute yield deadline;
291
- * native wakes can still advance heads while admitted deliveries retire.
292
- * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill joins are
312
+ * producer keeps its generation dirty and immediately eligible. Quiescent native retries
313
+ * retain their durable backoff.
314
+ * 3. Keep native and delivery admission open together while finite waves remain active, so
315
+ * fresh replies and abort controls can progress during unrelated cleanup. Close atomically
316
+ * at quiescence, or at the original ten-minute yield deadline, before retiring listeners.
317
+ * 4. Native message delivery retains its driver-owned Claim deadline. Host/backfill waves are
293
318
  * bounded independently; incoming native work never restarts or cancels their attempts.
294
319
  * Auxiliary failures are reported after the current native opportunity.
295
320
  * 5. Close every event resource before the final gated deadline snapshot and alarm decision.
@@ -608,13 +633,15 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
608
633
  yield* failpoint.hit("maintenance:checkpoint:before");
609
634
  const nextAttemptAt = yield* mutations.withSnapshot((active) => runTransaction("checkpoint native maintenance", () => ctx.storage.transaction(async (transaction) => {
610
635
  const { state } = await readMaintenanceState(transaction);
611
- const processed = autonomous || observation.activeAtStart > 0 || started.activeAtStart > 0 || active > 0 ? state.processed : state.processed > observation.generation ? state.processed : observation.generation;
636
+ const mutationOverlap = observation.activeAtStart > 0 || started.activeAtStart > 0 || active > 0;
637
+ const processed = autonomous || mutationOverlap ? state.processed : state.processed > observation.generation ? state.processed : observation.generation;
638
+ const canBackoff = !mutationOverlap && state.dirty === started.generation;
612
639
  const next = ThreadMaintenanceState.make({
613
640
  ...Struct.omit(state, ["retry"]),
614
641
  processed,
615
642
  nonterminal: remaining.length,
616
643
  bindingRetries: (state.bindingRetries ?? []).filter((retry) => remaining.some((row) => row.submissionId === retry.submissionId)),
617
- ...autonomous && !progressed ? { retry: MaintenanceRetry.make({
644
+ ...autonomous && !progressed && canBackoff ? { retry: MaintenanceRetry.make({
618
645
  generation: started.generation,
619
646
  notBefore: now + delay,
620
647
  nativeOnly: true,
@@ -622,8 +649,8 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
622
649
  }) } : {}
623
650
  });
624
651
  await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
625
- if (autonomous) return started.activeAtStart > 0 || active > 0 || state.dirty !== started.generation ? now + minimumAlarmDelay : now + delay;
626
- return started.activeAtStart > 0 || active > 0 || next.dirty > next.processed ? now + minimumAlarmDelay : void 0;
652
+ if (autonomous) return now + (canBackoff ? delay : minimumAlarmDelay);
653
+ return mutationOverlap || next.dirty > next.processed ? now + minimumAlarmDelay : void 0;
627
654
  })));
628
655
  yield* failpoint.hit("maintenance:checkpoint:after");
629
656
  return {
@@ -637,6 +664,47 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
637
664
  const notified = (yield* Stream.toPull(wakes.wakes)).pipe(Effect.asVoid, Effect.catch(() => Effect.never));
638
665
  const started = yield* beginNative(observed);
639
666
  const auxiliaryScope = yield* Effect.acquireRelease(Scope.make("parallel"), (scope, exit) => Scope.close(scope, exit));
667
+ const dispatchClosed = yield* Deferred.make();
668
+ const stopDispatch = Deferred.await(dispatchClosed);
669
+ const checks = yield* PubSub.sliding(1);
670
+ yield* Effect.addFinalizer(() => PubSub.shutdown(checks));
671
+ let dispatchOpen = true;
672
+ let activityChanged = yield* Deferred.make();
673
+ const signalActivity = Effect.suspend(() => Deferred.succeed(activityChanged, void 0));
674
+ const closeDispatch = Effect.sync(() => {
675
+ dispatchOpen = false;
676
+ }).pipe(Effect.andThen(Deferred.succeed(dispatchClosed, void 0)));
677
+ const makeActivity = Effect.fnUntraced(function* (allowance) {
678
+ const initialized = yield* Deferred.make();
679
+ let active = 0;
680
+ let failed = false;
681
+ return {
682
+ activity: {
683
+ ready: Deferred.succeed(initialized, void 0).pipe(Effect.andThen(signalActivity), Effect.asVoid, Effect.uninterruptible),
684
+ subscribeChanges: PubSub.subscribe(checks).pipe(Effect.map(PubSub.take)),
685
+ run: (wave) => Effect.acquireUseRelease(Effect.sync(() => {
686
+ if (!dispatchOpen) return false;
687
+ active++;
688
+ return true;
689
+ }), (admitted) => !admitted ? Effect.void : (allowance === void 0 ? wave : wave.pipe(Effect.timeoutOrElse({
690
+ duration: allowance,
691
+ orElse: () => DurableAlarmError.make({
692
+ operation: "host dispatch allowance",
693
+ message: "The admitted host wave exceeded its allowance; durable work remains pending"
694
+ })
695
+ }))).pipe(Effect.onExit((exit) => Effect.sync(() => {
696
+ if (Exit.isFailure(exit)) failed = true;
697
+ }))), (admitted) => Effect.sync(() => {
698
+ if (admitted) active--;
699
+ }).pipe(Effect.andThen(signalActivity)))
700
+ },
701
+ initialized: Deferred.await(initialized),
702
+ quiet: () => active === 0 && Deferred.isDoneUnsafe(initialized),
703
+ failed: () => failed
704
+ };
705
+ });
706
+ const messageActivity = yield* makeActivity();
707
+ const hostActivity = yield* makeActivity(host.dispatchTimeoutMillis);
640
708
  const recovery = {
641
709
  queue: yield* Deferred.make(),
642
710
  pending: /* @__PURE__ */ new Set(),
@@ -664,18 +732,23 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
664
732
  yield* wakes.notify(threadId);
665
733
  }
666
734
  }), auxiliaryScope);
667
- const dispatchClosed = yield* Deferred.make();
668
- const stopDispatch = Deferred.await(dispatchClosed);
669
- const deliveryFiber = yield* Effect.forkIn(Scope.provide(auxiliaryScope)(messages.drainUntil(stopDispatch, dispatchUntil)), auxiliaryScope);
735
+ const deliveryFiber = yield* Effect.forkIn(Scope.provide(auxiliaryScope)(messages.drainUntil(stopDispatch, dispatchUntil).pipe(Effect.provideService(ThreadMaintenanceActivity, messageActivity.activity), Effect.onExit(() => messageActivity.activity.ready))), auxiliaryScope);
670
736
  const hostFiber = yield* Effect.forkIn(Scope.provide(auxiliaryScope)(Effect.gen(function* () {
671
737
  yield* Schema.decodeEffect(AuxiliaryDispatchMillis)(host.dispatchTimeoutMillis).pipe(Effect.mapError((cause) => DurableAlarmError.make({
672
738
  operation: "host dispatch allowance",
673
739
  message: "Declare an integer whole-wave allowance between 1 and 300000 milliseconds",
674
740
  cause
675
741
  })));
676
- yield* host.drainUntil(stopDispatch, dispatchUntil);
677
- })), auxiliaryScope);
678
- const backfill = yield* Effect.forkIn(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis)), auxiliaryScope);
742
+ const initialization = hostActivity.initialized.pipe(Effect.timeoutOrElse({
743
+ duration: host.dispatchTimeoutMillis,
744
+ orElse: () => DurableAlarmError.make({
745
+ operation: "host initialization",
746
+ message: "The host did not account for initial maintenance before its allowance; durable work remains pending"
747
+ })
748
+ }), Effect.andThen(Effect.never));
749
+ yield* Effect.raceFirst(host.drainUntil(stopDispatch, dispatchUntil).pipe(Effect.provideService(ThreadMaintenanceActivity, hostActivity.activity)), initialization);
750
+ }).pipe(Effect.onExit(() => hostActivity.activity.ready))), auxiliaryScope);
751
+ const backfill = yield* Effect.forkIn(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection), Effect.timeoutOption(config.projectionDispatchTimeoutMillis), Effect.onExit(() => signalActivity)), auxiliaryScope);
679
752
  let result = yield* advance(started, yieldAfter, observed, recovery);
680
753
  recovery.started = true;
681
754
  yield* Deferred.succeed(recovery.queue, []);
@@ -694,9 +767,9 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
694
767
  backfill,
695
768
  recoveryFiber
696
769
  ]));
697
- const auxiliaryPending = () => deliveryFiber.pollUnsafe() === void 0 || hostFiber.pollUnsafe() === void 0 || backfill.pollUnsafe() === void 0 || recoveryFiber.pollUnsafe() === void 0;
698
770
  while (true) {
699
771
  const recoveryFinished = recoveryFiber.pollUnsafe() !== void 0;
772
+ const retiredExit = retired.pollUnsafe();
700
773
  if (recoveryFinished) {
701
774
  if (recovery.needsCheckpoint) {
702
775
  yield* Fiber.join(recoveryFiber);
@@ -704,27 +777,43 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
704
777
  if (result.phase === "actionable") phase = "actionable";
705
778
  settled += result.settled;
706
779
  recovery.needsCheckpoint = false;
780
+ yield* PubSub.publish(checks, void 0);
707
781
  }
708
- yield* Deferred.succeed(dispatchClosed, void 0);
709
782
  }
710
- if (retired.pollUnsafe() !== void 0 || !auxiliaryPending()) break;
783
+ if (retiredExit !== void 0 && Exit.isFailure(retiredExit)) break;
784
+ if (recoveryFinished && backfill.pollUnsafe() !== void 0 && messageActivity.quiet() && hostActivity.quiet()) {
785
+ const now = yield* Clock.currentTimeMillis;
786
+ const messageDeadline = yield* messages.pendingDeadline;
787
+ const hostDeadline = yield* host.pendingDeadline;
788
+ const messageDue = deliveryFiber.pollUnsafe() === void 0 && !messageActivity.failed() && Option.isSome(messageDeadline) && messageDeadline.value <= now;
789
+ const hostDue = hostFiber.pollUnsafe() === void 0 && !hostActivity.failed() && now + host.dispatchTimeoutMillis <= DateTime.toEpochMillis(dispatchUntil) && Option.isSome(hostDeadline) && hostDeadline.value <= now;
790
+ if (yield* Effect.sync(() => {
791
+ if (messageDue || hostDue || !messageActivity.quiet() || !hostActivity.quiet()) return false;
792
+ dispatchOpen = false;
793
+ return true;
794
+ })) break;
795
+ yield* PubSub.publish(checks, void 0);
796
+ }
711
797
  const now = yield* Clock.currentTimeMillis;
712
798
  const until = DateTime.toEpochMillis(yieldAfter);
713
799
  if (now >= until) break;
714
800
  const next = Math.min(result.nextAttemptAt ?? Infinity, now + config.wakeScanInterval, until);
715
801
  const recoveryDone = recoveryFinished ? Effect.never : Fiber.await(recoveryFiber);
716
- const ready = yield* Effect.raceFirst(Effect.raceFirst(notified, Effect.sleep(Math.max(0, next - now))).pipe(Effect.as("native")), Effect.raceFirst(recoveryDone.pipe(Effect.as("recovery")), Fiber.await(retired).pipe(Effect.as("retired"))));
717
- if (ready === "recovery") continue;
718
- if (ready === "retired" || retired.pollUnsafe() !== void 0) break;
719
- if (!auxiliaryPending()) continue;
802
+ const retirementDone = retiredExit === void 0 ? Fiber.await(retired) : Effect.never;
803
+ const changed = activityChanged;
804
+ const ready = yield* Effect.raceFirst(Effect.raceFirst(notified, Effect.sleep(Math.max(0, next - now))).pipe(Effect.as("native")), Effect.raceFirst(recoveryDone.pipe(Effect.as("recovery")), Effect.raceFirst(retirementDone.pipe(Effect.as("retired")), Deferred.await(changed).pipe(Effect.as("activity")))));
805
+ if (ready === "recovery" || ready === "retired") continue;
806
+ if (ready === "activity") activityChanged = yield* Deferred.make();
720
807
  if ((yield* Clock.currentTimeMillis) >= until) break;
808
+ if (ready === "native") yield* PubSub.publish(checks, void 0);
721
809
  const awakened = yield* beginNative(observed);
722
810
  result = yield* advance(awakened, yieldAfter, observed, recovery);
723
811
  if (result.phase === "actionable") phase = "actionable";
724
812
  settled += result.settled;
725
813
  observed.nativeOnly = false;
814
+ if (result.settled > 0) yield* PubSub.publish(checks, void 0);
726
815
  }
727
- yield* Deferred.succeed(dispatchClosed, void 0);
816
+ yield* closeDispatch;
728
817
  yield* Fiber.join(retired);
729
818
  if (recovery.needsCheckpoint) {
730
819
  result = yield* advance(started, yieldAfter, observed, recovery, false);
@@ -786,6 +875,6 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
786
875
  }));
787
876
  };
788
877
  //#endregion
789
- export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadRecoveryFault, publishCommitted, Alarm_exports as t };
878
+ export { DurableAlarmError, DurableAlarmService, MaintenancePassReport, ThreadHostMaintenance, ThreadMaintenance, ThreadMaintenanceActivity, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, ThreadRecoveryFault, publishCommitted, Alarm_exports as t };
790
879
 
791
880
  //# sourceMappingURL=Alarm.mjs.map