@effect-agent/platform-cloudflare 0.1.0-beta.36 → 0.1.0-beta.38

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.
@@ -0,0 +1,1189 @@
1
+ import { Clock, Context, Crypto, DateTime, Duration, Effect, Layer, Option, Predicate, Random, Ref, Schema, Semaphore, Stream } from "effect";
2
+ import { AbortCommand, AbortIntent, AdmissionConflict, AgentBindingResolver, AppendConflict, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, CanonicalRecordEnvelope, CanonicalSequence, ConversationNotMaterialized, ConversationStoreError, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, DigestError, DurableAgentRuntime, DurableRuntimeFailpointError, FenceRejected, IdempotencyKey, JoinedToHost, LedgerError, OperationDenied, PersistedJson, Principal, Receipt, ScheduleAuthorizationError, ScheduleCapacityError, ScheduleConflict, ScheduleDestination, ScheduleDriver, ScheduleFailpointError, ScheduleId, ScheduleNotFound, ScheduleOwner, ScheduleScope, ScheduleSnapshot, ScheduleSnapshotPage, ScheduleStorageError, ScheduleTimingRequest, ScheduleValidationError, ScheduleWakeNoop, ScheduledInputAdmission, ScheduledInputRetryable, Scheduling, Settlement, SettlementConflict, SubmissionLedger, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, defaultSchedulingLimits, scheduleOwnerKey } from "@effect-agent/session";
3
+ import { DEFAULT_MAX_STORED_VALUE_BYTES, DoScheduleAlarmControl, DoScheduleTransaction, scheduleStoreLayer } from "@effect-agent/storage-cloudflare";
4
+ import { AgentId, AgentInputError } from "@effect-agent/core";
5
+ import { BrowserCrypto } from "@effect/platform-browser";
6
+ import { SqliteClient } from "@effect/sql-sqlite-do";
7
+ import { DurableObject, DurableObjectAlarm, DurableObjectState, RpcTracing } from "effect-cf";
8
+ //#region src/bindings.ts
9
+ /**
10
+ * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
11
+ * are supplied as Effect services/Layers"). Application code never reads `env` or touches a
12
+ * `DurableObjectState` directly — the Conversation Object class constructs these Layers once
13
+ * per incarnation and everything downstream consumes the services.
14
+ */
15
+ /** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
16
+ var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindingError", {
17
+ binding: Schema.String,
18
+ message: Schema.String
19
+ }) {};
20
+ /**
21
+ * The `DurableObjectNamespace` binding that addresses Conversation Objects. The Object
22
+ * identity rule is `namespace.idFromName(conversationId)` (plan §1.2): Conversation IDs are
23
+ * globally unique, so the mapping is total and deterministic and no directory service exists.
24
+ */
25
+ var ConversationObjectNamespace = class ConversationObjectNamespace extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectNamespace") {
26
+ static layer(namespace) {
27
+ return Layer.succeed(ConversationObjectNamespace)({ namespace });
28
+ }
29
+ };
30
+ /**
31
+ * Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
32
+ * and a namespace binding is a host object no Schema can decode, so this is the documented
33
+ * narrowest-boundary check (structural probe for the namespace surface the transport uses);
34
+ * a missing or misshaped binding fails typed before any Layer is built.
35
+ */
36
+ const conversationNamespaceFromEnv = Effect.fn("conversationNamespaceFromEnv")(function* (env, binding) {
37
+ if (!Predicate.isObjectKeyword(env)) return yield* CloudflareBindingError.make({
38
+ binding,
39
+ message: "The Worker environment is not an object; no bindings are available."
40
+ });
41
+ const candidate = yield* Effect.try({
42
+ try: () => {
43
+ const value = Reflect.get(env, binding);
44
+ if (!Predicate.isObjectKeyword(value)) return void 0;
45
+ const idFromName = Reflect.get(value, "idFromName");
46
+ const get = Reflect.get(value, "get");
47
+ return typeof idFromName === "function" && typeof get === "function" ? value : void 0;
48
+ },
49
+ catch: () => CloudflareBindingError.make({
50
+ binding,
51
+ message: `env.${binding} could not be inspected as a DurableObjectNamespace binding.`
52
+ })
53
+ });
54
+ if (candidate !== void 0) return candidate;
55
+ return yield* CloudflareBindingError.make({
56
+ binding,
57
+ message: `env.${binding} is not a DurableObjectNamespace binding; declare the Conversation Object class under this binding in the Worker configuration.`
58
+ });
59
+ });
60
+ /**
61
+ * Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
62
+ * receiver also opts into the effect-cf native RPC trace-context contract.
63
+ */
64
+ const conversationNamespaceLayer = (env, binding, options = {}) => Layer.effect(ConversationObjectNamespace)(Effect.map(conversationNamespaceFromEnv(env, binding), (namespace) => ({
65
+ namespace,
66
+ ...options.rpcTracing === true ? { rpcTracing: binding } : {}
67
+ })));
68
+ /**
69
+ * The live Durable Object execution context of THIS incarnation. Only Layer construction and
70
+ * the alarm service consume it; important state never lives on it (`ctx.storage` is truth,
71
+ * everything in memory is a cache — deployment spec §11).
72
+ */
73
+ var DurableObjectContext = class DurableObjectContext extends Context.Service()("@effect-agent/platform-cloudflare/DurableObjectContext") {
74
+ static layer(ctx, env) {
75
+ return Layer.succeed(DurableObjectContext)({
76
+ ctx,
77
+ env
78
+ });
79
+ }
80
+ };
81
+ /**
82
+ * The Conversation identity this Object serializes and the producer identity its Attempts
83
+ * write with (`{producerPrefix}:{conversationId}`, plan §1.4). Derived once per incarnation
84
+ * from `ctx.id.name` — the Object identity rule guarantees the name IS the Conversation ID.
85
+ */
86
+ var ConversationObjectIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectIdentity") {};
87
+ //#endregion
88
+ //#region src/config.ts
89
+ /**
90
+ * Schema-validated configuration for the Cloudflare durable runtime (deployment spec §4:
91
+ * decoded once during Layer construction, exposed as a typed service; DEPLOY-003). Every
92
+ * cadence is in milliseconds; every bound is finite and checked before any resource opens.
93
+ */
94
+ const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
95
+ const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
96
+ /** The supplied Cloudflare durable runtime configuration failed validation (DEPLOY-003). */
97
+ var CloudflarePlatformConfigError = class extends Schema.TaggedError()("CloudflarePlatformConfigError", {
98
+ message: Schema.String,
99
+ cause: Schema.optionalKey(Schema.Defect())
100
+ }) {};
101
+ /**
102
+ * An admission was refused by a host resource limit BEFORE any ledger row existed
103
+ * (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
104
+ * behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
105
+ * `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
106
+ * Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
107
+ * nothing was admitted or written.
108
+ */
109
+ var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
110
+ limit: Schema.Literals([
111
+ "queue-depth",
112
+ "input-bytes",
113
+ "database-bytes"
114
+ ]),
115
+ actual: Schema.Int,
116
+ maximum: Schema.Int
117
+ }) {
118
+ get message() {
119
+ return `Admission refused before any ledger row existed: ${this.limit} ${this.actual} exceeds the configured maximum ${this.maximum}. Accepted work is unaffected; retry after the lane drains or raise the limit (DEPLOY-007).`;
120
+ }
121
+ };
122
+ /** The platform's hard per-Object database cap (10 GB, developers.cloudflare.com limits). */
123
+ const CLOUDFLARE_DATABASE_CAP_BYTES = 1e10;
124
+ /** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
125
+ const DEFAULT_MAX_DATABASE_BYTES = 9e9;
126
+ /**
127
+ * Explicit bounded admission quotas checked by the Conversation Object BEFORE admission
128
+ * (exit gate "resource limits are checked before admission").
129
+ */
130
+ var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue")({
131
+ /** Maximum nonterminal Submissions per Conversation lane before new admissions refuse. */
132
+ maxQueueDepthPerLane: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1e5)),
133
+ /** Maximum encoded input bytes; never above the storage per-value bound. */
134
+ maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
135
+ /** Maximum `ctx.storage.sql.databaseSize` at admission; stays under the 10 GB platform cap. */
136
+ maxDatabaseBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(CLOUDFLARE_DATABASE_CAP_BYTES))
137
+ }) {};
138
+ /**
139
+ * Validated Cloudflare durable runtime configuration. The producer identity of one
140
+ * Conversation Object is `{producerPrefix}:{conversationId}` — stable across incarnations of
141
+ * the same deployment, distinct across deployments — and producer-epoch fencing (not the
142
+ * producer name) remains the correctness authority (DUR-006).
143
+ */
144
+ var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue")({
145
+ deploymentId: DeploymentId,
146
+ /** Head of the minted producer identity `{producerPrefix}:{conversationId}`. */
147
+ producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
148
+ /** Submission ownership lease duration (D5); fences work across Object incarnations. */
149
+ ownershipLeaseDuration: PositiveMillis,
150
+ /** Base delay of the alarm re-arm backoff when a pass makes no progress. */
151
+ alarmBackoffBase: PositiveMillis,
152
+ /** Ceiling of the alarm re-arm backoff. */
153
+ alarmBackoffCap: PositiveMillis,
154
+ /**
155
+ * The maintenance-pass scan cadence and the ceiling of every re-arm delay: nonterminal
156
+ * work is revisited at least this often (wake/scan pairing, persistence §14).
157
+ */
158
+ wakeScanInterval: PositiveMillis,
159
+ /** `awaitSettlement` ledger re-check cadence when no wake arrives. */
160
+ settlementPollInterval: PositiveMillis,
161
+ /** Worker ownership-lease renewal cadence during an active Attempt. */
162
+ leaseRenewalInterval: PositiveMillis,
163
+ /** Active-Run abort-intent poll cadence. */
164
+ abortPollInterval: PositiveMillis,
165
+ /** Canonical observation poll cadence of the Durable Object store. */
166
+ observationPollInterval: NonNegativeMillis,
167
+ /** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
168
+ maxStoredValueBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
169
+ /** Opt-in full payload/digest-chain audit while opening the store. */
170
+ verifyOnOpen: Schema.Boolean,
171
+ limits: CloudflareAdmissionLimitsValue
172
+ }) {};
173
+ /** Explicit configuration authority for the assembled Cloudflare durable runtime. */
174
+ var CloudflareDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {};
175
+ /** Documented production defaults applied by `CloudflareDurableRuntime.layer`. */
176
+ const CLOUDFLARE_RUNTIME_DEFAULTS = {
177
+ ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
178
+ alarmBackoffBase: 100,
179
+ alarmBackoffCap: 5e3,
180
+ wakeScanInterval: 1e3,
181
+ settlementPollInterval: 500,
182
+ leaseRenewalInterval: 1e4,
183
+ abortPollInterval: 500,
184
+ observationPollInterval: 25,
185
+ maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
186
+ verifyOnOpen: false,
187
+ maxQueueDepthPerLane: 256,
188
+ maxInputBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
189
+ maxDatabaseBytes: DEFAULT_MAX_DATABASE_BYTES
190
+ };
191
+ //#endregion
192
+ //#region src/boundary.ts
193
+ const MAX_FOREIGN_DIAGNOSTIC_LENGTH = 8192;
194
+ const boundForeignDiagnostic = (message) => message.slice(0, MAX_FOREIGN_DIAGNOSTIC_LENGTH);
195
+ /** Render a foreign failure without trusting accessors or coercion hooks on the value. */
196
+ const safeCauseMessage = (cause, fallback) => {
197
+ try {
198
+ const message = cause instanceof Error ? cause.message : cause;
199
+ return boundForeignDiagnostic(typeof message === "string" ? message : String(message));
200
+ } catch {
201
+ return boundForeignDiagnostic(fallback);
202
+ }
203
+ };
204
+ /** Include an Error name when worker-failure classification needs it. */
205
+ const safeCauseDiagnostic = (cause, fallback) => {
206
+ try {
207
+ return cause instanceof Error ? boundForeignDiagnostic(`${cause.name}: ${cause.message}`) : safeCauseMessage(cause, fallback);
208
+ } catch {
209
+ return boundForeignDiagnostic(fallback);
210
+ }
211
+ };
212
+ /** Read Cloudflare RPC classifications without letting a hostile proxy defect the client. */
213
+ const cloudflareFailureSignals = (cause) => {
214
+ if (!Predicate.isObjectKeyword(cause)) return {};
215
+ try {
216
+ const retryableValue = Reflect.get(cause, "retryable");
217
+ const overloadedValue = Reflect.get(cause, "overloaded");
218
+ const resetValue = Reflect.get(cause, "durableObjectReset");
219
+ const retryable = typeof retryableValue === "boolean" ? retryableValue : resetValue === true ? true : void 0;
220
+ const overloaded = typeof overloadedValue === "boolean" ? overloadedValue : void 0;
221
+ return {
222
+ ...retryable === void 0 ? {} : { retryable },
223
+ ...overloaded === void 0 ? {} : { overloaded }
224
+ };
225
+ } catch {
226
+ return {};
227
+ }
228
+ };
229
+ //#endregion
230
+ //#region src/alarm.ts
231
+ /**
232
+ * The single multiplexed Durable Object alarm (decision D-P6-2). A Durable Object has ONE
233
+ * alarm slot; every cadence the Node host ran on fibers (wake scan, lease expiry, settlement
234
+ * and abort re-checks, retry backoff) multiplexes into one idempotent maintenance pass, and
235
+ * the slot always holds the EARLIEST deadline any caller asked for.
236
+ *
237
+ * The alarm invariant (plan §1.4): every committed actionable mutation carries a newer durable
238
+ * maintenance generation and a committed alarm. Stable externally-driven waits may be
239
+ * nonterminal without retaining an alarm; their resolving mutation advances the generation and
240
+ * restores the alarm atomically.
241
+ */
242
+ /** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */
243
+ var DurableAlarmError = class extends Schema.TaggedError()("DurableAlarmError", {
244
+ operation: Schema.String,
245
+ message: Schema.String,
246
+ cause: Schema.optionalKey(Schema.Defect())
247
+ }) {};
248
+ const alarmFailure = (operation) => (cause) => DurableAlarmError.make({
249
+ operation,
250
+ message: safeCauseMessage(cause, "The Cloudflare alarm API failed without a diagnostic"),
251
+ cause
252
+ });
253
+ /** `ctx.storage` alarm slot as an Effect service; storage is truth, never a memory field. */
254
+ var DurableAlarmService = class DurableAlarmService extends Context.Service()("@effect-agent/platform-cloudflare/DurableAlarmService") {
255
+ static layer = Layer.effect(DurableAlarmService)(Effect.gen(function* () {
256
+ const { ctx } = yield* DurableObjectContext;
257
+ /**
258
+ * In-memory pass bookkeeping — a pure CACHE, never state: a fresh incarnation has no
259
+ * running pass, and a deferred wake lost to eviction was only ever a promptness hint
260
+ * on top of the already-committed pre-armed alarm.
261
+ */
262
+ const runningPasses = yield* Ref.make(0);
263
+ const scheduled = Effect.tryPromise({
264
+ try: () => ctx.storage.getAlarm(),
265
+ catch: alarmFailure("get alarm")
266
+ }).pipe(Effect.map((deadline) => deadline === null ? Option.none() : Option.some(deadline)));
267
+ const scheduleAt = (epochMillis) => Effect.tryPromise({
268
+ try: () => ctx.storage.setAlarm(epochMillis),
269
+ catch: alarmFailure("set alarm")
270
+ });
271
+ const ensureScheduledBy = (epochMillis) => scheduled.pipe(Effect.flatMap((existing) => Option.isSome(existing) && existing.value <= epochMillis ? Effect.void : scheduleAt(epochMillis)));
272
+ const armNow = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => ensureScheduledBy(now)));
273
+ const scheduleNow = Ref.get(runningPasses).pipe(Effect.flatMap((passes) => passes > 0 ? Effect.void : armNow));
274
+ const withWakesDeferred = (body) => Ref.update(runningPasses, (passes) => passes + 1).pipe(Effect.andThen(body), Effect.ensuring(Ref.update(runningPasses, (passes) => passes - 1)));
275
+ const cancel = Effect.tryPromise({
276
+ try: () => ctx.storage.deleteAlarm(),
277
+ catch: alarmFailure("delete alarm")
278
+ });
279
+ return DurableAlarmService.of({
280
+ scheduled,
281
+ scheduleAt,
282
+ ensureScheduledBy,
283
+ scheduleNow,
284
+ withWakesDeferred,
285
+ cancel
286
+ });
287
+ }));
288
+ };
289
+ /** What one maintenance pass did — auditable evidence mirroring `NodeDurableHost`'s report. */
290
+ var MaintenancePassReport = class extends Schema.Class("@effect-agent/platform-cloudflare/MaintenancePassReport")({
291
+ /** `caught-up` is generation-only; `actionable` ran recovery and one bounded drain. */
292
+ phase: Schema.Literals(["caught-up", "actionable"]),
293
+ /** Recovery decisions executed (or deferred) BEFORE any new claim in this pass. */
294
+ recovered: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
295
+ /** Settlements the drain pass finalized. */
296
+ settled: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
297
+ /** Submissions still nonterminal after the pass (suspended/unknown lanes stay honest). */
298
+ nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
299
+ /** `rearmed` for dirty/autonomous work, `cleared` for stable waits or settlement. */
300
+ alarm: Schema.Literals(["rearmed", "cleared"])
301
+ }) {};
302
+ /** Test-only fault authority; production uses the inert layer. */
303
+ var ConversationMaintenanceFailpoint = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationMaintenanceFailpoint") {
304
+ static layer = Layer.succeed(this)({ hit: () => Effect.void });
305
+ };
306
+ const MaintenanceGeneration = Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n));
307
+ /** Versioned, platform-private maintenance state stored through Durable Object KV. */
308
+ var ConversationMaintenanceState = class extends Schema.Class("@effect-agent/platform-cloudflare/ConversationMaintenanceState")({
309
+ schemaVersion: Schema.Literal(1),
310
+ dirty: MaintenanceGeneration,
311
+ processed: MaintenanceGeneration,
312
+ nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
313
+ }) {};
314
+ const MAINTENANCE_STATE_KEY = "effect-agent:conversation-maintenance:v1";
315
+ const decodeMaintenanceState = Schema.decodeUnknownSync(ConversationMaintenanceState);
316
+ const encodeMaintenanceState = Schema.encodeSync(ConversationMaintenanceState);
317
+ const initialMaintenanceState = () => ConversationMaintenanceState.make({
318
+ schemaVersion: 1,
319
+ dirty: 1n,
320
+ processed: 0n,
321
+ nonterminal: 0
322
+ });
323
+ const readMaintenanceState = async (transaction) => {
324
+ const encoded = await transaction.get(MAINTENANCE_STATE_KEY);
325
+ return encoded === void 0 ? {
326
+ state: initialMaintenanceState(),
327
+ initialized: false
328
+ } : {
329
+ state: decodeMaintenanceState(encoded),
330
+ initialized: true
331
+ };
332
+ };
333
+ const ensureTransactionAlarmBy = async (transaction, deadline) => {
334
+ const scheduled = await transaction.getAlarm();
335
+ if (scheduled === null || scheduled > deadline) await transaction.setAlarm(deadline);
336
+ };
337
+ const stableExternalWait = (snapshot, reports) => {
338
+ const decision = reports.get(snapshot.submissionId)?.decision._tag;
339
+ if (decision === "SettleAborted") return false;
340
+ switch (snapshot.state) {
341
+ case "suspended":
342
+ case "joined": return true;
343
+ case "unknown": return decision === "AwaitUnknownResolution" || decision === "MarkUnknown";
344
+ case "admitted": return reports.get(snapshot.submissionId)?.decision._tag === "AwaitParentEstablishment";
345
+ case "input-applied":
346
+ case "joining":
347
+ case "ready":
348
+ case "running":
349
+ case "settled":
350
+ case "terminalizing": return false;
351
+ }
352
+ };
353
+ /**
354
+ * Incremental, quiescent maintenance over a durable dirty/processed generation (issue #93).
355
+ *
356
+ * `pass` = generation snapshot/pre-arm → recovery → bounded drain → generation acknowledgement:
357
+ *
358
+ * 1. One storage transaction reads dirty/processed and re-arms before work. A caught-up forced
359
+ * alarm takes an O(1) path without recovery, ledger scans, or canonical-history reads.
360
+ * 2. Recovery still strictly precedes a new claim, and one bounded drain advances the lane.
361
+ * 3. The final transaction acknowledges only the generation observed at pass start. A racing
362
+ * mutation therefore remains `dirty > processed` and retains its atomically-established alarm.
363
+ * 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease
364
+ * recovery states leave their generation dirty and retain bounded backoff rearming.
365
+ */
366
+ var ConversationMaintenance = class ConversationMaintenance extends Context.Service()("@effect-agent/platform-cloudflare/ConversationMaintenance") {
367
+ static layer = Layer.effect(ConversationMaintenance)(Effect.gen(function* () {
368
+ const runtime = yield* DurableAgentRuntime;
369
+ const resolver = yield* AgentBindingResolver;
370
+ const ledger = yield* SubmissionLedger;
371
+ const alarm = yield* DurableAlarmService;
372
+ const config = yield* CloudflareDurableRuntimeConfig;
373
+ const identity = yield* ConversationObjectIdentity;
374
+ const { ctx } = yield* DurableObjectContext;
375
+ const failpoint = yield* ConversationMaintenanceFailpoint;
376
+ /**
377
+ * Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
378
+ * restarts at zero and merely re-arms sooner than a long-lived one would have.
379
+ */
380
+ const stalls = yield* Ref.make(0);
381
+ /**
382
+ * Incarnation-local mutation count guarded with the generation transactions below. It is
383
+ * deliberately not durable: after eviction every begun mutation has stopped, while its
384
+ * pre-armed dirty generation remains durable for recovery. The short gate never spans the
385
+ * caller's mutation or cross-Object I/O.
386
+ */
387
+ const activeMutations = yield* Ref.make(0);
388
+ const generationGate = yield* Semaphore.make(1);
389
+ const maintenancePassGate = yield* Semaphore.make(1);
390
+ const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
391
+ const runTransaction = (operation, transaction) => Effect.tryPromise({
392
+ try: transaction,
393
+ catch: alarmFailure(operation)
394
+ });
395
+ const beginMutation = Effect.fn("ConversationMaintenance.beginMutation")(function* () {
396
+ yield* failpoint.hit("maintenance:dirty:before");
397
+ const now = yield* Clock.currentTimeMillis;
398
+ yield* runTransaction("advance maintenance generation", () => ctx.storage.transaction(async (transaction) => {
399
+ const { state } = await readMaintenanceState(transaction);
400
+ const next = ConversationMaintenanceState.make({
401
+ ...state,
402
+ dirty: state.dirty + 1n
403
+ });
404
+ await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
405
+ await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
406
+ }));
407
+ yield* failpoint.hit("maintenance:dirty:after");
408
+ yield* Ref.update(activeMutations, (active) => active + 1);
409
+ });
410
+ const endMutation = generationGate.withPermit(Ref.update(activeMutations, (active) => Math.max(0, active - 1)));
411
+ const withMutation = (body) => Effect.acquireUseRelease(generationGate.withPermit(beginMutation()), () => failpoint.hit("maintenance:mutation:armed").pipe(Effect.andThen(body), Effect.tap(() => failpoint.hit("maintenance:mutation:finished"))), () => endMutation);
412
+ const ensureAlarm = Effect.fn("ConversationMaintenance.ensureAlarm")(function* () {
413
+ yield* failpoint.hit("maintenance:ensure:before");
414
+ const now = yield* Clock.currentTimeMillis;
415
+ yield* runTransaction("ensure maintenance alarm", () => ctx.storage.transaction(async (transaction) => {
416
+ const { state, initialized } = await readMaintenanceState(transaction);
417
+ if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
418
+ if (state.dirty > state.processed) await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);
419
+ }));
420
+ yield* failpoint.hit("maintenance:ensure:after");
421
+ });
422
+ const beginPass = Effect.fn("ConversationMaintenance.beginPass")(function* () {
423
+ yield* failpoint.hit("maintenance:begin:before");
424
+ const now = yield* Clock.currentTimeMillis;
425
+ const result = yield* runTransaction("begin maintenance pass", () => ctx.storage.transaction(async (transaction) => {
426
+ const { state, initialized } = await readMaintenanceState(transaction);
427
+ if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
428
+ if (state.processed >= state.dirty) {
429
+ await transaction.deleteAlarm();
430
+ return {
431
+ _tag: "CaughtUp",
432
+ nonterminal: state.nonterminal
433
+ };
434
+ }
435
+ await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
436
+ return {
437
+ _tag: "Actionable",
438
+ generation: state.dirty
439
+ };
440
+ }));
441
+ yield* failpoint.hit("maintenance:begin:after");
442
+ return result;
443
+ });
444
+ const rearmDelay = Effect.fn("ConversationMaintenance.rearmDelay")(function* (progressed) {
445
+ const priorStalls = yield* Ref.getAndUpdate(stalls, (count) => progressed ? 0 : count + 1);
446
+ if (progressed) return config.alarmBackoffBase;
447
+ const exponent = Math.min(priorStalls, 30);
448
+ const backoff = Math.min(config.alarmBackoffCap, config.alarmBackoffBase * 2 ** exponent);
449
+ const jitter = yield* Random.next;
450
+ const jittered = Math.ceil(backoff / 2 + backoff / 2 * jitter);
451
+ return Math.min(jittered, config.wakeScanInterval);
452
+ });
453
+ const pass = Effect.fn("ConversationMaintenance.pass")(function* () {
454
+ const annotate = (report) => Effect.annotateCurrentSpan({
455
+ phase: report.phase,
456
+ recovered: report.recovered,
457
+ settled: report.settled,
458
+ nonterminal: report.nonterminal,
459
+ alarm: report.alarm
460
+ }).pipe(Effect.as(report));
461
+ const started = yield* generationGate.withPermit(Effect.gen(function* () {
462
+ const activeAtStart = yield* Ref.get(activeMutations);
463
+ return {
464
+ ...yield* beginPass(),
465
+ activeAtStart
466
+ };
467
+ }));
468
+ if (started._tag === "CaughtUp") return yield* annotate(MaintenancePassReport.make({
469
+ phase: "caught-up",
470
+ recovered: 0,
471
+ settled: 0,
472
+ nonterminal: started.nonterminal,
473
+ alarm: "cleared"
474
+ }));
475
+ const recovered = yield* runtime.runRecovery;
476
+ const settlements = yield* runtime.processConversationResolved(identity.conversationId).pipe(Effect.provideService(AgentBindingResolver, resolver));
477
+ const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
478
+ const reports = new Map(recovered.map((report) => [report.submissionId, report]));
479
+ const head = remaining[0];
480
+ const headWaiting = head !== void 0 && stableExternalWait(head, reports);
481
+ const autonomous = remaining.some((snapshot, index) => {
482
+ if (index > 0 && headWaiting && snapshot.state === "ready" && reports.get(snapshot.submissionId)?.decision._tag === "ApplyInput") return false;
483
+ return !stableExternalWait(snapshot, reports);
484
+ });
485
+ const progressed = settlements.length > 0 || recovered.some((report) => report.disposition === "repaired");
486
+ const delay = autonomous ? yield* rearmDelay(progressed) : 0;
487
+ const now = yield* Clock.currentTimeMillis;
488
+ yield* failpoint.hit("maintenance:finish:before");
489
+ const alarmDisposition = yield* generationGate.withPermit(Effect.gen(function* () {
490
+ const active = yield* Ref.get(activeMutations);
491
+ return yield* runTransaction("finish maintenance pass", () => ctx.storage.transaction(async (transaction) => {
492
+ const { state } = await readMaintenanceState(transaction);
493
+ const processed = autonomous || started.activeAtStart > 0 || active > 0 ? state.processed : state.processed > started.generation ? state.processed : started.generation;
494
+ const next = ConversationMaintenanceState.make({
495
+ ...state,
496
+ processed,
497
+ nonterminal: remaining.length
498
+ });
499
+ await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
500
+ if (autonomous) {
501
+ await transaction.setAlarm(now + delay);
502
+ return "rearmed";
503
+ }
504
+ if (started.activeAtStart > 0 || active > 0 || next.dirty > next.processed) {
505
+ await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);
506
+ return "rearmed";
507
+ }
508
+ await transaction.deleteAlarm();
509
+ return "cleared";
510
+ }));
511
+ }));
512
+ yield* failpoint.hit("maintenance:finish:after");
513
+ if (alarmDisposition === "cleared") yield* Ref.set(stalls, 0);
514
+ return yield* annotate(MaintenancePassReport.make({
515
+ phase: "actionable",
516
+ recovered: recovered.length,
517
+ settled: settlements.length,
518
+ nonterminal: remaining.length,
519
+ alarm: alarmDisposition
520
+ }));
521
+ });
522
+ return ConversationMaintenance.of({
523
+ pass: alarm.withWakesDeferred(maintenancePassGate.withPermit(pass())),
524
+ ensureAlarm: ensureAlarm(),
525
+ withMutation
526
+ });
527
+ }));
528
+ };
529
+ //#endregion
530
+ //#region src/client.ts
531
+ /**
532
+ * The Worker↔Conversation-Object host protocol (plan §1.4): Schema envelopes for the host
533
+ * entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
534
+ * `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
535
+ * it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
536
+ * failures travel as their own tagged classes and RE-DECODE to identical tags on the caller
537
+ * (error-tag fidelity), and protocol anomalies are answered typed instead of thrown.
538
+ */
539
+ /** Ceiling for host protocol diagnostic strings. */
540
+ const MAX_HOST_DIAGNOSTIC_LENGTH = 4096;
541
+ const BoundedDiagnostic = Schema.String.check(Schema.isMaxLength(MAX_HOST_DIAGNOSTIC_LENGTH));
542
+ /** Truncate a diagnostic string to the host protocol's bounded length. */
543
+ const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH ? `${value.slice(0, MAX_HOST_DIAGNOSTIC_LENGTH - 3)}...` : value;
544
+ /**
545
+ * The envelope itself could not be honored: the Object could not decode the request, or a
546
+ * response could not be encoded/decoded. Never carries operation semantics.
547
+ */
548
+ var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
549
+ /** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
550
+ var ConversationClientError = class extends Schema.TaggedError()("ConversationClientError", {
551
+ conversationId: Schema.String,
552
+ message: Schema.String,
553
+ cause: Schema.optionalKey(Schema.Defect()),
554
+ /** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
555
+ retryable: Schema.optionalKey(Schema.Boolean),
556
+ /** Cloudflare overloads are surfaced immediately instead of adding retry pressure. */
557
+ overloaded: Schema.optionalKey(Schema.Boolean)
558
+ }) {};
559
+ /**
560
+ * One durable submission, input ALREADY encoded by the caller through the Agent Binding's
561
+ * input schema (the Worker bundles the same Agent definitions as the Object, so schema
562
+ * validation happens client-side; the resolved Binding re-validates at claim time). The
563
+ * Conversation identity is deliberately absent — the addressed Object IS the lane.
564
+ */
565
+ var SubmitRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/SubmitRequest")({
566
+ agentId: AgentId,
567
+ principal: Principal,
568
+ idempotencyKey: IdempotencyKey,
569
+ definitions: DefinitionDigests,
570
+ inputPayload: PersistedJson
571
+ }) {};
572
+ /** One bounded page of canonical records after an optional sequence. */
573
+ var ObservePageRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/ObservePageRequest")({
574
+ afterSequence: Schema.optionalKey(CanonicalSequence),
575
+ limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1024))
576
+ }) {};
577
+ /** One event-driven wait for canonical progress strictly after this sequence. */
578
+ var AwaitProgressRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AwaitProgressRequest")({
579
+ afterSequence: CanonicalSequence,
580
+ waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256))
581
+ }) {};
582
+ /** Best-effort cancellation of one in-flight progress RPC. */
583
+ var CancelProgressRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/CancelProgressRequest")({ waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)) }) {};
584
+ /**
585
+ * Every typed failure a host entry point can produce, plus the protocol's own errors. Same
586
+ * closed-union discipline as the WP2 `PortFailure`: members re-decode to the SAME tagged
587
+ * classes on the Worker side; `cause` chains travel as Schema defects without instance
588
+ * fidelity (plan §2.8).
589
+ */
590
+ const HostFailure = Schema.Union([
591
+ AgentInputError,
592
+ DigestError,
593
+ AdmissionConflict,
594
+ SettlementConflict,
595
+ ApprovalConflict,
596
+ UnknownResolutionConflict,
597
+ JoinedToHost,
598
+ LedgerError,
599
+ ConversationStoreError,
600
+ ConversationNotMaterialized,
601
+ AppendConflict,
602
+ FenceRejected,
603
+ DurableRuntimeFailpointError,
604
+ AdmissionLimitExceeded,
605
+ DurableAlarmError,
606
+ OperationDenied,
607
+ HostProtocolError
608
+ ]);
609
+ var SubmitSucceeded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SubmitSucceeded")("SubmitSucceeded", { receipt: Receipt }) {};
610
+ var SettlementReached = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SettlementReached")("SettlementReached", { settlement: Settlement }) {};
611
+ var ObservedPage = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ObservedPage")("ObservedPage", { records: Schema.Array(CanonicalRecordEnvelope).check(Schema.isMaxLength(1024)) }) {};
612
+ /** A record was already committed or an incarnation-local hint says the caller should re-read. */
613
+ var ProgressObserved = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ProgressObserved")("ProgressObserved", {}) {};
614
+ var ProgressCancelled = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ProgressCancelled")("ProgressCancelled", {}) {};
615
+ var AbortRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/AbortRecorded")("AbortRecorded", { intent: AbortIntent }) {};
616
+ var ApprovalRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ApprovalRecorded")("ApprovalRecorded", { intent: ApprovalDecisionIntent }) {};
617
+ var UnknownResolutionRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/UnknownResolutionRecorded")("UnknownResolutionRecorded", { intent: UnknownResolutionIntent }) {};
618
+ /** The entry point failed TYPED on the Object; the failure re-decodes verbatim. */
619
+ var HostFailed = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/HostFailed")("HostFailed", { failure: HostFailure }) {};
620
+ /** The uniform answer of one host entry point. Callers narrow by the tag their call implies. */
621
+ const HostResponse = Schema.Union([
622
+ SubmitSucceeded,
623
+ SettlementReached,
624
+ ObservedPage,
625
+ ProgressObserved,
626
+ ProgressCancelled,
627
+ AbortRecorded,
628
+ ApprovalRecorded,
629
+ UnknownResolutionRecorded,
630
+ HostFailed
631
+ ]);
632
+ const decodeSubmitRequest = Schema.decodeUnknownEffect(SubmitRequest);
633
+ const encodeSubmitRequest = Schema.encodeEffect(SubmitRequest);
634
+ const decodeReceipt = Schema.decodeUnknownEffect(Receipt);
635
+ const encodeReceipt = Schema.encodeEffect(Receipt);
636
+ const decodeObservePageRequest = Schema.decodeUnknownEffect(ObservePageRequest);
637
+ const encodeObservePageRequest = Schema.encodeEffect(ObservePageRequest);
638
+ const decodeAwaitProgressRequest = Schema.decodeUnknownEffect(AwaitProgressRequest);
639
+ const encodeAwaitProgressRequest = Schema.encodeEffect(AwaitProgressRequest);
640
+ const decodeCancelProgressRequest = Schema.decodeUnknownEffect(CancelProgressRequest);
641
+ const encodeCancelProgressRequest = Schema.encodeEffect(CancelProgressRequest);
642
+ const decodeAbortCommand = Schema.decodeUnknownEffect(AbortCommand);
643
+ const encodeAbortCommand = Schema.encodeEffect(AbortCommand);
644
+ const decodeApprovalDecisionCommand = Schema.decodeUnknownEffect(ApprovalDecisionCommand);
645
+ const encodeApprovalDecisionCommand = Schema.encodeEffect(ApprovalDecisionCommand);
646
+ const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolutionCommand);
647
+ const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
648
+ const encodeHostResponse = Schema.encodeEffect(HostResponse);
649
+ const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
650
+ /** Failure surface of `CloudflareConversationClient.submit`. */
651
+ const ClientSubmitHostFailure = Schema.Union([
652
+ AgentInputError,
653
+ DigestError,
654
+ AdmissionConflict,
655
+ LedgerError,
656
+ ConversationStoreError,
657
+ ConversationNotMaterialized,
658
+ AppendConflict,
659
+ FenceRejected,
660
+ DurableRuntimeFailpointError,
661
+ AdmissionLimitExceeded,
662
+ DurableAlarmError,
663
+ HostProtocolError
664
+ ]);
665
+ const ClientAwaitHostFailure = Schema.Union([
666
+ LedgerError,
667
+ SettlementConflict,
668
+ HostProtocolError
669
+ ]);
670
+ const ClientObserveHostFailure = Schema.Union([
671
+ ConversationStoreError,
672
+ ConversationNotMaterialized,
673
+ OperationDenied,
674
+ HostProtocolError
675
+ ]);
676
+ const ClientAbortHostFailure = Schema.Union([
677
+ LedgerError,
678
+ SettlementConflict,
679
+ JoinedToHost,
680
+ DurableRuntimeFailpointError,
681
+ DurableAlarmError,
682
+ HostProtocolError
683
+ ]);
684
+ const ClientApprovalHostFailure = Schema.Union([
685
+ LedgerError,
686
+ SettlementConflict,
687
+ ApprovalConflict,
688
+ OperationDenied,
689
+ DurableAlarmError,
690
+ HostProtocolError
691
+ ]);
692
+ const ClientUnknownHostFailure = Schema.Union([
693
+ LedgerError,
694
+ SettlementConflict,
695
+ UnknownResolutionConflict,
696
+ JoinedToHost,
697
+ DurableRuntimeFailpointError,
698
+ OperationDenied,
699
+ DurableAlarmError,
700
+ HostProtocolError
701
+ ]);
702
+ const outOfContract = (conversationId, operation, observed) => ConversationClientError.make({
703
+ conversationId,
704
+ message: boundHostDiagnostic(`The Conversation Object answered ${operation} with the out-of-contract ${observed}.`)
705
+ });
706
+ const hostRpcMethods = {
707
+ submit: "submitEncoded",
708
+ awaitSettlement: "awaitSettlementEncoded",
709
+ awaitProgress: "awaitProgressEncoded",
710
+ cancelProgress: "cancelProgressEncoded",
711
+ observePage: "observePage",
712
+ abort: "abortEncoded",
713
+ resolveApproval: "resolveApprovalEncoded",
714
+ resolveUnknown: "resolveUnknownEncoded"
715
+ };
716
+ /** Worker-side client over the Conversation Object namespace (DEPLOY-010). */
717
+ var CloudflareConversationClient = class CloudflareConversationClient extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareConversationClient") {
718
+ static layer = Layer.effect(CloudflareConversationClient)(Effect.gen(function* () {
719
+ const { namespace, rpcTracing } = yield* ConversationObjectNamespace;
720
+ const crypto = yield* Crypto.Crypto;
721
+ const call = Effect.fn(function* (conversationId, operation, encoded) {
722
+ const traceArgs = rpcTracing === void 0 ? [] : yield* RpcTracing.withRpcTraceContext([]);
723
+ const raw = yield* Effect.tryPromise({
724
+ try: () => {
725
+ return namespace.get(namespace.idFromName(conversationId))[hostRpcMethods[operation]](encoded, ...traceArgs);
726
+ },
727
+ catch: (cause) => ConversationClientError.make({
728
+ conversationId,
729
+ message: boundHostDiagnostic(`${operation} did not reach the Conversation Object: ${safeCauseMessage(cause, "the RPC failed without a diagnostic")}`),
730
+ cause,
731
+ ...cloudflareFailureSignals(cause)
732
+ })
733
+ });
734
+ return yield* decodeHostResponse(raw).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`The ${operation} answer could not be decoded: ${error.message}`) })));
735
+ }, (effect, conversationId, operation) => rpcTracing === void 0 ? Effect.withSpan(effect, "CloudflareConversationClient.call", { attributes: {
736
+ conversationId,
737
+ operation
738
+ } }) : RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]));
739
+ const expect = (conversationId, operation, resultSchema, failureSchema) => {
740
+ const isExpectedResult = Schema.is(resultSchema);
741
+ const isExpectedFailure = Schema.is(failureSchema);
742
+ return (response) => {
743
+ if (response._tag === "HostFailed") {
744
+ const failure = response.failure;
745
+ return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(conversationId, operation, `failure ${failure._tag}`));
746
+ }
747
+ if (!isExpectedResult(response)) return Effect.fail(outOfContract(conversationId, operation, `result ${response._tag}`));
748
+ return Effect.succeed(response);
749
+ };
750
+ };
751
+ const readPage = (conversationId, options) => Effect.gen(function* () {
752
+ const request = ObservePageRequest.make({
753
+ ...options?.afterSequence === void 0 ? {} : { afterSequence: options.afterSequence },
754
+ limit: options?.limit ?? 256
755
+ });
756
+ const encoded = yield* encodeObservePageRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`observePage request encode failed: ${error.message}`) })));
757
+ const response = yield* call(conversationId, "observePage", encoded);
758
+ return (yield* expect(conversationId, "observePage", ObservedPage, ClientObserveHostFailure)(response)).records;
759
+ });
760
+ const cancelProgress = (conversationId, waiterId) => encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(Effect.mapError(() => void 0), Effect.flatMap((encoded) => call(conversationId, "cancelProgress", encoded)), Effect.asVoid, Effect.ignore);
761
+ return CloudflareConversationClient.of({
762
+ submit: (agent, input, options) => Effect.gen(function* () {
763
+ const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError((cause) => AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` })));
764
+ const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(encodedInput).pipe(Effect.mapError(() => AgentInputError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
765
+ const request = SubmitRequest.make({
766
+ agentId: agent.definition.id,
767
+ principal: options.principal,
768
+ idempotencyKey: options.idempotencyKey,
769
+ definitions: options.definitions,
770
+ inputPayload
771
+ });
772
+ const encoded = yield* encodeSubmitRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`submit request encode failed: ${error.message}`) })));
773
+ const response = yield* call(options.conversationId, "submit", encoded);
774
+ return (yield* expect(options.conversationId, "submit", SubmitSucceeded, ClientSubmitHostFailure)(response)).receipt;
775
+ }),
776
+ awaitSettlement: (receipt) => Effect.gen(function* () {
777
+ const encoded = yield* encodeReceipt(receipt).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`receipt encode failed: ${error.message}`) })));
778
+ const response = yield* call(receipt.conversationId, "awaitSettlement", encoded);
779
+ return (yield* expect(receipt.conversationId, "awaitSettlement", SettlementReached, ClientAwaitHostFailure)(response)).settlement;
780
+ }),
781
+ awaitProgress: (conversationId, afterSequence) => Effect.gen(function* () {
782
+ const waiterId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress cancellation identity generation failed: ${error.message}`) })));
783
+ const request = AwaitProgressRequest.make({
784
+ afterSequence,
785
+ waiterId
786
+ });
787
+ const encoded = yield* encodeAwaitProgressRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress request encode failed: ${error.message}`) })));
788
+ const attempt = (retry) => call(conversationId, "awaitProgress", encoded).pipe(Effect.flatMap(expect(conversationId, "awaitProgress", ProgressObserved, ClientObserveHostFailure)), Effect.asVoid, Effect.catchTag("ConversationClientError", (error) => error.retryable === true && error.overloaded !== true && retry < 5 ? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(Effect.andThen(attempt(retry + 1))) : Effect.fail(error)));
789
+ yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(conversationId, waiterId)));
790
+ }),
791
+ readPage,
792
+ readAll: (conversationId) => Effect.gen(function* () {
793
+ const all = [];
794
+ let after;
795
+ for (;;) {
796
+ const page = yield* readPage(conversationId, {
797
+ afterSequence: after,
798
+ limit: 1024
799
+ });
800
+ all.push(...page);
801
+ const last = page.at(-1);
802
+ if (page.length < 1024 || last === void 0) return all;
803
+ after = last.sequence;
804
+ }
805
+ }),
806
+ abort: (conversationId, command) => Effect.gen(function* () {
807
+ const encoded = yield* encodeAbortCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`abort command encode failed: ${error.message}`) })));
808
+ const response = yield* call(conversationId, "abort", encoded);
809
+ return (yield* expect(conversationId, "abort", AbortRecorded, ClientAbortHostFailure)(response)).intent;
810
+ }),
811
+ resolveApproval: (conversationId, command) => Effect.gen(function* () {
812
+ const encoded = yield* encodeApprovalDecisionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`approval command encode failed: ${error.message}`) })));
813
+ const response = yield* call(conversationId, "resolveApproval", encoded);
814
+ return (yield* expect(conversationId, "resolveApproval", ApprovalRecorded, ClientApprovalHostFailure)(response)).intent;
815
+ }),
816
+ resolveUnknown: (conversationId, command) => Effect.gen(function* () {
817
+ const encoded = yield* encodeUnknownResolutionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`resolution command encode failed: ${error.message}`) })));
818
+ const response = yield* call(conversationId, "resolveUnknown", encoded);
819
+ return (yield* expect(conversationId, "resolveUnknown", UnknownResolutionRecorded, ClientUnknownHostFailure)(response)).intent;
820
+ })
821
+ });
822
+ }));
823
+ };
824
+ //#endregion
825
+ //#region src/scheduling.ts
826
+ const SCHEDULE_ALARM_TAG = "effect-agent/ScheduleOwnerWake";
827
+ const SCHEDULE_ALARM_ID = "driver";
828
+ const ScheduleAlarmPayload = Schema.Struct({
829
+ schemaVersion: Schema.Literal(1),
830
+ generation: Schema.Int.check(Schema.isGreaterThan(0))
831
+ });
832
+ var ScheduleAlarmProtocolError = class extends Schema.TaggedError()("ScheduleAlarmProtocolError", { message: Schema.String }) {};
833
+ const boundedProtocolMessage = (message) => message.length <= 4096 ? message : `${message.slice(0, 4093)}...`;
834
+ var ScheduleOwnerProtocolError = class extends Schema.TaggedError()("ScheduleOwnerProtocolError", { message: Schema.String.check(Schema.isMaxLength(4096)) }) {};
835
+ const ScheduleMutationRequestFields = {
836
+ schemaVersion: Schema.Literal(1),
837
+ agentId: AgentId,
838
+ input: PersistedJson,
839
+ scope: ScheduleScope,
840
+ scheduleId: ScheduleId,
841
+ timing: ScheduleTimingRequest,
842
+ destination: ScheduleDestination,
843
+ deliveryPrincipal: ScheduleScope.fields.principal,
844
+ definitions: DefinitionDigests
845
+ };
846
+ const ScheduleCreateRequest = Schema.TaggedStruct("Create", ScheduleMutationRequestFields);
847
+ const ScheduleUpdateRequest = Schema.TaggedStruct("Update", {
848
+ ...ScheduleMutationRequestFields,
849
+ expectedRevision: Schema.Int.check(Schema.isGreaterThan(0))
850
+ });
851
+ const ScheduleGetRequest = Schema.TaggedStruct("Get", {
852
+ schemaVersion: Schema.Literal(1),
853
+ scope: ScheduleScope,
854
+ scheduleId: ScheduleId
855
+ });
856
+ const ScheduleListRequest = Schema.TaggedStruct("List", {
857
+ schemaVersion: Schema.Literal(1),
858
+ scope: ScheduleScope,
859
+ after: Schema.optionalKey(ScheduleId),
860
+ limit: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)))
861
+ });
862
+ const ScheduleControlRequest = Schema.TaggedStruct("Control", {
863
+ schemaVersion: Schema.Literal(1),
864
+ operation: Schema.Literals([
865
+ "pause",
866
+ "resume",
867
+ "cancel"
868
+ ]),
869
+ scope: ScheduleScope,
870
+ scheduleId: ScheduleId,
871
+ expectedRevision: Schema.Int.check(Schema.isGreaterThan(0))
872
+ });
873
+ const ScheduleOwnerRequest = Schema.Union([
874
+ ScheduleCreateRequest,
875
+ ScheduleUpdateRequest,
876
+ ScheduleGetRequest,
877
+ ScheduleListRequest,
878
+ ScheduleControlRequest
879
+ ]);
880
+ const ScheduleOwnerFailure = Schema.Union([
881
+ ScheduleValidationError,
882
+ ScheduleAuthorizationError,
883
+ ScheduleConflict,
884
+ ScheduleNotFound,
885
+ ScheduleCapacityError,
886
+ ScheduleStorageError,
887
+ ScheduleFailpointError,
888
+ ScheduleOwnerProtocolError
889
+ ]);
890
+ const ScheduleOwnerResponse = Schema.Union([
891
+ Schema.TaggedStruct("Snapshot", { value: ScheduleSnapshot }),
892
+ Schema.TaggedStruct("Page", { value: ScheduleSnapshotPage }),
893
+ Schema.TaggedStruct("Failed", { failure: ScheduleOwnerFailure })
894
+ ]);
895
+ const decodeScheduleOwnerRequest = Schema.decodeUnknownEffect(ScheduleOwnerRequest);
896
+ const encodeScheduleOwnerRequest = Schema.encodeEffect(ScheduleOwnerRequest);
897
+ const decodeScheduleOwnerResponse = Schema.decodeUnknownEffect(ScheduleOwnerResponse);
898
+ const encodeScheduleOwnerResponse = Schema.encodeEffect(ScheduleOwnerResponse);
899
+ const scheduleProtocolFailure = (message) => ({
900
+ _tag: "Failed",
901
+ failure: ScheduleOwnerProtocolError.make({ message: boundedProtocolMessage(message) })
902
+ });
903
+ var ScheduleOwnerNamespace = class extends Context.Service()("@effect-agent/platform-cloudflare/ScheduleOwnerNamespace") {};
904
+ const passthroughAgent = (agentId) => ({ definition: {
905
+ id: agentId,
906
+ input: PersistedJson
907
+ } });
908
+ const requestOwner = (request) => request.scope.owner;
909
+ /** Provides the same authorized management service as NodeScheduling.layer. */
910
+ var CloudflareSchedulingClient = class {
911
+ static layer = Layer.effect(Scheduling, Effect.gen(function* () {
912
+ const { namespace } = yield* ScheduleOwnerNamespace;
913
+ const call = Effect.fn("CloudflareSchedulingClient.call")(function* (owner, request) {
914
+ const encoded = yield* encodeScheduleOwnerRequest(request).pipe(Effect.mapError(() => ScheduleStorageError.make({
915
+ operation: "Schedule Owner protocol",
916
+ reason: "corrupt"
917
+ })));
918
+ const raw = yield* Effect.tryPromise({
919
+ try: () => namespace.get(namespace.idFromName(scheduleOwnerKey(owner))).schedule(encoded),
920
+ catch: () => ScheduleStorageError.make({
921
+ operation: "call Schedule Owner",
922
+ reason: "unavailable"
923
+ })
924
+ });
925
+ const response = yield* decodeScheduleOwnerResponse(raw).pipe(Effect.mapError(() => ScheduleStorageError.make({
926
+ operation: "Schedule Owner protocol",
927
+ reason: "corrupt"
928
+ })));
929
+ if (response._tag !== "Failed") return response;
930
+ return yield* response.failure._tag === "ScheduleOwnerProtocolError" ? ScheduleStorageError.make({
931
+ operation: "Schedule Owner protocol",
932
+ reason: "corrupt"
933
+ }) : response.failure;
934
+ });
935
+ const encodeInput = Effect.fn("CloudflareSchedulingClient.encodeInput")(function* (agent, input) {
936
+ const encoded = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError(() => ScheduleValidationError.make({ message: "Unable to encode Agent input" })));
937
+ return yield* Schema.decodeUnknownEffect(PersistedJson)(encoded).pipe(Effect.mapError(() => ScheduleValidationError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
938
+ });
939
+ const create = (agent, input, options) => Effect.gen(function* () {
940
+ const payload = yield* encodeInput(agent, input);
941
+ const response = yield* call(options.scope.owner, {
942
+ _tag: "Create",
943
+ schemaVersion: 1,
944
+ agentId: agent.definition.id,
945
+ input: payload,
946
+ ...options
947
+ });
948
+ return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
949
+ operation: "Schedule Owner protocol",
950
+ reason: "corrupt"
951
+ });
952
+ });
953
+ const update = (agent, input, options) => Effect.gen(function* () {
954
+ const payload = yield* encodeInput(agent, input);
955
+ const response = yield* call(options.scope.owner, {
956
+ _tag: "Update",
957
+ schemaVersion: 1,
958
+ agentId: agent.definition.id,
959
+ input: payload,
960
+ ...options
961
+ });
962
+ return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
963
+ operation: "Schedule Owner protocol",
964
+ reason: "corrupt"
965
+ });
966
+ });
967
+ const get = (scope, scheduleId) => Effect.gen(function* () {
968
+ const response = yield* call(scope.owner, {
969
+ _tag: "Get",
970
+ schemaVersion: 1,
971
+ scope,
972
+ scheduleId
973
+ });
974
+ return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
975
+ operation: "Schedule Owner protocol",
976
+ reason: "corrupt"
977
+ });
978
+ });
979
+ const list = (scope, options = {}) => Effect.gen(function* () {
980
+ const response = yield* call(scope.owner, {
981
+ _tag: "List",
982
+ schemaVersion: 1,
983
+ scope,
984
+ ...options.after === void 0 ? {} : { after: options.after },
985
+ ...options.limit === void 0 ? {} : { limit: options.limit }
986
+ });
987
+ return response._tag === "Page" ? response.value : yield* ScheduleStorageError.make({
988
+ operation: "Schedule Owner protocol",
989
+ reason: "corrupt"
990
+ });
991
+ });
992
+ const control = (operation, scope, scheduleId, expectedRevision) => Effect.gen(function* () {
993
+ const response = yield* call(scope.owner, {
994
+ _tag: "Control",
995
+ schemaVersion: 1,
996
+ operation,
997
+ scope,
998
+ scheduleId,
999
+ expectedRevision
1000
+ });
1001
+ return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
1002
+ operation: "Schedule Owner protocol",
1003
+ reason: "corrupt"
1004
+ });
1005
+ });
1006
+ return Scheduling.of({
1007
+ create,
1008
+ update,
1009
+ get,
1010
+ list,
1011
+ pause: (scope, id, revision) => control("pause", scope, id, revision),
1012
+ resume: (scope, id, revision) => control("resume", scope, id, revision),
1013
+ cancel: (scope, id, revision) => control("cancel", scope, id, revision)
1014
+ });
1015
+ }));
1016
+ };
1017
+ var ScheduleOwnerIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ScheduleOwnerIdentity") {};
1018
+ const decodeOwnerName = Effect.fn("decodeScheduleOwnerName")(function* (name) {
1019
+ if (name === null || name === void 0) return yield* ScheduleOwnerProtocolError.make({ message: "Schedule Owner objects require an idFromName identity" });
1020
+ const tuple = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Tuple([Schema.String, Schema.String])))(name).pipe(Effect.mapError(() => ScheduleOwnerProtocolError.make({ message: "Schedule Owner object name is malformed" })));
1021
+ return yield* Schema.decodeUnknownEffect(ScheduleOwner)({
1022
+ tenantId: tuple[0],
1023
+ ownerId: tuple[1]
1024
+ }).pipe(Effect.mapError(() => ScheduleOwnerProtocolError.make({ message: "Schedule Owner object identity is invalid" })));
1025
+ });
1026
+ const alarmStorageError = (operation) => (error) => ScheduleStorageError.make({
1027
+ operation,
1028
+ reason: error._tag === "StorageOperationError" ? "unavailable" : "corrupt"
1029
+ });
1030
+ const transactionLayer = Layer.effect(DoScheduleTransaction, Effect.gen(function* () {
1031
+ const alarms = yield* DurableObjectAlarm.DurableObjectAlarm;
1032
+ return DoScheduleTransaction.of({ run: (body) => Effect.gen(function* () {
1033
+ const nowMillis = yield* Clock.currentTimeMillis;
1034
+ return yield* alarms.transaction((transaction) => body((replacement) => replacement.deadlineAtMillis === null ? transaction.cancelAlarm({
1035
+ id: SCHEDULE_ALARM_ID,
1036
+ tag: SCHEDULE_ALARM_TAG
1037
+ }).pipe(Effect.mapError(alarmStorageError("cancel Schedule Owner alarm"))) : Effect.fromOption(DateTime.make(Math.max(replacement.deadlineAtMillis, nowMillis + 1))).pipe(Effect.mapError(() => ScheduleStorageError.make({
1038
+ operation: "validate Schedule Owner alarm deadline",
1039
+ reason: "corrupt"
1040
+ })), Effect.flatMap((runAt) => transaction.scheduleAlarm({
1041
+ id: SCHEDULE_ALARM_ID,
1042
+ tag: SCHEDULE_ALARM_TAG,
1043
+ runAt,
1044
+ payload: {
1045
+ schemaVersion: 1,
1046
+ generation: replacement.generation
1047
+ }
1048
+ }).pipe(Effect.mapError(alarmStorageError("schedule Schedule Owner alarm"))))))).pipe(Effect.catchTag("StorageOperationError", () => ScheduleStorageError.make({
1049
+ operation: "commit Schedule Owner transaction",
1050
+ reason: "unavailable"
1051
+ })));
1052
+ }) });
1053
+ }));
1054
+ const admissionLayer = Layer.effect(ScheduledInputAdmission, Effect.gen(function* () {
1055
+ const client = yield* CloudflareConversationClient;
1056
+ const submit = (envelope) => client.submit(passthroughAgent(envelope.agentId), envelope.input, {
1057
+ conversationId: envelope.conversationId,
1058
+ principal: envelope.deliveryPrincipal,
1059
+ idempotencyKey: envelope.admissionKey,
1060
+ definitions: envelope.definitions
1061
+ }).pipe(Effect.catchTags({
1062
+ AdmissionConflict: () => ScheduleStorageError.make({
1063
+ operation: "scheduled admission",
1064
+ reason: "corrupt"
1065
+ }),
1066
+ AdmissionLimitExceeded: () => ScheduledInputRetryable.make({ reason: "capacity" }),
1067
+ ConversationClientError: (error) => ScheduledInputRetryable.make({ reason: error.overloaded === true ? "capacity" : "transport" }),
1068
+ HostProtocolError: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
1069
+ LedgerError: () => ScheduledInputRetryable.make({ reason: "storage" }),
1070
+ ConversationStoreError: () => ScheduledInputRetryable.make({ reason: "storage" }),
1071
+ DurableAlarmError: () => ScheduledInputRetryable.make({ reason: "storage" }),
1072
+ AgentInputError: () => ScheduleStorageError.make({
1073
+ operation: "scheduled admission",
1074
+ reason: "corrupt"
1075
+ }),
1076
+ DigestError: () => ScheduleStorageError.make({
1077
+ operation: "scheduled admission",
1078
+ reason: "corrupt"
1079
+ }),
1080
+ ConversationNotMaterialized: () => ScheduledInputRetryable.make({ reason: "storage" }),
1081
+ AppendConflict: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
1082
+ FenceRejected: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
1083
+ DurableRuntimeFailpointError: () => ScheduledInputRetryable.make({ reason: "ambiguous" })
1084
+ }));
1085
+ return ScheduledInputAdmission.of({ submit });
1086
+ }));
1087
+ const ensureOwner = (expected, request) => {
1088
+ const observed = requestOwner(request);
1089
+ return observed.tenantId === expected.tenantId && observed.ownerId === expected.ownerId ? Effect.void : Effect.fail(ScheduleOwnerProtocolError.make({ message: "The request owner does not match the addressed Schedule Owner object" }));
1090
+ };
1091
+ const handleScheduleRequest = Effect.fn("ScheduleOwner.handleRequest")(function* (encoded) {
1092
+ const decoded = yield* decodeScheduleOwnerRequest(encoded).pipe(Effect.result);
1093
+ if (decoded._tag === "Failure") return yield* encodeScheduleOwnerResponse(scheduleProtocolFailure("The Schedule request could not be decoded")).pipe(Effect.orDie);
1094
+ const request = decoded.success;
1095
+ const { owner } = yield* ScheduleOwnerIdentity;
1096
+ const scheduling = yield* Scheduling;
1097
+ const response = yield* Effect.gen(function* () {
1098
+ yield* ensureOwner(owner, request);
1099
+ switch (request._tag) {
1100
+ case "Create": return {
1101
+ _tag: "Snapshot",
1102
+ value: yield* scheduling.create(passthroughAgent(request.agentId), request.input, {
1103
+ scope: request.scope,
1104
+ scheduleId: request.scheduleId,
1105
+ timing: request.timing,
1106
+ destination: request.destination,
1107
+ deliveryPrincipal: request.deliveryPrincipal,
1108
+ definitions: request.definitions
1109
+ })
1110
+ };
1111
+ case "Update": return {
1112
+ _tag: "Snapshot",
1113
+ value: yield* scheduling.update(passthroughAgent(request.agentId), request.input, {
1114
+ scope: request.scope,
1115
+ scheduleId: request.scheduleId,
1116
+ timing: request.timing,
1117
+ destination: request.destination,
1118
+ deliveryPrincipal: request.deliveryPrincipal,
1119
+ definitions: request.definitions,
1120
+ expectedRevision: request.expectedRevision
1121
+ })
1122
+ };
1123
+ case "Get": return {
1124
+ _tag: "Snapshot",
1125
+ value: yield* scheduling.get(request.scope, request.scheduleId)
1126
+ };
1127
+ case "List": return {
1128
+ _tag: "Page",
1129
+ value: yield* scheduling.list(request.scope, {
1130
+ ...request.after === void 0 ? {} : { after: request.after },
1131
+ ...request.limit === void 0 ? {} : { limit: request.limit }
1132
+ })
1133
+ };
1134
+ case "Control": return {
1135
+ _tag: "Snapshot",
1136
+ value: request.operation === "pause" ? yield* scheduling.pause(request.scope, request.scheduleId, request.expectedRevision) : request.operation === "resume" ? yield* scheduling.resume(request.scope, request.scheduleId, request.expectedRevision) : yield* scheduling.cancel(request.scope, request.scheduleId, request.expectedRevision)
1137
+ };
1138
+ }
1139
+ }).pipe(Effect.map((value) => value), Effect.catch((failure) => Schema.is(ScheduleOwnerFailure)(failure) ? Effect.succeed({
1140
+ _tag: "Failed",
1141
+ failure
1142
+ }) : Effect.succeed(scheduleProtocolFailure("The Schedule operation failed outside its public contract"))));
1143
+ return yield* encodeScheduleOwnerResponse(response).pipe(Effect.orDie);
1144
+ });
1145
+ const scheduleAlarmHandler = (limits) => DurableObjectAlarm.processDue((event) => Effect.gen(function* () {
1146
+ if (event.tag !== SCHEDULE_ALARM_TAG || event.id !== SCHEDULE_ALARM_ID) return yield* ScheduleAlarmProtocolError.make({ message: `Unsupported Schedule Owner alarm ${event.tag}/${event.id}` });
1147
+ yield* Schema.decodeUnknownEffect(ScheduleAlarmPayload)(event.payload).pipe(Effect.mapError(() => ScheduleAlarmProtocolError.make({ message: "Unsupported Schedule Owner alarm payload version" })));
1148
+ const scheduling = yield* ScheduleDriver;
1149
+ const alarmControl = yield* DoScheduleAlarmControl;
1150
+ const { owner } = yield* ScheduleOwnerIdentity;
1151
+ const nowMillis = yield* Clock.currentTimeMillis;
1152
+ yield* alarmControl.prearm(nowMillis + limits.recoveryPollMillis);
1153
+ if ((yield* scheduling.runDue(owner)).failed > 0) yield* alarmControl.prearm((yield* Clock.currentTimeMillis) + limits.recoveryPollMillis);
1154
+ else yield* alarmControl.reconcile;
1155
+ }), { mode: "ordered" }).pipe(Effect.asVoid);
1156
+ /**
1157
+ * The host Layer supplies authorization and routing and is cached for the object incarnation.
1158
+ * Cloudflare eviction does not guarantee its finalizers run. Do not acquire resources requiring
1159
+ * cleanup in this Layer; acquire them inside scoped `manage` / `prepare` operations instead.
1160
+ * Native services belong to effect-cf; the database and alarm runtime remain instance-owned.
1161
+ */
1162
+ const makeScheduleOwnerObjectClass = (host, limits = defaultSchedulingLimits) => {
1163
+ const ownerLayer = Layer.effect(ScheduleOwnerIdentity, Effect.gen(function* () {
1164
+ const state = yield* DurableObjectState.DurableObjectState;
1165
+ return ScheduleOwnerIdentity.of({ owner: yield* decodeOwnerName(state.raw.id.name) });
1166
+ }));
1167
+ const sqlLayer = Layer.unwrap(Effect.map(DurableObjectState.DurableObjectState, (state) => SqliteClient.layer({ storage: state.raw.storage })));
1168
+ const application = Layer.merge(Scheduling.layer(limits), ScheduleDriver.layer(limits)).pipe(Layer.provideMerge(scheduleStoreLayer.pipe(Layer.provide(transactionLayer), Layer.provide(sqlLayer))), Layer.provide(admissionLayer.pipe(Layer.provide(CloudflareConversationClient.layer))), Layer.provide(ScheduleWakeNoop), Layer.provide(BrowserCrypto.layer), Layer.provideMerge(DurableObjectAlarm.DurableObjectAlarm.layer), Layer.provide(host), Layer.provideMerge(ownerLayer));
1169
+ const runtime = Layer.effectContext(Effect.gen(function* () {
1170
+ const state = yield* DurableObjectState.DurableObjectState;
1171
+ const scope = yield* Effect.scope;
1172
+ return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
1173
+ }));
1174
+ const Base = DurableObject.make(runtime, {
1175
+ initialize: Effect.void,
1176
+ rpc: { schedule: (encoded) => handleScheduleRequest(encoded) },
1177
+ alarms: scheduleAlarmHandler(limits)
1178
+ });
1179
+ class ScheduleOwnerObject extends Base {
1180
+ alarm(alarmInfo) {
1181
+ return super.alarm?.(alarmInfo);
1182
+ }
1183
+ }
1184
+ return ScheduleOwnerObject;
1185
+ };
1186
+ //#endregion
1187
+ export { CLOUDFLARE_RUNTIME_DEFAULTS as $, decodeHostResponse as A, encodeObservePageRequest as B, SubmitSucceeded as C, decodeApprovalDecisionCommand as D, decodeAbortCommand as E, encodeAbortCommand as F, ConversationMaintenanceFailpoint as G, encodeSubmitRequest as H, encodeApprovalDecisionCommand as I, MaintenancePassReport as J, DurableAlarmError as K, encodeAwaitProgressRequest as L, decodeReceipt as M, decodeSubmitRequest as N, decodeAwaitProgressRequest as O, decodeUnknownResolutionCommand as P, CLOUDFLARE_DATABASE_CAP_BYTES as Q, encodeCancelProgressRequest as R, SubmitRequest as S, boundHostDiagnostic as T, encodeUnknownResolutionCommand as U, encodeReceipt as V, ConversationMaintenance as W, safeCauseMessage as X, safeCauseDiagnostic as Y, AdmissionLimitExceeded as Z, ObservePageRequest as _, ScheduleOwnerProtocolError as a, CloudflareBindingError as at, ProgressObserved as b, ApprovalRecorded as c, DurableObjectContext as ct, CloudflareConversationClient as d, CloudflareAdmissionLimitsValue as et, ConversationClientError as f, HostResponse as g, HostProtocolError as h, ScheduleOwnerNamespace as i, DEFAULT_MAX_DATABASE_BYTES as it, decodeObservePageRequest as j, decodeCancelProgressRequest as k, AwaitProgressRequest as l, conversationNamespaceFromEnv as lt, HostFailure as m, ScheduleAlarmProtocolError as n, CloudflareDurableRuntimeConfigValue as nt, makeScheduleOwnerObjectClass as o, ConversationObjectIdentity as ot, HostFailed as p, DurableAlarmService as q, ScheduleOwnerIdentity as r, CloudflarePlatformConfigError as rt, AbortRecorded as s, ConversationObjectNamespace as st, CloudflareSchedulingClient as t, CloudflareDurableRuntimeConfig as tt, CancelProgressRequest as u, conversationNamespaceLayer as ut, ObservedPage as v, UnknownResolutionRecorded as w, SettlementReached as x, ProgressCancelled as y, encodeHostResponse as z };
1188
+
1189
+ //# sourceMappingURL=scheduling-B-OFqoS9.mjs.map