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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/bindings-BrVEOkfe.d.mts +102 -0
  2. package/dist/browser-quick-action.d.mts +18 -1
  3. package/dist/browser-quick-action.mjs +14 -1
  4. package/dist/browser-quick-action.mjs.map +1 -1
  5. package/dist/browser-rest-capture.d.mts +17 -2
  6. package/dist/browser-rest-capture.mjs +12 -1
  7. package/dist/browser-rest-capture.mjs.map +1 -1
  8. package/dist/browser-rest-crawl.mjs +2 -2
  9. package/dist/browser-rest-crawl.mjs.map +1 -1
  10. package/dist/index.d.mts +245 -237
  11. package/dist/index.mjs +168 -164
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/interactive-browser.d.mts +160 -2
  14. package/dist/interactive-browser.mjs +1166 -2
  15. package/dist/interactive-browser.mjs.map +1 -0
  16. package/dist/{scheduling-B-OFqoS9.mjs → prepared-admission-BKp_Upw2.mjs} +134 -433
  17. package/dist/prepared-admission-BKp_Upw2.mjs.map +1 -0
  18. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  19. package/dist/scheduling.d.mts +47 -2
  20. package/dist/scheduling.mjs +339 -1
  21. package/dist/scheduling.mjs.map +1 -0
  22. package/dist/subscriptions.d.mts +52 -0
  23. package/dist/subscriptions.mjs +346 -0
  24. package/dist/subscriptions.mjs.map +1 -0
  25. package/package.json +20 -13
  26. package/src/alarm.ts +264 -265
  27. package/src/bindings.ts +34 -30
  28. package/src/browser-quick-action.ts +31 -0
  29. package/src/browser-rest-capture.ts +27 -0
  30. package/src/client.ts +111 -105
  31. package/src/code-mode-executor.ts +19 -0
  32. package/src/config.ts +7 -7
  33. package/src/index.ts +10 -11
  34. package/src/interactive-browser.ts +29 -1
  35. package/src/layers.ts +178 -257
  36. package/src/prepared-admission.ts +90 -0
  37. package/src/scheduling.ts +19 -55
  38. package/src/subscriptions.ts +661 -0
  39. package/src/{conversation-object.ts → thread-object.ts} +167 -136
  40. package/src/transport.ts +15 -15
  41. package/src/wake-scheduler.ts +17 -22
  42. package/dist/interactive-browser-BRo-Bfvb.d.mts +0 -144
  43. package/dist/interactive-browser-Cy5q-ldl.mjs +0 -1154
  44. package/dist/interactive-browser-Cy5q-ldl.mjs.map +0 -1
  45. package/dist/scheduling-B-OFqoS9.mjs.map +0 -1
  46. package/dist/scheduling-BJs_kHTx.d.mts +0 -142
