@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.
package/dist/index.mjs CHANGED
@@ -1,539 +1,18 @@
1
+ import { $ as CLOUDFLARE_RUNTIME_DEFAULTS, A as decodeHostResponse, B as encodeObservePageRequest, C as SubmitSucceeded, D as decodeApprovalDecisionCommand, E as decodeAbortCommand, F as encodeAbortCommand, G as ConversationMaintenanceFailpoint, H as encodeSubmitRequest, I as encodeApprovalDecisionCommand, J as MaintenancePassReport, K as DurableAlarmError, L as encodeAwaitProgressRequest, M as decodeReceipt, N as decodeSubmitRequest, O as decodeAwaitProgressRequest, P as decodeUnknownResolutionCommand, Q as CLOUDFLARE_DATABASE_CAP_BYTES, R as encodeCancelProgressRequest, S as SubmitRequest, T as boundHostDiagnostic, U as encodeUnknownResolutionCommand, V as encodeReceipt, W as ConversationMaintenance, X as safeCauseMessage, Y as safeCauseDiagnostic, Z as AdmissionLimitExceeded, _ as ObservePageRequest, a as ScheduleOwnerProtocolError, at as CloudflareBindingError, b as ProgressObserved, c as ApprovalRecorded, ct as DurableObjectContext, d as CloudflareConversationClient, et as CloudflareAdmissionLimitsValue, f as ConversationClientError, g as HostResponse, h as HostProtocolError, i as ScheduleOwnerNamespace, it as DEFAULT_MAX_DATABASE_BYTES, j as decodeObservePageRequest, k as decodeCancelProgressRequest, l as AwaitProgressRequest, lt as conversationNamespaceFromEnv, m as HostFailure, n as ScheduleAlarmProtocolError, nt as CloudflareDurableRuntimeConfigValue, o as makeScheduleOwnerObjectClass, ot as ConversationObjectIdentity, p as HostFailed, q as DurableAlarmService, r as ScheduleOwnerIdentity, rt as CloudflarePlatformConfigError, s as AbortRecorded, st as ConversationObjectNamespace, t as CloudflareSchedulingClient, tt as CloudflareDurableRuntimeConfig, u as CancelProgressRequest, ut as conversationNamespaceLayer, v as ObservedPage, w as UnknownResolutionRecorded, x as SettlementReached, y as ProgressCancelled, z as encodeHostResponse } from "./scheduling-B-OFqoS9.mjs";
1
2
  import { BrowserQuickActionBrowserBinding, BrowserQuickActionRpcError, BrowserQuickActionWorkersAi, BrowserQuickActionWorkersAiPolicyError, browserQuickActionCaptureLayer, browserQuickActionImplementation, browserQuickActionScreenshotLayer, browserQuickActionWorkersAiCaptureLayer } from "./browser-quick-action.mjs";
2
3
  import { browserRestCaptureImplementation, browserRestCaptureLayer, browserRestWorkersAiCaptureLayer } from "./browser-rest-capture.mjs";
3
- import { BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunInteractiveBinding, BrowserRunInteractiveHost, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, browserRunInteractiveHostLayer, browserRunInteractiveImplementation, browserRunInteractiveLayer } from "./interactive-browser.mjs";
4
+ import { a as BrowserRunInteractiveHost, c as BrowserRunViewport, d as browserRunInteractiveLayer, f as isBrowserRunUndispatchedActionError, i as BrowserRunInteractiveBinding, l as browserRunInteractiveHostLayer, m as BrowserRunSessionLifecycle, n as BrowserRunHandoffResult, o as BrowserRunLiveViewRequest, p as BrowserRunCleanupError, r as BrowserRunHandoffState, s as BrowserRunLiveViewResult, t as BrowserRunHandoffRequest, u as browserRunInteractiveImplementation } from "./interactive-browser-Cy5q-ldl.mjs";
4
5
  import { browserRestCrawlImplementation, browserRestCrawlLayer } from "./browser-rest-crawl.mjs";
5
- import { Clock, Context, Crypto, Deferred, Duration, Effect, Exit, Fiber, Layer, Option, Predicate, PubSub, Queue, Random, Ref, Schema, Semaphore, Stream } from "effect";
6
- import { AbortCommand, AbortIntent, AdmissionConflict, AgentBindingResolver, AppendConflict, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, CanonicalRecordEnvelope, CanonicalSequence, ConversationNotMaterialized, ConversationRead, ConversationStore, ConversationStoreError, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, DigestError, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, DurableRuntimeFailpointError, FenceRejected, IdempotencyKey, IntegrityReport, JoinedToHost, LedgerError, ObligationReport, ObligationThresholds, OperationAuthorizationRequest, OperationAuthorizer, OperationDenied, OwnershipLost, PersistedJson, Principal, ProducerId, Receipt, RecoveryExplanation, RecoveryReport, RetryCommand, RetryRefused, RunJournalError, Settlement, SettlementConflict, SubmissionLedger, SubmissionLookupByKey, ToolReconciler, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WakeScheduler, makeWakeSubscriptionHub, operationAuthorizerLayer } from "@effect-agent/session";
7
- import { ConversationPortTransport, DEFAULT_MAX_STORED_VALUE_BYTES, conversationStoreLayer, decodePortRequest, encodePortResponse, executePortRequest, portTransportFailure, routedConversationStoreLayer, routedSubmissionLedgerLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer } from "@effect-agent/storage-cloudflare";
8
- import { AgentId, AgentInputError, ConversationId, SubmissionId } from "@effect-agent/core";
6
+ import { Clock, Context, Deferred, Duration, Effect, Exit, Fiber, Layer, Option, PubSub, Queue, Ref, Schema, Stream } from "effect";
7
+ import { AgentBindingResolver, AppendConflict, ConversationNotMaterialized, ConversationRead, ConversationStore, ConversationStoreError, DigestError, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, DurableRuntimeFailpointError, FenceRejected, IntegrityReport, LedgerError, ObligationReport, ObligationThresholds, OperationAuthorizationRequest, OperationAuthorizer, OperationDenied, OwnershipLost, PersistedJson, ProducerId, RecoveryExplanation, RecoveryReport, RetryCommand, RetryRefused, RunJournalError, SettlementConflict, SubmissionLedger, SubmissionLookupByKey, ToolReconciler, WakeScheduler, makeWakeSubscriptionHub, operationAuthorizerLayer } from "@effect-agent/session";
8
+ import { ConversationPortTransport, conversationStoreLayer, decodePortRequest, encodePortResponse, executePortRequest, portTransportFailure, routedConversationStoreLayer, routedSubmissionLedgerLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer } from "@effect-agent/storage-cloudflare";
9
+ import { ConversationId, SubmissionId } from "@effect-agent/core";
9
10
  import { CurrentToolFailureObserver, RunContextPreparationPassthrough, toolFailureObserverLayer } from "@effect-agent/engine";
10
11
  import { BrowserCrypto } from "@effect/platform-browser";
11
12
  import { SqliteClient } from "@effect/sql-sqlite-do";