package/src/client.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AgentId, AgentInputError, type ConversationId } from "@effect-agent/core";
1
+ import { AgentId, AgentInputError, type ThreadId } from "@effect-agent/core";
2
2
  import {
3
3
  AbortCommand,
4
4
  AbortIntent,
@@ -9,8 +9,8 @@ import {
9
9
  ApprovalDecisionIntent,
10
10
  CanonicalRecordEnvelope,
11
11
  CanonicalSequence,
12
- ConversationNotMaterialized,
13
- ConversationStoreError,
12
+ ThreadNotMaterialized,
13
+ ThreadStoreError,
14
14
  DefinitionDigests,
15
15
  DigestError,
16
16
  DurableRuntimeFailpointError,
@@ -29,17 +29,18 @@ import {
29
29
  UnknownResolutionIntent,
30
30
  type DurableSubmitAgent,
31
31
  type DurableSubmitOptions,
32
- } from "@effect-agent/session";
32
+ } from "@effect-agent/thread";
33
+ import { BrowserCrypto } from "@effect/platform-browser";
33
34
  import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
34
35
  import { RpcTracing } from "effect-cf";
35
36
 
36
37
  import { DurableAlarmError } from "./alarm.ts";
37
- import { ConversationObjectNamespace, type ConversationObjectRpc } from "./bindings.ts";
38
+ import { ThreadObjectNamespace, type ThreadObjectRpc } from "./bindings.ts";
38
39
  import { cloudflareFailureSignals, safeCauseMessage } from "./boundary.ts";
39
40
  import { AdmissionLimitExceeded } from "./config.ts";
40
41
 
41
42
  /**
42
- * The Worker↔Conversation-Object host protocol (plan §1.4): Schema envelopes for the host
43
+ * The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
43
44
  * entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
44
45
  * `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
45
46
  * it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
@@ -70,10 +71,10 @@ export class HostProtocolError extends Schema.TaggedError<HostProtocolError>()(
70
71
  ) {}
71
72
 
72
73
  /** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
73
- export class ConversationClientError extends Schema.TaggedError<ConversationClientError>()(
74
- "ConversationClientError",
74
+ export class ThreadClientError extends Schema.TaggedError<ThreadClientError>()(
75
+ "ThreadClientError",
75
76
  {
76
- conversationId: Schema.String,
77
+ threadId: Schema.String,
77
78
  message: Schema.String,
78
79
  cause: Schema.optionalKey(Schema.Defect()),
79
80
  /** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
@@ -91,7 +92,7 @@ export class ConversationClientError extends Schema.TaggedError<ConversationClie
91
92
  * One durable submission, input ALREADY encoded by the caller through the Agent Binding's
92
93
  * input schema (the Worker bundles the same Agent definitions as the Object, so schema
93
94
  * validation happens client-side; the resolved Binding re-validates at claim time). The
94
- * Conversation identity is deliberately absent — the addressed Object IS the lane.
95
+ * Thread identity is deliberately absent — the addressed Object IS the lane.
95
96
  */
96
97
  export class SubmitRequest extends Schema.Class<SubmitRequest>(
97
98
  "@effect-agent/platform-cloudflare/SubmitRequest",
@@ -145,8 +146,8 @@ export const HostFailure = Schema.Union([
145
146
  UnknownResolutionConflict,
146
147
  JoinedToHost,
147
148
  LedgerError,
148
- ConversationStoreError,
149
- ConversationNotMaterialized,
149
+ ThreadStoreError,
150
+ ThreadNotMaterialized,
150
151
  AppendConflict,
151
152
  FenceRejected,
152
153
  DurableRuntimeFailpointError,
@@ -250,14 +251,14 @@ export const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
250
251
  // Worker-side client
251
252
  // ---------------------------------------------------------------------------
252
253
 
253
- /** Failure surface of `CloudflareConversationClient.submit`. */
254
+ /** Failure surface of `CloudflareThreadClient.submit`. */
254
255
  const ClientSubmitHostFailure = Schema.Union([
255
256
  AgentInputError,
256
257
  DigestError,
257
258
  AdmissionConflict,
258
259
  LedgerError,
259
- ConversationStoreError,
260
- ConversationNotMaterialized,
260
+ ThreadStoreError,
261
+ ThreadNotMaterialized,
261
262
  AppendConflict,
262
263
  FenceRejected,
263
264
  DurableRuntimeFailpointError,
@@ -265,14 +266,20 @@ const ClientSubmitHostFailure = Schema.Union([
265
266
  DurableAlarmError,
266
267
  HostProtocolError,
267
268
  ]);
268
- const ClientAwaitHostFailure = Schema.Union([LedgerError, SettlementConflict, HostProtocolError]);
269
+ const ClientAwaitHostFailure = Schema.Union([
270
+ LedgerError,
271
+ SettlementConflict,
272
+ OperationDenied,
273
+ HostProtocolError,
274
+ ]);
269
275
  const ClientObserveHostFailure = Schema.Union([
270
- ConversationStoreError,
271
- ConversationNotMaterialized,
276
+ ThreadStoreError,
277
+ ThreadNotMaterialized,
272
278
  OperationDenied,
273
279
  HostProtocolError,
274
280
  ]);
275
281
  const ClientAbortHostFailure = Schema.Union([
282
+ OperationDenied,
276
283
  LedgerError,
277
284
  SettlementConflict,
278
285
  JoinedToHost,
@@ -299,23 +306,19 @@ const ClientUnknownHostFailure = Schema.Union([
299
306
  HostProtocolError,
300
307
  ]);
301
308
 
302
- export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type | ConversationClientError;
303
- export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type | ConversationClientError;
304
- export type ClientObserveFailure = typeof ClientObserveHostFailure.Type | ConversationClientError;
309
+ export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type | ThreadClientError;
310
+ export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type | ThreadClientError;
311
+ export type ClientObserveFailure = typeof ClientObserveHostFailure.Type | ThreadClientError;
305
312
  export type ClientProgressFailure = ClientObserveFailure;
306
- export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ConversationClientError;
307
- export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ConversationClientError;
308
- export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ConversationClientError;
309
-
310
- const outOfContract = (
311
- conversationId: string,
312
- operation: string,
313
- observed: string,
314
- ): ConversationClientError =>
315
- ConversationClientError.make({
316
- conversationId,
313
+ export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ThreadClientError;
314
+ export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ThreadClientError;
315
+ export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ThreadClientError;
316
+
317
+ const outOfContract = (threadId: string, operation: string, observed: string): ThreadClientError =>
318
+ ThreadClientError.make({
319
+ threadId,
317
320
  message: boundHostDiagnostic(
318
- `The Conversation Object answered ${operation} with the out-of-contract ${observed}.`,
321
+ `The Thread Object answered ${operation} with the out-of-contract ${observed}.`,
319
322
  ),
320
323
  });
321
324
 
@@ -328,11 +331,11 @@ const hostRpcMethods = {
328
331
  abort: "abortEncoded",
329
332
  resolveApproval: "resolveApprovalEncoded",
330
333
  resolveUnknown: "resolveUnknownEncoded",
331
- } as const satisfies Record<string, keyof ConversationObjectRpc>;
334
+ } as const satisfies Record<string, keyof ThreadObjectRpc>;
332
335
 
333
- /** Worker-side client over the Conversation Object namespace (DEPLOY-010). */
334
- export class CloudflareConversationClient extends Context.Service<
335
- CloudflareConversationClient,
336
+ /** Worker-side client over the Thread Object namespace (DEPLOY-010). */
337
+ export class CloudflareThreadClient extends Context.Service<
338
+ CloudflareThreadClient,
336
339
  {
337
340
  /** Encode the input client-side, then durably submit to the owning Object. */
338
341
  readonly submit: <InputSchema extends Schema.Top>(
@@ -347,12 +350,12 @@ export class CloudflareConversationClient extends Context.Service<
347
350
  * The result is deliberately void: canonical records remain authoritative and must be read.
348
351
  */
349
352
  readonly awaitProgress: (
350
- conversationId: ConversationId,
353
+ threadId: ThreadId,
351
354
  afterSequence: CanonicalSequence,
352
355
  ) => Effect.Effect<void, ClientProgressFailure>;
353
356
  /** One bounded page of canonical records. */
354
357
  readonly readPage: (
355
- conversationId: ConversationId,
358
+ threadId: ThreadId,
356
359
  options?: {
357
360
  readonly afterSequence?: CanonicalSequence | undefined;
358
361
  readonly limit?: number | undefined;
@@ -364,55 +367,69 @@ export class CloudflareConversationClient extends Context.Service<
364
367
  * `awaitSettlement`.
365
368
  */
366
369
  readonly readAll: (
367
- conversationId: ConversationId,
370
+ threadId: ThreadId,
368
371
  ) => Effect.Effect<ReadonlyArray<CanonicalRecordEnvelope>, ClientObserveFailure>;
369
372
  /**
370
- * Submission-addressed operations take the owning Conversation explicitly (from the
373
+ * Submission-addressed operations take the owning Thread explicitly (from the
371
374
  * Receipt): minted Submission identities stay OPAQUE outside the storage adapter that
372
375
  * minted them (D-P6-5), so the client never parses one to find the lane.
373
376
  */
374
377
  readonly abort: (
375
- conversationId: ConversationId,
378
+ threadId: ThreadId,
376
379
  command: AbortCommand,
377
380
  ) => Effect.Effect<AbortIntent, ClientAbortFailure>;
378
381
  readonly resolveApproval: (
379
- conversationId: ConversationId,
382
+ threadId: ThreadId,
380
383
  command: ApprovalDecisionCommand,
381
384
  ) => Effect.Effect<ApprovalDecisionIntent, ClientApprovalFailure>;
382
385
  readonly resolveUnknown: (
383
- conversationId: ConversationId,
386
+ threadId: ThreadId,
384
387
  command: UnknownResolutionCommand,
385
388
  ) => Effect.Effect<UnknownResolutionIntent, ClientUnknownFailure>;
386
389
  }
387
- >()("@effect-agent/platform-cloudflare/CloudflareConversationClient") {
390
+ >()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
391
+ /**
392
+ * Assemble the client from a resolved namespace and the platform Crypto implementation.
393
+ * rpcTracing is the binding name and remains opt-in. Caller authentication, principals,
394
+ * idempotency keys, and definition digests are still explicit submission inputs.
395
+ */
396
+ static layerFromBinding(options: {
397
+ readonly namespace: DurableObjectNamespace<ThreadObjectRpc>;
398
+ readonly rpcTracing?: string;
399
+ }): Layer.Layer<CloudflareThreadClient> {
400
+ return CloudflareThreadClient.layer.pipe(
401
+ Layer.provide([ThreadObjectNamespace.layer(options.namespace, options), BrowserCrypto.layer]),
402
+ );
403
+ }
404
+
388
405
  static readonly layer: Layer.Layer<
389
- CloudflareConversationClient,
406
+ CloudflareThreadClient,
390
407
  never,
391
- ConversationObjectNamespace | Crypto.Crypto
392
- > = Layer.effect(CloudflareConversationClient)(
408
+ ThreadObjectNamespace | Crypto.Crypto
409
+ > = Layer.effect(CloudflareThreadClient)(
393
410
  Effect.gen(function* () {
394
- const { namespace, rpcTracing } = yield* ConversationObjectNamespace;
411
+ const { namespace, rpcTracing } = yield* ThreadObjectNamespace;
395
412
  const crypto = yield* Crypto.Crypto;
396
413
 
397
414
  const call = Effect.fn(
398
415
  function* (
399
- conversationId: string,
416
+ threadId: string,
400
417
  operation: keyof typeof hostRpcMethods,
401
418
  encoded: unknown,
402
- ): Effect.fn.Return<HostResponse, ConversationClientError | HostProtocolError> {
419
+ ): Effect.fn.Return<HostResponse, ThreadClientError | HostProtocolError> {
403
420
  // Empty arguments preserve native arity. Passing `undefined` still adds an argument.
404
421
  const traceArgs =
405
422
  rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
406
423
  const raw = yield* Effect.tryPromise({
407
424
  try: () => {
408
- const stub = namespace.get(namespace.idFromName(conversationId));
425
+ const stub = namespace.get(namespace.idFromName(threadId));
409
426
  return stub[hostRpcMethods[operation]](encoded, ...traceArgs);
410
427
  },
411
428
  catch: (cause) =>
412
- ConversationClientError.make({
413
- conversationId,
429
+ ThreadClientError.make({
430
+ threadId,
414
431
  message: boundHostDiagnostic(
415
- `${operation} did not reach the Conversation Object: ${safeCauseMessage(
432
+ `${operation} did not reach the Thread Object: ${safeCauseMessage(
416
433
  cause,
417
434
  "the RPC failed without a diagnostic",
418
435
  )}`,
@@ -422,26 +439,25 @@ export class CloudflareConversationClient extends Context.Service<
422
439
  }),
423
440
  });
424
441
  return yield* decodeHostResponse(raw).pipe(
425
- Effect.mapError(
426
- (error): HostProtocolError =>
427
- HostProtocolError.make({
428
- message: boundHostDiagnostic(
429
- `The ${operation} answer could not be decoded: ${error.message}`,
430
- ),
431
- }),
442
+ Effect.mapError((error): HostProtocolError =>
443
+ HostProtocolError.make({
444
+ message: boundHostDiagnostic(
445
+ `The ${operation} answer could not be decoded: ${error.message}`,
446
+ ),
447
+ }),
432
448
  ),
433
449
  );
434
450
  },
435
- (effect, conversationId, operation) =>
451
+ (effect, threadId, operation) =>
436
452
  rpcTracing === undefined
437
- ? Effect.withSpan(effect, "CloudflareConversationClient.call", {
438
- attributes: { conversationId, operation },
453
+ ? Effect.withSpan(effect, "CloudflareThreadClient.call", {
454
+ attributes: { threadId, operation },
439
455
  })
440
456
  : RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]),
441
457
  );
442
458
 
443
459
  const expect = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
444
- conversationId: string,
460
+ threadId: string,
445
461
  operation: string,
446
462
  resultSchema: ResultSchema,
447
463
  failureSchema: FailureSchema,
@@ -450,22 +466,22 @@ export class CloudflareConversationClient extends Context.Service<
450
466
  const isExpectedFailure = Schema.is(failureSchema);
451
467
  return (
452
468
  response: HostResponse,
453
- ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ConversationClientError> => {
469
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadClientError> => {
454
470
  if (response._tag === "HostFailed") {
455
471
  const failure = response.failure;
456
472
  return isExpectedFailure(failure)
457
473
  ? Effect.fail(failure)
458
- : Effect.fail(outOfContract(conversationId, operation, `failure ${failure._tag}`));
474
+ : Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
459
475
  }
460
476
  if (!isExpectedResult(response)) {
461
- return Effect.fail(outOfContract(conversationId, operation, `result ${response._tag}`));
477
+ return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
462
478
  }
463
479
  return Effect.succeed(response);
464
480
  };
465
481
  };
466
482
 
467
483
  const readPage = (
468
- conversationId: ConversationId,
484
+ threadId: ThreadId,
469
485
  options?: {
470
486
  readonly afterSequence?: CanonicalSequence | undefined;
471
487
  readonly limit?: number | undefined;
@@ -485,9 +501,9 @@ export class CloudflareConversationClient extends Context.Service<
485
501
  }),
486
502
  ),
487
503
  );
488
- const response = yield* call(conversationId, "observePage", encoded);
504
+ const response = yield* call(threadId, "observePage", encoded);
489
505
  const page = yield* expect(
490
- conversationId,
506
+ threadId,
491
507
  "observePage",
492
508
  ObservedPage,
493
509
  ClientObserveHostFailure,
@@ -495,18 +511,15 @@ export class CloudflareConversationClient extends Context.Service<
495
511
  return page.records;
496
512
  });
497
513
 
498
- const cancelProgress = (
499
- conversationId: ConversationId,
500
- waiterId: string,
501
- ): Effect.Effect<void> =>
514
+ const cancelProgress = (threadId: ThreadId, waiterId: string): Effect.Effect<void> =>
502
515
  encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(
503
516
  Effect.mapError(() => undefined),
504
- Effect.flatMap((encoded) => call(conversationId, "cancelProgress", encoded)),
517
+ Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)),
505
518
  Effect.asVoid,
506
519
  Effect.ignore,
507
520
  );
508
521
 
509
- return CloudflareConversationClient.of({
522
+ return CloudflareThreadClient.of({
510
523
  submit: <InputSchema extends Schema.Top>(
511
524
  agent: DurableSubmitAgent<InputSchema>,
512
525
  input: InputSchema["Type"],
@@ -543,9 +556,9 @@ export class CloudflareConversationClient extends Context.Service<
543
556
  }),
544
557
  ),
545
558
  );
546
- const response = yield* call(options.conversationId, "submit", encoded);
559
+ const response = yield* call(options.threadId, "submit", encoded);
547
560
  const succeeded = yield* expect(
548
- options.conversationId,
561
+ options.threadId,
549
562
  "submit",
550
563
  SubmitSucceeded,
551
564
  ClientSubmitHostFailure,
@@ -562,9 +575,9 @@ export class CloudflareConversationClient extends Context.Service<
562
575
  }),
563
576
  ),
564
577
  );
565
- const response = yield* call(receipt.conversationId, "awaitSettlement", encoded);
578
+ const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
566
579
  const settled = yield* expect(
567
- receipt.conversationId,
580
+ receipt.threadId,
568
581
  "awaitSettlement",
569
582
  SettlementReached,
570
583
  ClientAwaitHostFailure,
@@ -572,7 +585,7 @@ export class CloudflareConversationClient extends Context.Service<
572
585
  return settled.settlement;
573
586
  }),
574
587
 
575
- awaitProgress: (conversationId, afterSequence) =>
588
+ awaitProgress: (threadId, afterSequence) =>
576
589
  Effect.gen(function* () {
577
590
  const waiterId = yield* crypto.randomUUIDv4.pipe(
578
591
  Effect.mapError((error) =>
@@ -595,17 +608,12 @@ export class CloudflareConversationClient extends Context.Service<
595
608
  );
596
609
 
597
610
  const attempt = (retry: number): Effect.Effect<void, ClientProgressFailure> =>
598
- call(conversationId, "awaitProgress", encoded).pipe(
611
+ call(threadId, "awaitProgress", encoded).pipe(
599
612
  Effect.flatMap(
600
- expect(
601
- conversationId,
602
- "awaitProgress",
603
- ProgressObserved,
604
- ClientObserveHostFailure,
605
- ),
613
+ expect(threadId, "awaitProgress", ProgressObserved, ClientObserveHostFailure),
606
614
  ),
607
615
  Effect.asVoid,
608
- Effect.catchTag("ConversationClientError", (error) =>
616
+ Effect.catchTag("ThreadClientError", (error) =>
609
617
  error.retryable === true && error.overloaded !== true && retry < 5
610
618
  ? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(
611
619
  Effect.andThen(attempt(retry + 1)),
@@ -614,19 +622,17 @@ export class CloudflareConversationClient extends Context.Service<
614
622
  ),
615
623
  );
616
624
 
617
- yield* attempt(0).pipe(
618
- Effect.onInterrupt(() => cancelProgress(conversationId, waiterId)),
619
- );
625
+ yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
620
626
  }),
621
627
 
622
628
  readPage,
623
629
 
624
- readAll: (conversationId) =>
630
+ readAll: (threadId) =>
625
631
  Effect.gen(function* () {
626
632
  const all: Array<CanonicalRecordEnvelope> = [];
627
633
  let after: CanonicalSequence | undefined;
628
634
  for (;;) {
629
- const page = yield* readPage(conversationId, { afterSequence: after, limit: 1_024 });
635
+ const page = yield* readPage(threadId, { afterSequence: after, limit: 1_024 });
630
636
  all.push(...page);
631
637
  const last = page.at(-1);
632
638
  if (page.length < 1_024 || last === undefined) return all;
@@ -634,7 +640,7 @@ export class CloudflareConversationClient extends Context.Service<
634
640
  }
635
641
  }),
636
642
 
637
- abort: (conversationId, command) =>
643
+ abort: (threadId, command) =>
638
644
  Effect.gen(function* () {
639
645
  const encoded = yield* encodeAbortCommand(command).pipe(
640
646
  Effect.mapError((error) =>
@@ -643,9 +649,9 @@ export class CloudflareConversationClient extends Context.Service<
643
649
  }),
644
650
  ),
645
651
  );
646
- const response = yield* call(conversationId, "abort", encoded);
652
+ const response = yield* call(threadId, "abort", encoded);
647
653
  const recorded = yield* expect(
648
- conversationId,
654
+ threadId,
649
655
  "abort",
650
656
  AbortRecorded,
651
657
  ClientAbortHostFailure,
@@ -653,7 +659,7 @@ export class CloudflareConversationClient extends Context.Service<
653
659
  return recorded.intent;
654
660
  }),
655
661
 
656
- resolveApproval: (conversationId, command) =>
662
+ resolveApproval: (threadId, command) =>
657
663
  Effect.gen(function* () {
658
664
  const encoded = yield* encodeApprovalDecisionCommand(command).pipe(
659
665
  Effect.mapError((error) =>
@@ -662,9 +668,9 @@ export class CloudflareConversationClient extends Context.Service<
662
668
  }),
663
669
  ),
664
670
  );
665
- const response = yield* call(conversationId, "resolveApproval", encoded);
671
+ const response = yield* call(threadId, "resolveApproval", encoded);
666
672
  const recorded = yield* expect(
667
- conversationId,
673
+ threadId,
668
674
  "resolveApproval",
669
675
  ApprovalRecorded,
670
676
  ClientApprovalHostFailure,
@@ -672,7 +678,7 @@ export class CloudflareConversationClient extends Context.Service<
672
678
  return recorded.intent;
673
679
  }),
674
680
 
675
- resolveUnknown: (conversationId, command) =>
681
+ resolveUnknown: (threadId, command) =>
676
682
  Effect.gen(function* () {
677
683
  const encoded = yield* encodeUnknownResolutionCommand(command).pipe(
678
684
  Effect.mapError((error) =>
@@ -683,9 +689,9 @@ export class CloudflareConversationClient extends Context.Service<
683
689
  }),
684
690
  ),
685
691
  );
686
- const response = yield* call(conversationId, "resolveUnknown", encoded);
692
+ const response = yield* call(threadId, "resolveUnknown", encoded);
687
693
  const recorded = yield* expect(
688
- conversationId,
694
+ threadId,
689
695
  "resolveUnknown",
690
696
  UnknownResolutionRecorded,
691
697
  ClientUnknownHostFailure,
@@ -814,3 +814,22 @@ export const dynamicWorkerCodeExecutorLayer = (
814
814
  return CodeExecutor.of({ execute: makeExecute(options, clock) });
815
815
  }),
816
816
  );
817
+
818
+ /** Assemble Code Mode handlers with the isolated Dynamic Worker executor. */
819
+ export const CloudflareCodeMode = {
820
+ /**
821
+ * Provide the selected tool handlers at construction, where Code Mode captures them.
822
+ * Their errors and remaining dependencies stay visible. The definition still owns the
823
+ * allowlist and limits; the runtime supplies the live Tool broker for each scoped pass.
824
+ */
825
+ layer: <A, E, R, Handlers, HandlerError, HandlerRequirements>(
826
+ definition: { readonly handlers: Layer.Layer<A, E, R> },
827
+ options: DynamicWorkerCodeExecutorOptions & {
828
+ readonly handlers: Layer.Layer<Handlers, HandlerError, HandlerRequirements>;
829
+ },
830
+ ) =>
831
+ definition.handlers.pipe(
832
+ Layer.provide(options.handlers),
833
+ Layer.provide(dynamicWorkerCodeExecutorLayer(options)),
834
+ ),
835
+ };
package/src/config.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId } from "@effect-agent/session";
2
1
  import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@effect-agent/storage-cloudflare";
2
+ import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId } from "@effect-agent/thread";
3
3
  import { Context, Duration, Schema } from "effect";
4
4
 
5
5
  /**
@@ -25,7 +25,7 @@ export class CloudflarePlatformConfigError extends Schema.TaggedError<Cloudflare
25
25
  * (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
26
26
  * behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
27
27
  * `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
28
- * Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
28
+ * Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
29
29
  * nothing was admitted or written.
30
30
  */
31
31
  export class AdmissionLimitExceeded extends Schema.TaggedError<AdmissionLimitExceeded>()(
@@ -52,13 +52,13 @@ export const CLOUDFLARE_DATABASE_CAP_BYTES = 10_000_000_000;
52
52
  export const DEFAULT_MAX_DATABASE_BYTES = 9_000_000_000;
53
53
 
54
54
  /**
55
- * Explicit bounded admission quotas checked by the Conversation Object BEFORE admission
55
+ * Explicit bounded admission quotas checked by the Thread Object BEFORE admission
56
56
  * (exit gate "resource limits are checked before admission").
57
57
  */
58
58
  export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmissionLimitsValue>(
59
59
  "@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue",
60
60
  )({
61
- /** Maximum nonterminal Submissions per Conversation lane before new admissions refuse. */
61
+ /** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
62
62
  maxQueueDepthPerLane: Schema.Int.check(
63
63
  Schema.isGreaterThan(0),
64
64
  Schema.isLessThanOrEqualTo(100_000),
@@ -74,7 +74,7 @@ export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmis
74
74
 
75
75
  /**
76
76
  * Validated Cloudflare durable runtime configuration. The producer identity of one
77
- * Conversation Object is `{producerPrefix}:{conversationId}` — stable across incarnations of
77
+ * Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
78
78
  * the same deployment, distinct across deployments — and producer-epoch fencing (not the
79
79
  * producer name) remains the correctness authority (DUR-006).
80
80
  */
@@ -82,7 +82,7 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
82
82
  "@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue",
83
83
  )({
84
84
  deploymentId: DeploymentId,
85
- /** Head of the minted producer identity `{producerPrefix}:{conversationId}`. */
85
+ /** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
86
86
  producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
87
87
  /** Submission ownership lease duration (D5); fences work across Object incarnations. */
88
88
  ownershipLeaseDuration: PositiveMillis,
@@ -119,7 +119,7 @@ export class CloudflareDurableRuntimeConfig extends Context.Service<
119
119
  CloudflareDurableRuntimeConfigValue
120
120
  >()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {}
121
121
 
122
- /** Documented production defaults applied by `CloudflareDurableRuntime.layer`. */
122
+ /** Documented production defaults applied by `ThreadObject.layer`. */
123
123
  export const CLOUDFLARE_RUNTIME_DEFAULTS = {
124
124
  ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
125
125
  alarmBackoffBase: 100,
package/src/index.ts CHANGED
@@ -2,14 +2,14 @@
2
2
  * `@effect-agent/platform-cloudflare` — the Cloudflare Layer-assembly library for deployment
3
3
  * class DC (deployment spec §12: a Layer-assembly library, not an application entrypoint).
4
4
  *
5
- * One SQLite-backed Durable Object per Conversation is the serialized owner (durability §6):
6
- * `makeConversationObjectClass` builds the class applications export from their Worker,
7
- * `CloudflareDurableRuntime.layer` assembles the coordinator over the storage-cloudflare
8
- * adapters and the WP2 cross-Object routing, `DurableAlarmService`/`ConversationMaintenance`
5
+ * One SQLite-backed Durable Object per Thread is the serialized owner (durability §6):
6
+ * `ThreadObject.make` builds the class applications export from their Worker,
7
+ * `ThreadObject.layer` assembles the coordinator over the storage-cloudflare
8
+ * adapters and the WP2 cross-Object routing, `DurableAlarmService`/`ThreadMaintenance`
9
9
  * multiplex every cadence into the Object's single alarm slot (dirty or autonomously
10
10
  * actionable work retains a committed alarm; stable external waits quiesce until their next
11
11
  * durably pre-armed mutation), and
12
- * `CloudflareConversationClient` is the Worker-side ingress. Platform bindings enter ONLY
12
+ * `CloudflareThreadClient` is the Worker-side ingress. Platform bindings enter ONLY
13
13
  * through the `bindings.ts` Layers (DEPLOY-010). This is the only workspace package allowed
14
14
  * to import the `cloudflare:workers` runtime module.
15
15
  */
@@ -19,12 +19,11 @@ export * from "./alarm.ts";
19
19
  export * from "./wake-scheduler.ts";
20
20
  export * from "./progress-wait.ts";
21
21
  export * from "./transport.ts";
22
- export * from "./layers.ts";
23
- export * from "./conversation-object.ts";
22
+ export { ThreadObjectPorts } from "./layers.ts";
23
+ export * as ThreadObject from "./thread-object.ts";
24
24
  export * from "./client.ts";
25
+ export * from "./prepared-admission.ts";
25
26
  export * from "./scheduling.ts";
27
+ export * from "./subscriptions.ts";
26
28
  export * from "./code-mode-executor.ts";
27
- export * from "./browser-quick-action.ts";
28
- export * from "./browser-rest-capture.ts";
29
- export * from "./interactive-browser.ts";
30
- export * from "./browser-rest-crawl.ts";
29
+ export { CloudflareBrowser, type CloudflareBrowserOptions } from "./browser-quick-action.ts";
@@ -44,7 +44,10 @@ import {
44
44
  type Scope,
45
45
  } from "effect";
46
46
 
47
- import { BrowserRunSessionLifecycle } from "./browser-session-lifecycle.ts";
47
+ import {
48
+ BrowserRunSessionLifecycle,
49
+ type BrowserRunLifecycleOptions,
50
+ } from "./browser-session-lifecycle.ts";
48
51
  export { BrowserRunCleanupError, BrowserRunSessionLifecycle } from "./browser-session-lifecycle.ts";
49
52
 
50
53
  export const browserRunInteractiveImplementation = SandboxImplementation.make({
@@ -2145,3 +2148,28 @@ export const browserRunInteractiveLayer = (): Layer.Layer<
2145
2148
  });
2146
2149
  }),
2147
2150
  );
2151
+
2152
+ /** Resolved Worker binding, cleanup credentials, and optional initial viewport. */
2153
+ export interface CloudflareInteractiveBrowserOptions extends BrowserRunLifecycleOptions {
2154
+ readonly browser: BrowserRun;
2155
+ readonly viewport?: BrowserRunViewport;
2156
+ }
2157
+
2158
+ const interactiveBindingLayer = (options: CloudflareInteractiveBrowserOptions) =>
2159
+ BrowserRunInteractiveBinding.layer(options).pipe(
2160
+ Layer.provide(BrowserRunSessionLifecycle.layer(options)),
2161
+ );
2162
+
2163
+ /** Worker-only assembly; importing ordinary capture never loads Puppeteer. */
2164
+ export const CloudflareInteractiveBrowser = {
2165
+ /**
2166
+ * Assemble binding, confirmed-session cleanup, and the generic browser service.
2167
+ * HttpClient and construction errors remain visible. Opening a pass still requires
2168
+ * an explicit policy and Scope; no browser is launched during Layer construction.
2169
+ */
2170
+ layer: (options: CloudflareInteractiveBrowserOptions) =>
2171
+ browserRunInteractiveLayer().pipe(Layer.provide(interactiveBindingLayer(options))),
2172
+ /** Opt into private host controls instead of exposing them through the generic service. */
2173
+ hostLayer: (options: CloudflareInteractiveBrowserOptions) =>
2174
+ browserRunInteractiveHostLayer().pipe(Layer.provide(interactiveBindingLayer(options))),
2175
+ };