12
- import { DurableObject, DurableObjectState, RpcTracing, WorkerEnvironment } from "effect-cf";
13
+ import { DurableObject, DurableObjectState, WorkerEnvironment } from "effect-cf";
13
14
  import { CodeExecutionHost, CodeExecutionProtocolError, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorStartError, CodeExecutorTerminatedError, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallLimitError, CodeHostCallResult, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, SandboxImplementation } from "@effect-agent/sandbox";
14
15
  import { RpcTarget } from "cloudflare:workers";
15
- //#region src/bindings.ts
16
- /**
17
- * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
18
- * are supplied as Effect services/Layers"). Application code never reads `env` or touches a
19
- * `DurableObjectState` directly — the Conversation Object class constructs these Layers once
20
- * per incarnation and everything downstream consumes the services.
21
- */
22
- /** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
23
- var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindingError", {
24
- binding: Schema.String,
25
- message: Schema.String
26
- }) {};
27
- /**
28
- * The `DurableObjectNamespace` binding that addresses Conversation Objects. The Object
29
- * identity rule is `namespace.idFromName(conversationId)` (plan §1.2): Conversation IDs are
30
- * globally unique, so the mapping is total and deterministic and no directory service exists.
31
- */
32
- var ConversationObjectNamespace = class ConversationObjectNamespace extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectNamespace") {
33
- static layer(namespace) {
34
- return Layer.succeed(ConversationObjectNamespace)({ namespace });
35
- }
36
- };
37
- /**
38
- * Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
39
- * and a namespace binding is a host object no Schema can decode, so this is the documented
40
- * narrowest-boundary check (structural probe for the namespace surface the transport uses);
41
- * a missing or misshaped binding fails typed before any Layer is built.
42
- */
43
- const conversationNamespaceFromEnv = Effect.fn("conversationNamespaceFromEnv")(function* (env, binding) {
44
- if (!Predicate.isObjectKeyword(env)) return yield* CloudflareBindingError.make({
45
- binding,
46
- message: "The Worker environment is not an object; no bindings are available."
47
- });
48
- const candidate = yield* Effect.try({
49
- try: () => {
50
- const value = Reflect.get(env, binding);
51
- if (!Predicate.isObjectKeyword(value)) return void 0;
52
- const idFromName = Reflect.get(value, "idFromName");
53
- const get = Reflect.get(value, "get");
54
- return typeof idFromName === "function" && typeof get === "function" ? value : void 0;
55
- },
56
- catch: () => CloudflareBindingError.make({
57
- binding,
58
- message: `env.${binding} could not be inspected as a DurableObjectNamespace binding.`
59
- })
60
- });
61
- if (candidate !== void 0) return candidate;
62
- return yield* CloudflareBindingError.make({
63
- binding,
64
- message: `env.${binding} is not a DurableObjectNamespace binding; declare the Conversation Object class under this binding in the Worker configuration.`
65
- });
66
- });
67
- /**
68
- * Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
69
- * receiver also opts into the effect-cf native RPC trace-context contract.
70
- */
71
- const conversationNamespaceLayer = (env, binding, options = {}) => Layer.effect(ConversationObjectNamespace)(Effect.map(conversationNamespaceFromEnv(env, binding), (namespace) => ({
72
- namespace,
73
- ...options.rpcTracing === true ? { rpcTracing: binding } : {}
74
- })));
75
- /**
76
- * The live Durable Object execution context of THIS incarnation. Only Layer construction and
77
- * the alarm service consume it; important state never lives on it (`ctx.storage` is truth,
78
- * everything in memory is a cache — deployment spec §11).
79
- */
80
- var DurableObjectContext = class DurableObjectContext extends Context.Service()("@effect-agent/platform-cloudflare/DurableObjectContext") {
81
- static layer(ctx, env) {
82
- return Layer.succeed(DurableObjectContext)({
83
- ctx,
84
- env
85
- });
86
- }
87
- };
88
- /**
89
- * The Conversation identity this Object serializes and the producer identity its Attempts
90
- * write with (`{producerPrefix}:{conversationId}`, plan §1.4). Derived once per incarnation
91
- * from `ctx.id.name` — the Object identity rule guarantees the name IS the Conversation ID.
92
- */
93
- var ConversationObjectIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectIdentity") {};
94
- //#endregion
95
- //#region src/config.ts
96
- /**
97
- * Schema-validated configuration for the Cloudflare durable runtime (deployment spec §4:
98
- * decoded once during Layer construction, exposed as a typed service; DEPLOY-003). Every
99
- * cadence is in milliseconds; every bound is finite and checked before any resource opens.
100
- */
101
- const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
102
- const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
103
- /** The supplied Cloudflare durable runtime configuration failed validation (DEPLOY-003). */
104
- var CloudflarePlatformConfigError = class extends Schema.TaggedError()("CloudflarePlatformConfigError", {
105
- message: Schema.String,
106
- cause: Schema.optionalKey(Schema.Defect())
107
- }) {};
108
- /**
109
- * An admission was refused by a host resource limit BEFORE any ledger row existed
110
- * (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
111
- * behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
112
- * `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
113
- * Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
114
- * nothing was admitted or written.
115
- */
116
- var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
117
- limit: Schema.Literals([
118
- "queue-depth",
119
- "input-bytes",
120
- "database-bytes"
121
- ]),
122
- actual: Schema.Int,
123
- maximum: Schema.Int
124
- }) {
125
- get message() {
126
- 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).`;
127
- }
128
- };
129
- /** The platform's hard per-Object database cap (10 GB, developers.cloudflare.com limits). */
130
- const CLOUDFLARE_DATABASE_CAP_BYTES = 1e10;
131
- /** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
132
- const DEFAULT_MAX_DATABASE_BYTES = 9e9;
133
- /**
134
- * Explicit bounded admission quotas checked by the Conversation Object BEFORE admission
135
- * (exit gate "resource limits are checked before admission").
136
- */
137
- var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue")({
138
- /** Maximum nonterminal Submissions per Conversation lane before new admissions refuse. */
139
- maxQueueDepthPerLane: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1e5)),
140
- /** Maximum encoded input bytes; never above the storage per-value bound. */
141
- maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
142
- /** Maximum `ctx.storage.sql.databaseSize` at admission; stays under the 10 GB platform cap. */
143
- maxDatabaseBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(CLOUDFLARE_DATABASE_CAP_BYTES))
144
- }) {};
145
- /**
146
- * Validated Cloudflare durable runtime configuration. The producer identity of one
147
- * Conversation Object is `{producerPrefix}:{conversationId}` — stable across incarnations of
148
- * the same deployment, distinct across deployments — and producer-epoch fencing (not the
149
- * producer name) remains the correctness authority (DUR-006).
150
- */
151
- var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue")({
152
- deploymentId: DeploymentId,
153
- /** Head of the minted producer identity `{producerPrefix}:{conversationId}`. */
154
- producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
155
- /** Submission ownership lease duration (D5); fences work across Object incarnations. */
156
- ownershipLeaseDuration: PositiveMillis,
157
- /** Base delay of the alarm re-arm backoff when a pass makes no progress. */
158
- alarmBackoffBase: PositiveMillis,
159
- /** Ceiling of the alarm re-arm backoff. */
160
- alarmBackoffCap: PositiveMillis,
161
- /**
162
- * The maintenance-pass scan cadence and the ceiling of every re-arm delay: nonterminal
163
- * work is revisited at least this often (wake/scan pairing, persistence §14).
164
- */
165
- wakeScanInterval: PositiveMillis,
166
- /** `awaitSettlement` ledger re-check cadence when no wake arrives. */
167
- settlementPollInterval: PositiveMillis,
168
- /** Worker ownership-lease renewal cadence during an active Attempt. */
169
- leaseRenewalInterval: PositiveMillis,
170
- /** Active-Run abort-intent poll cadence. */
171
- abortPollInterval: PositiveMillis,
172
- /** Canonical observation poll cadence of the Durable Object store. */
173
- observationPollInterval: NonNegativeMillis,
174
- /** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
175
- maxStoredValueBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
176
- /** Opt-in full payload/digest-chain audit while opening the store. */
177
- verifyOnOpen: Schema.Boolean,
178
- limits: CloudflareAdmissionLimitsValue
179
- }) {};
180
- /** Explicit configuration authority for the assembled Cloudflare durable runtime. */
181
- var CloudflareDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {};
182
- /** Documented production defaults applied by `CloudflareDurableRuntime.layer`. */
183
- const CLOUDFLARE_RUNTIME_DEFAULTS = {
184
- ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
185
- alarmBackoffBase: 100,
186
- alarmBackoffCap: 5e3,
187
- wakeScanInterval: 1e3,
188
- settlementPollInterval: 500,
189
- leaseRenewalInterval: 1e4,
190
- abortPollInterval: 500,
191
- observationPollInterval: 25,
192
- maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
193
- verifyOnOpen: false,
194
- maxQueueDepthPerLane: 256,
195
- maxInputBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
196
- maxDatabaseBytes: DEFAULT_MAX_DATABASE_BYTES
197
- };
198
- //#endregion
199
- //#region src/boundary.ts
200
- const MAX_FOREIGN_DIAGNOSTIC_LENGTH = 8192;
201
- const boundForeignDiagnostic = (message) => message.slice(0, MAX_FOREIGN_DIAGNOSTIC_LENGTH);
202
- /** Render a foreign failure without trusting accessors or coercion hooks on the value. */
203
- const safeCauseMessage = (cause, fallback) => {
204
- try {
205
- const message = cause instanceof Error ? cause.message : cause;
206
- return boundForeignDiagnostic(typeof message === "string" ? message : String(message));
207
- } catch {
208
- return boundForeignDiagnostic(fallback);
209
- }
210
- };
211
- /** Include an Error name when worker-failure classification needs it. */
212
- const safeCauseDiagnostic = (cause, fallback) => {
213
- try {
214
- return cause instanceof Error ? boundForeignDiagnostic(`${cause.name}: ${cause.message}`) : safeCauseMessage(cause, fallback);
215
- } catch {
216
- return boundForeignDiagnostic(fallback);
217
- }
218
- };
219
- /** Read Cloudflare RPC classifications without letting a hostile proxy defect the client. */
220
- const cloudflareFailureSignals = (cause) => {
221
- if (!Predicate.isObjectKeyword(cause)) return {};
222
- try {
223
- const retryableValue = Reflect.get(cause, "retryable");
224
- const overloadedValue = Reflect.get(cause, "overloaded");
225
- const resetValue = Reflect.get(cause, "durableObjectReset");
226
- const retryable = typeof retryableValue === "boolean" ? retryableValue : resetValue === true ? true : void 0;
227
- const overloaded = typeof overloadedValue === "boolean" ? overloadedValue : void 0;
228
- return {
229
- ...retryable === void 0 ? {} : { retryable },
230
- ...overloaded === void 0 ? {} : { overloaded }
231
- };
232
- } catch {
233
- return {};
234
- }
235
- };
236
- //#endregion
237
- //#region src/alarm.ts
238
- /**
239
- * The single multiplexed Durable Object alarm (decision D-P6-2). A Durable Object has ONE
240
- * alarm slot; every cadence the Node host ran on fibers (wake scan, lease expiry, settlement
241
- * and abort re-checks, retry backoff) multiplexes into one idempotent maintenance pass, and
242
- * the slot always holds the EARLIEST deadline any caller asked for.
243
- *
244
- * The alarm invariant (plan §1.4): every committed actionable mutation carries a newer durable
245
- * maintenance generation and a committed alarm. Stable externally-driven waits may be
246
- * nonterminal without retaining an alarm; their resolving mutation advances the generation and
247
- * restores the alarm atomically.
248
- */
249
- /** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */
250
- var DurableAlarmError = class extends Schema.TaggedError()("DurableAlarmError", {
251
- operation: Schema.String,
252
- message: Schema.String,
253
- cause: Schema.optionalKey(Schema.Defect())
254
- }) {};
255
- const alarmFailure = (operation) => (cause) => DurableAlarmError.make({
256
- operation,
257
- message: safeCauseMessage(cause, "The Cloudflare alarm API failed without a diagnostic"),
258
- cause
259
- });
260
- /** `ctx.storage` alarm slot as an Effect service; storage is truth, never a memory field. */
261
- var DurableAlarmService = class DurableAlarmService extends Context.Service()("@effect-agent/platform-cloudflare/DurableAlarmService") {
262
- static layer = Layer.effect(DurableAlarmService)(Effect.gen(function* () {
263
- const { ctx } = yield* DurableObjectContext;
264
- /**
265
- * In-memory pass bookkeeping — a pure CACHE, never state: a fresh incarnation has no
266
- * running pass, and a deferred wake lost to eviction was only ever a promptness hint
267
- * on top of the already-committed pre-armed alarm.
268
- */
269
- const runningPasses = yield* Ref.make(0);
270
- const scheduled = Effect.tryPromise({
271
- try: () => ctx.storage.getAlarm(),
272
- catch: alarmFailure("get alarm")
273
- }).pipe(Effect.map((deadline) => deadline === null ? Option.none() : Option.some(deadline)));
274
- const scheduleAt = (epochMillis) => Effect.tryPromise({
275
- try: () => ctx.storage.setAlarm(epochMillis),
276
- catch: alarmFailure("set alarm")
277
- });
278
- const ensureScheduledBy = (epochMillis) => scheduled.pipe(Effect.flatMap((existing) => Option.isSome(existing) && existing.value <= epochMillis ? Effect.void : scheduleAt(epochMillis)));
279
- const armNow = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => ensureScheduledBy(now)));
280
- const scheduleNow = Ref.get(runningPasses).pipe(Effect.flatMap((passes) => passes > 0 ? Effect.void : armNow));
281
- const withWakesDeferred = (body) => Ref.update(runningPasses, (passes) => passes + 1).pipe(Effect.andThen(body), Effect.ensuring(Ref.update(runningPasses, (passes) => passes - 1)));
282
- const cancel = Effect.tryPromise({
283
- try: () => ctx.storage.deleteAlarm(),
284
- catch: alarmFailure("delete alarm")
285
- });
286
- return DurableAlarmService.of({
287
- scheduled,
288
- scheduleAt,
289
- ensureScheduledBy,
290
- scheduleNow,
291
- withWakesDeferred,
292
- cancel
293
- });
294
- }));
295
- };
296
- /** What one maintenance pass did — auditable evidence mirroring `NodeDurableHost`'s report. */
297
- var MaintenancePassReport = class extends Schema.Class("@effect-agent/platform-cloudflare/MaintenancePassReport")({
298
- /** `caught-up` is generation-only; `actionable` ran recovery and one bounded drain. */
299
- phase: Schema.Literals(["caught-up", "actionable"]),
300
- /** Recovery decisions executed (or deferred) BEFORE any new claim in this pass. */
301
- recovered: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
302
- /** Settlements the drain pass finalized. */
303
- settled: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
304
- /** Submissions still nonterminal after the pass (suspended/unknown lanes stay honest). */
305
- nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
306
- /** `rearmed` for dirty/autonomous work, `cleared` for stable waits or settlement. */
307
- alarm: Schema.Literals(["rearmed", "cleared"])
308
- }) {};
309
- /** Test-only fault authority; production uses the inert layer. */
310
- var ConversationMaintenanceFailpoint = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationMaintenanceFailpoint") {
311
- static layer = Layer.succeed(this)({ hit: () => Effect.void });
312
- };
313
- const MaintenanceGeneration = Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n));
314
- /** Versioned, platform-private maintenance state stored through Durable Object KV. */
315
- var ConversationMaintenanceState = class extends Schema.Class("@effect-agent/platform-cloudflare/ConversationMaintenanceState")({
316
- schemaVersion: Schema.Literal(1),
317
- dirty: MaintenanceGeneration,
318
- processed: MaintenanceGeneration,
319
- nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
320
- }) {};
321
- const MAINTENANCE_STATE_KEY = "effect-agent:conversation-maintenance:v1";
322
- const decodeMaintenanceState = Schema.decodeUnknownSync(ConversationMaintenanceState);
323
- const encodeMaintenanceState = Schema.encodeSync(ConversationMaintenanceState);
324
- const initialMaintenanceState = () => ConversationMaintenanceState.make({
325
- schemaVersion: 1,
326
- dirty: 1n,
327
- processed: 0n,
328
- nonterminal: 0
329
- });
330
- const readMaintenanceState = async (transaction) => {
331
- const encoded = await transaction.get(MAINTENANCE_STATE_KEY);
332
- return encoded === void 0 ? {
333
- state: initialMaintenanceState(),
334
- initialized: false
335
- } : {
336
- state: decodeMaintenanceState(encoded),
337
- initialized: true
338
- };
339
- };
340
- const ensureTransactionAlarmBy = async (transaction, deadline) => {
341
- const scheduled = await transaction.getAlarm();
342
- if (scheduled === null || scheduled > deadline) await transaction.setAlarm(deadline);
343
- };
344
- const stableExternalWait = (snapshot, reports) => {
345
- const decision = reports.get(snapshot.submissionId)?.decision._tag;
346
- if (decision === "SettleAborted") return false;
347
- switch (snapshot.state) {
348
- case "suspended":
349
- case "joined": return true;
350
- case "unknown": return decision === "AwaitUnknownResolution" || decision === "MarkUnknown";
351
- case "admitted": return reports.get(snapshot.submissionId)?.decision._tag === "AwaitParentEstablishment";
352
- case "input-applied":
353
- case "joining":
354
- case "ready":
355
- case "running":
356
- case "settled":
357
- case "terminalizing": return false;
358
- }
359
- };
360
- /**
361
- * Incremental, quiescent maintenance over a durable dirty/processed generation (issue #93).
362
- *
363
- * `pass` = generation snapshot/pre-arm → recovery → bounded drain → generation acknowledgement:
364
- *
365
- * 1. One storage transaction reads dirty/processed and re-arms before work. A caught-up forced
366
- * alarm takes an O(1) path without recovery, ledger scans, or canonical-history reads.
367
- * 2. Recovery still strictly precedes a new claim, and one bounded drain advances the lane.
368
- * 3. The final transaction acknowledges only the generation observed at pass start. A racing
369
- * mutation therefore remains `dirty > processed` and retains its atomically-established alarm.
370
- * 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease
371
- * recovery states leave their generation dirty and retain bounded backoff rearming.
372
- */
373
- var ConversationMaintenance = class ConversationMaintenance extends Context.Service()("@effect-agent/platform-cloudflare/ConversationMaintenance") {
374
- static layer = Layer.effect(ConversationMaintenance)(Effect.gen(function* () {
375
- const runtime = yield* DurableAgentRuntime;
376
- const resolver = yield* AgentBindingResolver;
377
- const ledger = yield* SubmissionLedger;
378
- const alarm = yield* DurableAlarmService;
379
- const config = yield* CloudflareDurableRuntimeConfig;
380
- const identity = yield* ConversationObjectIdentity;
381
- const { ctx } = yield* DurableObjectContext;
382
- const failpoint = yield* ConversationMaintenanceFailpoint;
383
- /**
384
- * Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
385
- * restarts at zero and merely re-arms sooner than a long-lived one would have.
386
- */
387
- const stalls = yield* Ref.make(0);
388
- /**
389
- * Incarnation-local mutation count guarded with the generation transactions below. It is
390
- * deliberately not durable: after eviction every begun mutation has stopped, while its
391
- * pre-armed dirty generation remains durable for recovery. The short gate never spans the
392
- * caller's mutation or cross-Object I/O.
393
- */
394
- const activeMutations = yield* Ref.make(0);
395
- const generationGate = yield* Semaphore.make(1);
396
- const maintenancePassGate = yield* Semaphore.make(1);
397
- const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));
398
- const runTransaction = (operation, transaction) => Effect.tryPromise({
399
- try: transaction,
400
- catch: alarmFailure(operation)
401
- });
402
- const beginMutation = Effect.fn("ConversationMaintenance.beginMutation")(function* () {
403
- yield* failpoint.hit("maintenance:dirty:before");
404
- const now = yield* Clock.currentTimeMillis;
405
- yield* runTransaction("advance maintenance generation", () => ctx.storage.transaction(async (transaction) => {
406
- const { state } = await readMaintenanceState(transaction);
407
- const next = ConversationMaintenanceState.make({
408
- ...state,
409
- dirty: state.dirty + 1n
410
- });
411
- await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
412
- await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
413
- }));
414
- yield* failpoint.hit("maintenance:dirty:after");
415
- yield* Ref.update(activeMutations, (active) => active + 1);
416
- });
417
- const endMutation = generationGate.withPermit(Ref.update(activeMutations, (active) => Math.max(0, active - 1)));
418
- 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);
419
- const ensureAlarm = Effect.fn("ConversationMaintenance.ensureAlarm")(function* () {
420
- yield* failpoint.hit("maintenance:ensure:before");
421
- const now = yield* Clock.currentTimeMillis;
422
- yield* runTransaction("ensure maintenance alarm", () => ctx.storage.transaction(async (transaction) => {
423
- const { state, initialized } = await readMaintenanceState(transaction);
424
- if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
425
- if (state.dirty > state.processed) await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);
426
- }));
427
- yield* failpoint.hit("maintenance:ensure:after");
428
- });
429
- const beginPass = Effect.fn("ConversationMaintenance.beginPass")(function* () {
430
- yield* failpoint.hit("maintenance:begin:before");
431
- const now = yield* Clock.currentTimeMillis;
432
- const result = yield* runTransaction("begin maintenance pass", () => ctx.storage.transaction(async (transaction) => {
433
- const { state, initialized } = await readMaintenanceState(transaction);
434
- if (!initialized) await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));
435
- if (state.processed >= state.dirty) {
436
- await transaction.deleteAlarm();
437
- return {
438
- _tag: "CaughtUp",
439
- nonterminal: state.nonterminal
440
- };
441
- }
442
- await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);
443
- return {
444
- _tag: "Actionable",
445
- generation: state.dirty
446
- };
447
- }));
448
- yield* failpoint.hit("maintenance:begin:after");
449
- return result;
450
- });
451
- const rearmDelay = Effect.fn("ConversationMaintenance.rearmDelay")(function* (progressed) {
452
- const priorStalls = yield* Ref.getAndUpdate(stalls, (count) => progressed ? 0 : count + 1);
453
- if (progressed) return config.alarmBackoffBase;
454
- const exponent = Math.min(priorStalls, 30);
455
- const backoff = Math.min(config.alarmBackoffCap, config.alarmBackoffBase * 2 ** exponent);
456
- const jitter = yield* Random.next;
457
- const jittered = Math.ceil(backoff / 2 + backoff / 2 * jitter);
458
- return Math.min(jittered, config.wakeScanInterval);
459
- });
460
- const pass = Effect.fn("ConversationMaintenance.pass")(function* () {
461
- const annotate = (report) => Effect.annotateCurrentSpan({
462
- phase: report.phase,
463
- recovered: report.recovered,
464
- settled: report.settled,
465
- nonterminal: report.nonterminal,
466
- alarm: report.alarm
467
- }).pipe(Effect.as(report));
468
- const started = yield* generationGate.withPermit(Effect.gen(function* () {
469
- const activeAtStart = yield* Ref.get(activeMutations);
470
- return {
471
- ...yield* beginPass(),
472
- activeAtStart
473
- };
474
- }));
475
- if (started._tag === "CaughtUp") return yield* annotate(MaintenancePassReport.make({
476
- phase: "caught-up",
477
- recovered: 0,
478
- settled: 0,
479
- nonterminal: started.nonterminal,
480
- alarm: "cleared"
481
- }));
482
- const recovered = yield* runtime.runRecovery;
483
- const settlements = yield* runtime.processConversationResolved(identity.conversationId).pipe(Effect.provideService(AgentBindingResolver, resolver));
484
- const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
485
- const reports = new Map(recovered.map((report) => [report.submissionId, report]));
486
- const head = remaining[0];
487
- const headWaiting = head !== void 0 && stableExternalWait(head, reports);
488
- const autonomous = remaining.some((snapshot, index) => {
489
- if (index > 0 && headWaiting && snapshot.state === "ready" && reports.get(snapshot.submissionId)?.decision._tag === "ApplyInput") return false;
490
- return !stableExternalWait(snapshot, reports);
491
- });
492
- const progressed = settlements.length > 0 || recovered.some((report) => report.disposition === "repaired");
493
- const delay = autonomous ? yield* rearmDelay(progressed) : 0;
494
- const now = yield* Clock.currentTimeMillis;
495
- yield* failpoint.hit("maintenance:finish:before");
496
- const alarmDisposition = yield* generationGate.withPermit(Effect.gen(function* () {
497
- const active = yield* Ref.get(activeMutations);
498
- return yield* runTransaction("finish maintenance pass", () => ctx.storage.transaction(async (transaction) => {
499
- const { state } = await readMaintenanceState(transaction);
500
- const processed = autonomous || started.activeAtStart > 0 || active > 0 ? state.processed : state.processed > started.generation ? state.processed : started.generation;
501
- const next = ConversationMaintenanceState.make({
502
- ...state,
503
- processed,
504
- nonterminal: remaining.length
505
- });
506
- await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));
507
- if (autonomous) {
508
- await transaction.setAlarm(now + delay);
509
- return "rearmed";
510
- }
511
- if (started.activeAtStart > 0 || active > 0 || next.dirty > next.processed) {
512
- await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);
513
- return "rearmed";
514
- }
515
- await transaction.deleteAlarm();
516
- return "cleared";
517
- }));
518
- }));
519
- yield* failpoint.hit("maintenance:finish:after");
520
- if (alarmDisposition === "cleared") yield* Ref.set(stalls, 0);
521
- return yield* annotate(MaintenancePassReport.make({
522
- phase: "actionable",
523
- recovered: recovered.length,
524
- settled: settlements.length,
525
- nonterminal: remaining.length,
526
- alarm: alarmDisposition
527
- }));
528
- });
529
- return ConversationMaintenance.of({
530
- pass: alarm.withWakesDeferred(maintenancePassGate.withPermit(pass())),
531
- ensureAlarm: ensureAlarm(),
532
- withMutation
533
- });
534
- }));
535
- };
536
- //#endregion
537
16
  //#region src/wake-scheduler.ts
538
17
  /**
539
18
  * Bounded in-memory wake buffer for same-incarnation `awaitSettlement` subscribers. Wake
@@ -798,301 +277,6 @@ var CloudflareDurableRuntime = class {
798
277
  }
799
278
  };
800
279
  //#endregion
801
- //#region src/client.ts
802
- /**
803
- * The Worker↔Conversation-Object host protocol (plan §1.4): Schema envelopes for the host
804
- * entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
805
- * `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
806
- * it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
807
- * failures travel as their own tagged classes and RE-DECODE to identical tags on the caller
808
- * (error-tag fidelity), and protocol anomalies are answered typed instead of thrown.
809
- */
810
- /** Ceiling for host protocol diagnostic strings. */
811
- const MAX_HOST_DIAGNOSTIC_LENGTH = 4096;
812
- const BoundedDiagnostic = Schema.String.check(Schema.isMaxLength(MAX_HOST_DIAGNOSTIC_LENGTH));
813
- /** Truncate a diagnostic string to the host protocol's bounded length. */
814
- const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH ? `${value.slice(0, MAX_HOST_DIAGNOSTIC_LENGTH - 3)}...` : value;
815
- /**
816
- * The envelope itself could not be honored: the Object could not decode the request, or a
817
- * response could not be encoded/decoded. Never carries operation semantics.
818
- */
819
- var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
820
- /** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
821
- var ConversationClientError = class extends Schema.TaggedError()("ConversationClientError", {
822
- conversationId: Schema.String,
823
- message: Schema.String,
824
- cause: Schema.optionalKey(Schema.Defect()),
825
- /** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
826
- retryable: Schema.optionalKey(Schema.Boolean),
827
- /** Cloudflare overloads are surfaced immediately instead of adding retry pressure. */
828
- overloaded: Schema.optionalKey(Schema.Boolean)
829
- }) {};
830
- /**
831
- * One durable submission, input ALREADY encoded by the caller through the Agent Binding's
832
- * input schema (the Worker bundles the same Agent definitions as the Object, so schema
833
- * validation happens client-side; the resolved Binding re-validates at claim time). The
834
- * Conversation identity is deliberately absent — the addressed Object IS the lane.
835
- */
836
- var SubmitRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/SubmitRequest")({
837
- agentId: AgentId,
838
- principal: Principal,
839
- idempotencyKey: IdempotencyKey,
840
- definitions: DefinitionDigests,
841
- inputPayload: PersistedJson
842
- }) {};
843
- /** One bounded page of canonical records after an optional sequence. */
844
- var ObservePageRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/ObservePageRequest")({
845
- afterSequence: Schema.optionalKey(CanonicalSequence),
846
- limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1024))
847
- }) {};
848
- /** One event-driven wait for canonical progress strictly after this sequence. */
849
- var AwaitProgressRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AwaitProgressRequest")({
850
- afterSequence: CanonicalSequence,
851
- waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256))
852
- }) {};
853
- /** Best-effort cancellation of one in-flight progress RPC. */
854
- var CancelProgressRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/CancelProgressRequest")({ waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)) }) {};
855
- /**
856
- * Every typed failure a host entry point can produce, plus the protocol's own errors. Same
857
- * closed-union discipline as the WP2 `PortFailure`: members re-decode to the SAME tagged
858
- * classes on the Worker side; `cause` chains travel as Schema defects without instance
859
- * fidelity (plan §2.8).
860
- */
861
- const HostFailure = Schema.Union([
862
- AgentInputError,
863
- DigestError,
864
- AdmissionConflict,
865
- SettlementConflict,
866
- ApprovalConflict,
867
- UnknownResolutionConflict,
868
- JoinedToHost,
869
- LedgerError,
870
- ConversationStoreError,
871
- ConversationNotMaterialized,
872
- AppendConflict,
873
- FenceRejected,
874
- DurableRuntimeFailpointError,
875
- AdmissionLimitExceeded,
876
- DurableAlarmError,
877
- OperationDenied,
878
- HostProtocolError
879
- ]);
880
- var SubmitSucceeded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SubmitSucceeded")("SubmitSucceeded", { receipt: Receipt }) {};
881
- var SettlementReached = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SettlementReached")("SettlementReached", { settlement: Settlement }) {};
882
- var ObservedPage = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ObservedPage")("ObservedPage", { records: Schema.Array(CanonicalRecordEnvelope).check(Schema.isMaxLength(1024)) }) {};
883
- /** A record was already committed or an incarnation-local hint says the caller should re-read. */
884
- var ProgressObserved = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ProgressObserved")("ProgressObserved", {}) {};
885
- var ProgressCancelled = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ProgressCancelled")("ProgressCancelled", {}) {};
886
- var AbortRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/AbortRecorded")("AbortRecorded", { intent: AbortIntent }) {};
887
- var ApprovalRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ApprovalRecorded")("ApprovalRecorded", { intent: ApprovalDecisionIntent }) {};
888
- var UnknownResolutionRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/UnknownResolutionRecorded")("UnknownResolutionRecorded", { intent: UnknownResolutionIntent }) {};
889
- /** The entry point failed TYPED on the Object; the failure re-decodes verbatim. */
890
- var HostFailed = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/HostFailed")("HostFailed", { failure: HostFailure }) {};
891
- /** The uniform answer of one host entry point. Callers narrow by the tag their call implies. */
892
- const HostResponse = Schema.Union([
893
- SubmitSucceeded,
894
- SettlementReached,
895
- ObservedPage,
896
- ProgressObserved,
897
- ProgressCancelled,
898
- AbortRecorded,
899
- ApprovalRecorded,
900
- UnknownResolutionRecorded,
901
- HostFailed
902
- ]);
903
- const decodeSubmitRequest = Schema.decodeUnknownEffect(SubmitRequest);
904
- const encodeSubmitRequest = Schema.encodeEffect(SubmitRequest);
905
- const decodeReceipt = Schema.decodeUnknownEffect(Receipt);
906
- const encodeReceipt = Schema.encodeEffect(Receipt);
907
- const decodeObservePageRequest = Schema.decodeUnknownEffect(ObservePageRequest);
908
- const encodeObservePageRequest = Schema.encodeEffect(ObservePageRequest);
909
- const decodeAwaitProgressRequest = Schema.decodeUnknownEffect(AwaitProgressRequest);
910
- const encodeAwaitProgressRequest = Schema.encodeEffect(AwaitProgressRequest);
911
- const decodeCancelProgressRequest = Schema.decodeUnknownEffect(CancelProgressRequest);
912
- const encodeCancelProgressRequest = Schema.encodeEffect(CancelProgressRequest);
913
- const decodeAbortCommand = Schema.decodeUnknownEffect(AbortCommand);
914
- const encodeAbortCommand = Schema.encodeEffect(AbortCommand);
915
- const decodeApprovalDecisionCommand = Schema.decodeUnknownEffect(ApprovalDecisionCommand);
916
- const encodeApprovalDecisionCommand = Schema.encodeEffect(ApprovalDecisionCommand);
917
- const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolutionCommand);
918
- const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
919
- const encodeHostResponse = Schema.encodeEffect(HostResponse);
920
- const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
921
- /** Failure surface of `CloudflareConversationClient.submit`. */
922
- const ClientSubmitHostFailure = Schema.Union([
923
- AgentInputError,
924
- DigestError,
925
- AdmissionConflict,
926
- LedgerError,
927
- ConversationStoreError,
928
- ConversationNotMaterialized,
929
- AppendConflict,
930
- FenceRejected,
931
- DurableRuntimeFailpointError,
932
- AdmissionLimitExceeded,
933
- DurableAlarmError,
934
- HostProtocolError
935
- ]);
936
- const ClientAwaitHostFailure = Schema.Union([
937
- LedgerError,
938
- SettlementConflict,
939
- HostProtocolError
940
- ]);
941
- const ClientObserveHostFailure = Schema.Union([
942
- ConversationStoreError,
943
- ConversationNotMaterialized,
944
- OperationDenied,
945
- HostProtocolError
946
- ]);
947
- const ClientAbortHostFailure = Schema.Union([
948
- LedgerError,
949
- SettlementConflict,
950
- JoinedToHost,
951
- DurableRuntimeFailpointError,
952
- DurableAlarmError,
953
- HostProtocolError
954
- ]);
955
- const ClientApprovalHostFailure = Schema.Union([
956
- LedgerError,
957
- SettlementConflict,
958
- ApprovalConflict,
959
- OperationDenied,
960
- DurableAlarmError,
961
- HostProtocolError
962
- ]);
963
- const ClientUnknownHostFailure = Schema.Union([
964
- LedgerError,
965
- SettlementConflict,
966
- UnknownResolutionConflict,
967
- JoinedToHost,
968
- DurableRuntimeFailpointError,
969
- OperationDenied,
970
- DurableAlarmError,
971
- HostProtocolError
972
- ]);
973
- const outOfContract = (conversationId, operation, observed) => ConversationClientError.make({
974
- conversationId,
975
- message: boundHostDiagnostic(`The Conversation Object answered ${operation} with the out-of-contract ${observed}.`)
976
- });
977
- const hostRpcMethods = {
978
- submit: "submitEncoded",
979
- awaitSettlement: "awaitSettlementEncoded",
980
- awaitProgress: "awaitProgressEncoded",
981
- cancelProgress: "cancelProgressEncoded",
982
- observePage: "observePage",
983
- abort: "abortEncoded",
984
- resolveApproval: "resolveApprovalEncoded",
985
- resolveUnknown: "resolveUnknownEncoded"
986
- };
987
- /** Worker-side client over the Conversation Object namespace (DEPLOY-010). */
988
- var CloudflareConversationClient = class CloudflareConversationClient extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareConversationClient") {
989
- static layer = Layer.effect(CloudflareConversationClient)(Effect.gen(function* () {
990
- const { namespace, rpcTracing } = yield* ConversationObjectNamespace;
991
- const crypto = yield* Crypto.Crypto;
992
- const call = Effect.fn(function* (conversationId, operation, encoded) {
993
- const traceArgs = rpcTracing === void 0 ? [] : yield* RpcTracing.withRpcTraceContext([]);
994
- const raw = yield* Effect.tryPromise({
995
- try: () => {
996
- return namespace.get(namespace.idFromName(conversationId))[hostRpcMethods[operation]](encoded, ...traceArgs);
997
- },
998
- catch: (cause) => ConversationClientError.make({
999
- conversationId,
1000
- message: boundHostDiagnostic(`${operation} did not reach the Conversation Object: ${safeCauseMessage(cause, "the RPC failed without a diagnostic")}`),
1001
- cause,
1002
- ...cloudflareFailureSignals(cause)
1003
- })
1004
- });
1005
- return yield* decodeHostResponse(raw).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`The ${operation} answer could not be decoded: ${error.message}`) })));
1006
- }, (effect, conversationId, operation) => rpcTracing === void 0 ? Effect.withSpan(effect, "CloudflareConversationClient.call", { attributes: {
1007
- conversationId,
1008
- operation
1009
- } }) : RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]));
1010
- const expect = (conversationId, operation, resultSchema, failureSchema) => {
1011
- const isExpectedResult = Schema.is(resultSchema);
1012
- const isExpectedFailure = Schema.is(failureSchema);
1013
- return (response) => {
1014
- if (response._tag === "HostFailed") {
1015
- const failure = response.failure;
1016
- return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(conversationId, operation, `failure ${failure._tag}`));
1017
- }
1018
- if (!isExpectedResult(response)) return Effect.fail(outOfContract(conversationId, operation, `result ${response._tag}`));
1019
- return Effect.succeed(response);
1020
- };
1021
- };
1022
- const readPage = (conversationId, options) => Effect.gen(function* () {
1023
- const request = ObservePageRequest.make({
1024
- ...options?.afterSequence === void 0 ? {} : { afterSequence: options.afterSequence },
1025
- limit: options?.limit ?? 256
1026
- });
1027
- const encoded = yield* encodeObservePageRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`observePage request encode failed: ${error.message}`) })));
1028
- const response = yield* call(conversationId, "observePage", encoded);
1029
- return (yield* expect(conversationId, "observePage", ObservedPage, ClientObserveHostFailure)(response)).records;
1030
- });
1031
- const cancelProgress = (conversationId, waiterId) => encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(Effect.mapError(() => void 0), Effect.flatMap((encoded) => call(conversationId, "cancelProgress", encoded)), Effect.asVoid, Effect.ignore);
1032
- return CloudflareConversationClient.of({
1033
- submit: (agent, input, options) => Effect.gen(function* () {
1034
- const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError((cause) => AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` })));
1035
- const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(encodedInput).pipe(Effect.mapError(() => AgentInputError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
1036
- const request = SubmitRequest.make({
1037
- agentId: agent.definition.id,
1038
- principal: options.principal,
1039
- idempotencyKey: options.idempotencyKey,
1040
- definitions: options.definitions,
1041
- inputPayload
1042
- });
1043
- const encoded = yield* encodeSubmitRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`submit request encode failed: ${error.message}`) })));
1044
- const response = yield* call(options.conversationId, "submit", encoded);
1045
- return (yield* expect(options.conversationId, "submit", SubmitSucceeded, ClientSubmitHostFailure)(response)).receipt;
1046
- }),
1047
- awaitSettlement: (receipt) => Effect.gen(function* () {
1048
- const encoded = yield* encodeReceipt(receipt).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`receipt encode failed: ${error.message}`) })));
1049
- const response = yield* call(receipt.conversationId, "awaitSettlement", encoded);
1050
- return (yield* expect(receipt.conversationId, "awaitSettlement", SettlementReached, ClientAwaitHostFailure)(response)).settlement;
1051
- }),
1052
- awaitProgress: (conversationId, afterSequence) => Effect.gen(function* () {
1053
- const waiterId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress cancellation identity generation failed: ${error.message}`) })));
1054
- const request = AwaitProgressRequest.make({
1055
- afterSequence,
1056
- waiterId
1057
- });
1058
- const encoded = yield* encodeAwaitProgressRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress request encode failed: ${error.message}`) })));
1059
- 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)));
1060
- yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(conversationId, waiterId)));
1061
- }),
1062
- readPage,
1063
- readAll: (conversationId) => Effect.gen(function* () {
1064
- const all = [];
1065
- let after;
1066
- for (;;) {
1067
- const page = yield* readPage(conversationId, {
1068
- afterSequence: after,
1069
- limit: 1024
1070
- });
1071
- all.push(...page);
1072
- const last = page.at(-1);
1073
- if (page.length < 1024 || last === void 0) return all;
1074
- after = last.sequence;
1075
- }
1076
- }),
1077
- abort: (conversationId, command) => Effect.gen(function* () {
1078
- const encoded = yield* encodeAbortCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`abort command encode failed: ${error.message}`) })));
1079
- const response = yield* call(conversationId, "abort", encoded);
1080
- return (yield* expect(conversationId, "abort", AbortRecorded, ClientAbortHostFailure)(response)).intent;
1081
- }),
1082
- resolveApproval: (conversationId, command) => Effect.gen(function* () {
1083
- const encoded = yield* encodeApprovalDecisionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`approval command encode failed: ${error.message}`) })));
1084
- const response = yield* call(conversationId, "resolveApproval", encoded);
1085
- return (yield* expect(conversationId, "resolveApproval", ApprovalRecorded, ClientApprovalHostFailure)(response)).intent;
1086
- }),
1087
- resolveUnknown: (conversationId, command) => Effect.gen(function* () {
1088
- const encoded = yield* encodeUnknownResolutionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`resolution command encode failed: ${error.message}`) })));
1089
- const response = yield* call(conversationId, "resolveUnknown", encoded);
1090
- return (yield* expect(conversationId, "resolveUnknown", UnknownResolutionRecorded, ClientUnknownHostFailure)(response)).intent;
1091
- })
1092
- });
1093
- }));
1094
- };
1095
- //#endregion
1096
280
  //#region src/conversation-object.ts
1097
281
  /** Classify only a decoded port request so new protocol members cannot bypass pre-arming. */
1098
282
  const isMutatingPortRequest = (request) => {
@@ -2015,6 +1199,6 @@ const dynamicWorkerCodeExecutorLayer = (options) => Layer.effect(CodeExecutor, E
2015
1199
  return CodeExecutor.of({ execute: makeExecute(options, clock) });
2016
1200
  }));
2017
1201
  //#endregion
2018
- export { AbortRecorded, AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, AdmissionLimitExceeded, ApprovalRecorded, AwaitProgressRequest, BrowserQuickActionBrowserBinding, BrowserQuickActionRpcError, BrowserQuickActionWorkersAi, BrowserQuickActionWorkersAiPolicyError, BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunInteractiveBinding, BrowserRunInteractiveHost, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, CLOUDFLARE_DATABASE_CAP_BYTES, CLOUDFLARE_RUNTIME_DEFAULTS, CancelProgressRequest, CloudflareAdmissionLimitsValue, CloudflareBindingError, CloudflareConversationClient, CloudflareDurableRuntime, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError, ConversationClientError, ConversationMaintenance, ConversationMaintenanceFailpoint, ConversationObjectIdentity, ConversationObjectNamespace, ConversationObjectPorts, DEFAULT_MAX_DATABASE_BYTES, DurableAlarmError, DurableAlarmService, DurableObjectContext, ExplainedRecovery, HostFailed, HostFailure, HostProtocolError, HostResponse, MaintenancePassReport, ObligationsScanned, ObservePageRequest, ObservedPage, ProgressCancelled, ProgressObserved, ProgressWaitRegistry, RetryExecuted, SettlementReached, SubmitRequest, SubmitSucceeded, UnknownResolutionRecorded, VerifiedIntegrity, boundHostDiagnostic, browserQuickActionCaptureLayer, browserQuickActionImplementation, browserQuickActionScreenshotLayer, browserQuickActionWorkersAiCaptureLayer, browserRestCaptureImplementation, browserRestCaptureLayer, browserRestCrawlImplementation, browserRestCrawlLayer, browserRestWorkersAiCaptureLayer, browserRunInteractiveHostLayer, browserRunInteractiveImplementation, browserRunInteractiveLayer, cloudflareWakeSchedulerLayer, conversationNamespaceFromEnv, conversationNamespaceLayer, conversationPortTransportLayer, decodeAbortCommand, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeApprovalDecisionCommand, decodeAwaitProgressRequest, decodeCancelProgressRequest, decodeHostResponse, decodeObligationThresholds, decodeObservePageRequest, decodeReceipt, decodeRetryCommand, decodeSubmitRequest, decodeUnknownResolutionCommand, disposeRpcHandle, dynamicWorkerCodeExecutorLayer, dynamicWorkerImplementation, encodeAbortCommand, encodeAdminResponse, encodeApprovalDecisionCommand, encodeAwaitProgressRequest, encodeCancelProgressRequest, encodeHostResponse, encodeObservePageRequest, encodeReceipt, encodeSubmitRequest, encodeUnknownResolutionCommand, makeConversationObjectClass };
1202
+ export { AbortRecorded, AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, AdmissionLimitExceeded, ApprovalRecorded, AwaitProgressRequest, BrowserQuickActionBrowserBinding, BrowserQuickActionRpcError, BrowserQuickActionWorkersAi, BrowserQuickActionWorkersAiPolicyError, BrowserRunCleanupError, BrowserRunHandoffRequest, BrowserRunHandoffResult, BrowserRunHandoffState, BrowserRunInteractiveBinding, BrowserRunInteractiveHost, BrowserRunLiveViewRequest, BrowserRunLiveViewResult, BrowserRunSessionLifecycle, BrowserRunViewport, CLOUDFLARE_DATABASE_CAP_BYTES, CLOUDFLARE_RUNTIME_DEFAULTS, CancelProgressRequest, CloudflareAdmissionLimitsValue, CloudflareBindingError, CloudflareConversationClient, CloudflareDurableRuntime, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError, CloudflareSchedulingClient, ConversationClientError, ConversationMaintenance, ConversationMaintenanceFailpoint, ConversationObjectIdentity, ConversationObjectNamespace, ConversationObjectPorts, DEFAULT_MAX_DATABASE_BYTES, DurableAlarmError, DurableAlarmService, DurableObjectContext, ExplainedRecovery, HostFailed, HostFailure, HostProtocolError, HostResponse, MaintenancePassReport, ObligationsScanned, ObservePageRequest, ObservedPage, ProgressCancelled, ProgressObserved, ProgressWaitRegistry, RetryExecuted, ScheduleAlarmProtocolError, ScheduleOwnerIdentity, ScheduleOwnerNamespace, ScheduleOwnerProtocolError, SettlementReached, SubmitRequest, SubmitSucceeded, UnknownResolutionRecorded, VerifiedIntegrity, boundHostDiagnostic, browserQuickActionCaptureLayer, browserQuickActionImplementation, browserQuickActionScreenshotLayer, browserQuickActionWorkersAiCaptureLayer, browserRestCaptureImplementation, browserRestCaptureLayer, browserRestCrawlImplementation, browserRestCrawlLayer, browserRestWorkersAiCaptureLayer, browserRunInteractiveHostLayer, browserRunInteractiveImplementation, browserRunInteractiveLayer, cloudflareWakeSchedulerLayer, conversationNamespaceFromEnv, conversationNamespaceLayer, conversationPortTransportLayer, decodeAbortCommand, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeApprovalDecisionCommand, decodeAwaitProgressRequest, decodeCancelProgressRequest, decodeHostResponse, decodeObligationThresholds, decodeObservePageRequest, decodeReceipt, decodeRetryCommand, decodeSubmitRequest, decodeUnknownResolutionCommand, disposeRpcHandle, dynamicWorkerCodeExecutorLayer, dynamicWorkerImplementation, encodeAbortCommand, encodeAdminResponse, encodeApprovalDecisionCommand, encodeAwaitProgressRequest, encodeCancelProgressRequest, encodeHostResponse, encodeObservePageRequest, encodeReceipt, encodeSubmitRequest, encodeUnknownResolutionCommand, isBrowserRunUndispatchedActionError, makeConversationObjectClass, makeScheduleOwnerObjectClass };
2019
1203
 
2020
1204
  //# sourceMappingURL=index.mjs.map