@yielded/agent-platform-cloudflare 0.1.0-beta.167 → 0.1.0-beta.168

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 (97) hide show
  1. package/dist/{Alarm-B0ODr1NE.mjs → Alarm-DxODgAZZ.mjs} +259 -159
  2. package/dist/Alarm-DxODgAZZ.mjs.map +1 -0
  3. package/dist/Alarm.d.mts +27 -36
  4. package/dist/Alarm.mjs +2 -2
  5. package/dist/BrowserCredentials-C5IWfi7f.d.mts +91 -0
  6. package/dist/BrowserCredentials.d.mts +2 -91
  7. package/dist/BrowserRestCapture.mjs +3 -3
  8. package/dist/BrowserRestCapture.mjs.map +1 -1
  9. package/dist/BrowserRestCrawl.mjs +6 -6
  10. package/dist/BrowserRestCrawl.mjs.map +1 -1
  11. package/dist/{BrowserSession-RfIzxGCH.mjs → BrowserSession-BfNmyayH.mjs} +5 -5
  12. package/dist/BrowserSession-BfNmyayH.mjs.map +1 -0
  13. package/dist/{BrowserSession-B969Xg-2.d.mts → BrowserSession-Bx0V1-6_.d.mts} +6 -3
  14. package/dist/BrowserSession.d.mts +1 -1
  15. package/dist/BrowserSession.mjs +2 -2
  16. package/dist/BrowserUse-AstK0jKX.mjs +1467 -0
  17. package/dist/BrowserUse-AstK0jKX.mjs.map +1 -0
  18. package/dist/BrowserUse.d.mts +83 -0
  19. package/dist/BrowserUse.mjs +2 -0
  20. package/dist/CloudflareBindings.d.mts +8 -1
  21. package/dist/CloudflareBindings.mjs +2 -2
  22. package/dist/CloudflareBindings.mjs.map +1 -1
  23. package/dist/{CloudflareBrowser-5an6m-Qb.mjs → CloudflareBrowser-Vt8qTdYW.mjs} +11 -13
  24. package/dist/CloudflareBrowser-Vt8qTdYW.mjs.map +1 -0
  25. package/dist/CloudflareBrowser.mjs +1 -1
  26. package/dist/CloudflareCodeMode.mjs +1 -1
  27. package/dist/CloudflareCodeMode.mjs.map +1 -1
  28. package/dist/{CloudflareConfig-qDrRLofL.d.mts → CloudflareConfig-C9MvPbX8.d.mts} +9 -11
  29. package/dist/CloudflareConfig.d.mts +1 -1
  30. package/dist/CloudflareConfig.mjs +9 -12
  31. package/dist/CloudflareConfig.mjs.map +1 -1
  32. package/dist/CloudflareMemory.d.mts +3 -25
  33. package/dist/CloudflareMemory.mjs +33 -31
  34. package/dist/CloudflareMemory.mjs.map +1 -1
  35. package/dist/CloudflareScheduling.mjs +8 -15
  36. package/dist/CloudflareScheduling.mjs.map +1 -1
  37. package/dist/CloudflareSubscriptions.mjs +9 -16
  38. package/dist/CloudflareSubscriptions.mjs.map +1 -1
  39. package/dist/CloudflareThreadClient-CZm0TvUe.mjs +588 -0
  40. package/dist/CloudflareThreadClient-CZm0TvUe.mjs.map +1 -0
  41. package/dist/CloudflareThreadClient-QsVzLGGZ.d.mts +2033 -0
  42. package/dist/CloudflareThreadClient.d.mts +2 -1796
  43. package/dist/CloudflareThreadClient.mjs +2 -382
  44. package/dist/{InteractiveBrowser-BbG3kGA9.mjs → InteractiveBrowser-LdKzQeLR.mjs} +18 -19
  45. package/dist/InteractiveBrowser-LdKzQeLR.mjs.map +1 -0
  46. package/dist/InteractiveBrowser.mjs +1 -1
  47. package/dist/{ThreadObject-BcfXbzSO.d.mts → ThreadObject-7s5XnlH-.d.mts} +103 -79
  48. package/dist/{ThreadObject-DHjrnlX4.mjs → ThreadObject-C0vrcDrw.mjs} +265 -66
  49. package/dist/ThreadObject-C0vrcDrw.mjs.map +1 -0
  50. package/dist/ThreadObject.d.mts +2 -2
  51. package/dist/ThreadObject.mjs +2 -3
  52. package/dist/WakeScheduler.d.mts +6 -5
  53. package/dist/WakeScheduler.mjs +15 -11
  54. package/dist/WakeScheduler.mjs.map +1 -1
  55. package/dist/index.d.mts +7 -6
  56. package/dist/index.mjs +7 -6
  57. package/dist/page-function-CmUCcyNP.mjs +11 -0
  58. package/dist/page-function-CmUCcyNP.mjs.map +1 -0
  59. package/dist/{prepared-admission-DMX5p9dC.mjs → prepared-admission-3UUq0zth.mjs} +3 -3
  60. package/dist/{prepared-admission-DMX5p9dC.mjs.map → prepared-admission-3UUq0zth.mjs.map} +1 -1
  61. package/package.json +1 -1
  62. package/src/Alarm.ts +510 -279
  63. package/src/BrowserRestCapture.ts +2 -2
  64. package/src/BrowserRestCrawl.ts +10 -14
  65. package/src/BrowserSession.ts +10 -2
  66. package/src/BrowserUse.ts +1541 -0
  67. package/src/CloudflareBindings.ts +13 -4
  68. package/src/CloudflareCodeMode.ts +1 -1
  69. package/src/CloudflareConfig.ts +9 -12
  70. package/src/CloudflareMemory.ts +21 -28
  71. package/src/CloudflareScheduling.ts +7 -15
  72. package/src/CloudflareSubscriptions.ts +11 -20
  73. package/src/CloudflareThreadClient.ts +289 -9
  74. package/src/InteractiveBrowser.ts +325 -323
  75. package/src/ThreadObject.ts +172 -52
  76. package/src/WakeScheduler.ts +29 -18
  77. package/src/index.ts +1 -0
  78. package/src/internal/browser-dom.ts +845 -0
  79. package/src/internal/browser-file-selection.ts +8 -1
  80. package/src/internal/browser-jev-dom.ts +344 -0
  81. package/src/internal/browser-quick-action.ts +7 -8
  82. package/src/internal/browser-session-lifecycle.ts +2 -5
  83. package/src/internal/crypto.ts +41 -0
  84. package/src/internal/due-queue.ts +5 -69
  85. package/src/internal/layers.ts +100 -17
  86. package/src/internal/live-text-protocol.ts +51 -0
  87. package/src/internal/live-text.ts +256 -0
  88. package/src/internal/page-function.ts +12 -0
  89. package/src/internal/prepared-admission.ts +1 -1
  90. package/src/internal/progress-wait.ts +19 -24
  91. package/src/internal/transport.ts +1 -1
  92. package/dist/Alarm-B0ODr1NE.mjs.map +0 -1
  93. package/dist/BrowserSession-RfIzxGCH.mjs.map +0 -1
  94. package/dist/CloudflareBrowser-5an6m-Qb.mjs.map +0 -1
  95. package/dist/CloudflareThreadClient.mjs.map +0 -1
  96. package/dist/InteractiveBrowser-BbG3kGA9.mjs.map +0 -1
  97. package/dist/ThreadObject-DHjrnlX4.mjs.map +0 -1
@@ -22,6 +22,7 @@ import {
22
22
  DurableAgentRuntime,
23
23
  DurableRuntimeConfig,
24
24
  RecoveryReport,
25
+ RecoveryBlocked,
25
26
  type DurableSubmitAgent,
26
27
  } from "@yielded/agent/durable-agent-runtime";
27
28
  import { DurableRuntimeFailpointError } from "@yielded/agent/durable-failpoint";
@@ -48,18 +49,20 @@ import {
48
49
  ThreadRead,
49
50
  ThreadStore,
50
51
  ThreadStoreError,
51
- FenceRejected,
52
52
  } from "@yielded/agent/thread-store";
53
+ import { WorkDiscoveryUnavailable } from "@yielded/agent/thread-work";
53
54
  import { WakeScheduler } from "@yielded/agent/wake-scheduler";
54
- import { Effect, Layer, Option, Schema, Stream } from "effect";
55
+ import { Effect, Layer, Option, Schema, Stream, type Scope } from "effect";
55
56
  import {
56
57
  DurableObject as EffectCfDurableObject,
58
+ DurableObjectAlarm,
57
59
  DurableObjectState as EffectCfDurableObjectState,
58
60
  WorkerEnvironment,
59
61
  } from "effect-cf";
60
62
 
61
63
  import {
62
64
  ThreadMaintenance,
65
+ ThreadMaintenanceAlarm,
63
66
  DurableAlarmError,
64
67
  DurableAlarmService,
65
68
  ThreadMutationGate,
@@ -84,6 +87,7 @@ import {
84
87
  ProgressObserved,
85
88
  ProgressCancelled,
86
89
  SettlementReached,
90
+ SettlementRecordReached,
87
91
  SubmissionStatusResponse,
88
92
  SubmitSucceeded,
89
93
  UnknownResolutionRecorded,
@@ -109,6 +113,8 @@ import {
109
113
  type CloudflareDurableRuntimeServices,
110
114
  type CloudflareBootstrapServices,
111
115
  } from "./internal/layers.ts";
116
+ import { Request as WatchTextRequest } from "./internal/live-text-protocol.ts";
117
+ import { LiveTextHub } from "./internal/live-text.ts";
112
118
  import { ProgressWaitRegistry } from "./internal/progress-wait.ts";
113
119
 
114
120
  export {
@@ -146,11 +152,50 @@ export {
146
152
  * `runRecovery({ threadId })` BEFORE that Thread's claim; old recovery cannot gate fresh dispatch.
147
153
  */
148
154
 
155
+ type ApplicationAlarmRegistration<ApplicationServices, EventServices, AlarmServices, AlarmError> =
156
+ DurableObjectAlarm.AlarmRegistration<
157
+ AlarmServices,
158
+ | RuntimeServices
159
+ | ApplicationServices
160
+ | EventServices
161
+ | NoInfer<AlarmServices>
162
+ | EffectCfDurableObjectState.DurableObjectState
163
+ | WorkerEnvironment
164
+ | Scope.Scope,
165
+ AlarmError
166
+ >;
167
+
149
168
  /** Construction options for one deployed Thread Object class. */
150
- export interface Options<
169
+ export type Options<
151
170
  ApplicationServices = never,
152
171
  EventServices = never,
153
172
  EventLayerError = never,
173
+ AlarmServices = never,
174
+ AlarmError = never,
175
+ > = BaseOptions<ApplicationServices, EventServices, EventLayerError, AlarmServices> &
176
+ ([AlarmServices] extends [never]
177
+ ? {
178
+ readonly alarms?: ApplicationAlarmRegistration<
179
+ ApplicationServices,
180
+ EventServices,
181
+ AlarmServices,
182
+ AlarmError
183
+ >;
184
+ }
185
+ : {
186
+ readonly alarms: ApplicationAlarmRegistration<
187
+ ApplicationServices,
188
+ EventServices,
189
+ AlarmServices,
190
+ AlarmError
191
+ >;
192
+ });
193
+
194
+ interface BaseOptions<
195
+ ApplicationServices,
196
+ EventServices,
197
+ EventLayerError,
198
+ AlarmServices,
154
199
  > extends CloudflareDurableRuntimeOptions {
155
200
  /** Accept transient native RPC tracing through effect-cf; disabled by default. */
156
201
  readonly rpcTracing?: boolean;
@@ -166,6 +211,7 @@ export interface Options<
166
211
  EventLayerError,
167
212
  | RuntimeServices
168
213
  | ApplicationServices
214
+ | NoInfer<AlarmServices>
169
215
  | EffectCfDurableObjectState.DurableObjectState
170
216
  | WorkerEnvironment
171
217
  >;
@@ -192,16 +238,28 @@ const isMutatingPortRequest = (request: PortRequest): boolean => {
192
238
  case "LedgerRecordChildSettled":
193
239
  case "StoreMaterialize":
194
240
  case "StoreAppend":
241
+ case "StoreWorkRebuild":
242
+ case "StoreRangeSeal":
243
+ case "StoreRangeArchive":
244
+ case "SettlementPublish":
195
245
  case "MessageDeliveryComplete":
196
246
  return true;
197
247
  case "LedgerInspectWorker":
198
248
  case "LedgerLookup":
199
249
  case "LedgerResolveAdmission":
250
+ case "LedgerFundingOwner":
200
251
  case "StoreReadPage":
252
+ case "StoreReadPrompt":
253
+ case "StoreReadExecution":
254
+ case "StoreWorkPage":
201
255
  case "StoreInspectTail":
202
256
  case "StoreReadIdentity":
203
257
  case "StoreCountPeerMessages":
204
258
  case "StoreExport":
259
+ case "StoreRangePage":
260
+ case "StoreRangeVerify":
261
+ case "StoreVerification":
262
+ case "StoreWorkerCapacity":
205
263
  case "MessageDeliveryList":
206
264
  return false;
207
265
  }
@@ -253,7 +311,7 @@ const utf8Bytes = (value: PersistedJson): number =>
253
311
  * exempt: its accepted-work obligation already exists, and returning the original Receipt
254
312
  * consumes no new quota. Refusals are typed `AdmissionLimitExceeded` and nothing is written.
255
313
  */
256
- const gateAdmissionLimits = Effect.fn("ThreadObject.gateAdmissionLimits")(function* (
314
+ const gateAdmissionLimits = Effect.fnUntraced(function* (
257
315
  threadId: ThreadId,
258
316
  request: {
259
317
  readonly principal: SubmissionLookupByKey["principal"];
@@ -323,9 +381,7 @@ const passthroughSubmitAgent = (agentId: AgentId): DurableSubmitAgent<typeof Per
323
381
  });
324
382
 
325
383
  /** A physical owner may hold other Threads; an addressed request cannot act on their IDs. */
326
- const lookupAddressedSubmission = Effect.fn("ThreadObject.lookupAddressedSubmission")(function* (
327
- submissionId: SubmissionId,
328
- ) {
384
+ const lookupAddressedSubmission = Effect.fnUntraced(function* (submissionId: SubmissionId) {
329
385
  const { threadId } = yield* ThreadObjectIdentity;
330
386
  const ports = yield* ThreadObjectPorts;
331
387
  const submission = yield* ports.lookupSubmission(submissionId);
@@ -336,9 +392,7 @@ const lookupAddressedSubmission = Effect.fn("ThreadObject.lookupAddressedSubmiss
336
392
  return submission;
337
393
  });
338
394
 
339
- const requireSubmissionThread = Effect.fn("ThreadObject.requireSubmissionThread")(function* (
340
- submissionId: SubmissionId,
341
- ) {
395
+ const requireSubmissionThread = Effect.fnUntraced(function* (submissionId: SubmissionId) {
342
396
  const submission = yield* lookupAddressedSubmission(submissionId);
343
397
 
344
398
  if (Option.isNone(submission))
@@ -348,9 +402,7 @@ const requireSubmissionThread = Effect.fn("ThreadObject.requireSubmissionThread"
348
402
  });
349
403
  });
350
404
 
351
- const requireReceiptThread = Effect.fn("ThreadObject.requireReceiptThread")(function* (
352
- threadId: ThreadId,
353
- ) {
405
+ const requireReceiptThread = Effect.fnUntraced(function* (threadId: ThreadId) {
354
406
  const identity = yield* ThreadObjectIdentity;
355
407
 
356
408
  if (threadId !== identity.threadId)
@@ -359,6 +411,10 @@ const requireReceiptThread = Effect.fn("ThreadObject.requireReceiptThread")(func
359
411
 
360
412
  const requirePortThread = (request: PortRequest) => {
361
413
  switch (request._tag) {
414
+ case "SettlementPublish":
415
+ return requireReceiptThread(request.request.append.threadId).pipe(
416
+ Effect.andThen(requireSubmissionThread(request.request.submissionId)),
417
+ );
362
418
  case "MessageDeliveryComplete":
363
419
  return requireReceiptThread(request.key.ownerThreadId);
364
420
  case "MessageDeliveryList":
@@ -372,6 +428,10 @@ const requirePortThread = (request: PortRequest) => {
372
428
  return requireSubmissionThread(request.request.submissionId);
373
429
  case "LedgerRecordChildSettled":
374
430
  return requireSubmissionThread(request.request.parentSubmissionId);
431
+ case "LedgerFundingOwner":
432
+ return requireReceiptThread(request.request.threadId).pipe(
433
+ Effect.andThen(requireSubmissionThread(request.request.submissionId)),
434
+ );
375
435
  case "LedgerInspectWorker":
376
436
  case "LedgerStopWorker":
377
437
  case "WorkerAdmit":
@@ -380,10 +440,20 @@ const requirePortThread = (request: PortRequest) => {
380
440
  case "StoreMaterialize":
381
441
  case "StoreAppend":
382
442
  case "StoreReadPage":
443
+ case "StoreReadPrompt":
444
+ case "StoreReadExecution":
383
445
  case "StoreInspectTail":
384
446
  case "StoreReadIdentity":
385
447
  case "StoreCountPeerMessages":
386
448
  case "StoreExport":
449
+ case "StoreWorkPage":
450
+ case "StoreWorkRebuild":
451
+ case "StoreRangePage":
452
+ case "StoreRangeSeal":
453
+ case "StoreRangeArchive":
454
+ case "StoreRangeVerify":
455
+ case "StoreVerification":
456
+ case "StoreWorkerCapacity":
387
457
  return requireReceiptThread(request.request.threadId);
388
458
  }
389
459
  request satisfies never;
@@ -392,22 +462,21 @@ const requirePortThread = (request: PortRequest) => {
392
462
  /**
393
463
  * Admit an already Schema-decoded request to a logical Thread in this physical owner.
394
464
  * Custom hosts validate local placement before calling this Effect and provide their same
395
- * runtime/maintenance instances. The native endpoint uses this path too: queue limits,
465
+ * runtime, maintenance and alarm instances. The native endpoint uses this path too: queue limits,
396
466
  * idempotent receipts and the pre-admission generation/alarm commit have one owner.
397
467
  */
398
- export const submit = Effect.fn("ThreadObject.submit")(function* (
399
- threadId: ThreadId,
400
- request: SubmitRequest,
401
- ) {
468
+ export const submit = Effect.fnUntraced(function* (threadId: ThreadId, request: SubmitRequest) {
402
469
  const placement = yield* ThreadObjectPlacement;
403
470
 
404
471
  if (!placement.ownsThread(threadId))
405
472
  return yield* HostProtocolError.make({ message: "The Thread belongs to another Object" });
406
473
  const mutations = yield* ThreadMutationGate;
407
474
  const runtime = yield* DurableAgentRuntime;
475
+ const alarm = yield* DurableAlarmService;
408
476
 
409
477
  yield* gateAdmissionLimits(threadId, request);
410
478
 
479
+ // The mutation gate pre-arms durable maintenance; publication keeps its own wake behavior.
411
480
  return yield* mutations.withMutation(
412
481
  runtime
413
482
  .submit(passthroughSubmitAgent(request.agentId), request.inputPayload, {
@@ -424,7 +493,10 @@ export const submit = Effect.fn("ThreadObject.submit")(function* (
424
493
  : { messageAdmission: request.messageAdmission }),
425
494
  definitions: request.definitions,
426
495
  })
427
- .pipe(Effect.tap(() => publishCommitted)),
496
+ .pipe(
497
+ alarm.withHintsDeferred,
498
+ Effect.tap(() => publishCommitted),
499
+ ),
428
500
  );
429
501
  });
430
502
 
@@ -435,6 +507,18 @@ const submitEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoin
435
507
  Effect.gen(function* () {
436
508
  const identity = yield* ThreadObjectIdentity;
437
509
  const receipt = yield* submit(identity.threadId, request);
510
+ const scheduling = yield* DurableObjectAlarm.ScheduleConfiguration;
511
+
512
+ if (scheduling.dispatchAfterEvent === true) {
513
+ const alarms = yield* DurableAlarmService;
514
+
515
+ // Make accepted work eligible for post-response dispatch without changing queue retry floors.
516
+ yield* alarms.scheduleNow.pipe(
517
+ Effect.catch((error) =>
518
+ Effect.logWarning("Post-admission maintenance hint failed", error),
519
+ ),
520
+ );
521
+ }
438
522
 
439
523
  return SubmitSucceeded.make({ receipt });
440
524
  }),
@@ -472,6 +556,7 @@ const submissionStatusEndpoint = (
472
556
 
473
557
  const awaitSettlementEndpoint = (
474
558
  encoded: unknown,
559
+ includeRecord = false,
475
560
  ): Effect.Effect<unknown, never, EndpointServices> =>
476
561
  decodeReceipt(encoded).pipe(
477
562
  Effect.mapError(protocolFailure("The receipt could not be decoded")),
@@ -486,12 +571,23 @@ const awaitSettlementEndpoint = (
486
571
  submissionId: receipt.submissionId,
487
572
  }),
488
573
  );
574
+ if (includeRecord)
575
+ yield* authorizer.authorize(
576
+ OperationAuthorizationRequest.make({
577
+ operation: "observe",
578
+ threadId: receipt.threadId,
579
+ }),
580
+ );
489
581
  yield* requireReceiptThread(receipt.threadId);
490
582
  yield* requireSubmissionThread(receipt.submissionId);
491
583
  const runtime = yield* DurableAgentRuntime;
492
- const settlement = yield* runtime.awaitSettlement(receipt);
493
584
 
494
- return SettlementReached.make({ settlement });
585
+ if (includeRecord)
586
+ return SettlementRecordReached.make({
587
+ record: yield* runtime.awaitSettlementRecord(receipt),
588
+ });
589
+
590
+ return SettlementReached.make({ settlement: yield* runtime.awaitSettlement(receipt) });
495
591
  }),
496
592
  ),
497
593
  respond,
@@ -583,6 +679,25 @@ const observePageEndpoint = (encoded: unknown): Effect.Effect<unknown, never, En
583
679
  Effect.flatMap(encodeResponse),
584
680
  );
585
681
 
682
+ const watchTextEndpoint = (encoded: unknown): Effect.Effect<unknown, never, EndpointServices> =>
683
+ Schema.decodeUnknownEffect(WatchTextRequest)(encoded).pipe(
684
+ Effect.mapError(protocolFailure("The provisional text request could not be decoded")),
685
+ Effect.flatMap(() =>
686
+ Effect.gen(function* () {
687
+ const { threadId } = yield* ThreadObjectIdentity;
688
+ const authorizer = yield* OperationAuthorizer;
689
+
690
+ yield* authorizer.authorize(
691
+ OperationAuthorizationRequest.make({ operation: "observe", threadId }),
692
+ );
693
+ const hub = yield* LiveTextHub;
694
+
695
+ return yield* hub.open(threadId);
696
+ }),
697
+ ),
698
+ Effect.catch((failure) => encodeResponse(HostFailed.make({ failure }))),
699
+ );
700
+
586
701
  const abortEndpoint = (encoded: unknown): Effect.Effect<unknown, never, EndpointServices> =>
587
702
  decodeAbortCommand(encoded).pipe(
588
703
  Effect.mapError(protocolFailure("The abort command could not be decoded")),
@@ -696,10 +811,11 @@ export const AdminFailure = Schema.Union([
696
811
  ThreadStoreError,
697
812
  ThreadNotMaterialized,
698
813
  AppendConflict,
699
- FenceRejected,
700
814
  DurableRuntimeFailpointError,
701
815
  DurableAlarmError,
702
816
  HostProtocolError,
817
+ RecoveryBlocked,
818
+ WorkDiscoveryUnavailable,
703
819
  ]);
704
820
 
705
821
  export type AdminFailure = typeof AdminFailure.Type;
@@ -955,8 +1071,10 @@ export const ThreadRpcOperation = Schema.Literals([
955
1071
  "submitEncoded",
956
1072
  "submissionStatusEncoded",
957
1073
  "awaitSettlementEncoded",
1074
+ "awaitSettlementRecordEncoded",
958
1075
  "awaitProgressEncoded",
959
1076
  "cancelProgressEncoded",
1077
+ "watchTextEncoded",
960
1078
  "observePage",
961
1079
  "abortEncoded",
962
1080
  "resolveApprovalEncoded",
@@ -971,8 +1089,10 @@ const threadRpc = {
971
1089
  submitEncoded: submitEndpoint,
972
1090
  submissionStatusEncoded: submissionStatusEndpoint,
973
1091
  awaitSettlementEncoded: awaitSettlementEndpoint,
1092
+ awaitSettlementRecordEncoded: (encoded) => awaitSettlementEndpoint(encoded, true),
974
1093
  awaitProgressEncoded: awaitProgressEndpoint,
975
1094
  cancelProgressEncoded: cancelProgressEndpoint,
1095
+ watchTextEncoded: watchTextEndpoint,
976
1096
  observePage: observePageEndpoint,
977
1097
  abortEncoded: abortEndpoint,
978
1098
  resolveApprovalEncoded: resolveApprovalEndpoint,
@@ -991,7 +1111,7 @@ const threadRpc = {
991
1111
  * The producer comes from the actual runtime configuration, never from caller input. These
992
1112
  * guards supplement the existing current model/Tool and operation authorization policies.
993
1113
  */
994
- export const handleRpc = Effect.fn("ThreadObject.handleRpc")(function* (
1114
+ export const handleRpc = Effect.fnUntraced(function* (
995
1115
  threadId: ThreadId,
996
1116
  operation: ThreadRpcOperation,
997
1117
  encoded: unknown,
@@ -1007,15 +1127,11 @@ export const handleRpc = Effect.fn("ThreadObject.handleRpc")(function* (
1007
1127
  );
1008
1128
  });
1009
1129
 
1010
- const alarmEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
1011
- function* () {
1012
- const maintenance = yield* ThreadMaintenance;
1013
-
1014
- // Typed pass failures propagate: the rejected promise makes workerd retry the alarm
1015
- // (at-least-once delivery), and the dirty generation retains a committed slot meanwhile.
1016
- yield* maintenance.pass;
1017
- },
1018
- );
1130
+ /** Compose with application registrations on a shared physical object. */
1131
+ export const alarms = ThreadMaintenanceAlarm.handlers({
1132
+ "@yielded/agent/thread-maintenance": () =>
1133
+ Effect.flatMap(ThreadMaintenance, (maintenance) => Effect.asVoid(maintenance.pass)),
1134
+ });
1019
1135
 
1020
1136
  const gateEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
1021
1137
  function* () {
@@ -1067,13 +1183,18 @@ const effectCfPlatformLayer = (
1067
1183
 
1068
1184
  /** The public endpoints and effect-cf invocation hook of one Thread Object instance. */
1069
1185
  export interface Instance<EventServices = never> extends InstanceType<
1070
- EffectCfDurableObject.DurableObjectClass<Record<never, never>, RuntimeServices | EventServices>
1186
+ EffectCfDurableObject.DurableObjectClass<
1187
+ Record<never, never>,
1188
+ RuntimeServices | EventServices | ThreadMaintenanceAlarm
1189
+ >
1071
1190
  > {
1072
1191
  submitEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1073
1192
  submissionStatusEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1074
1193
  awaitSettlementEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1194
+ awaitSettlementRecordEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1075
1195
  awaitProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1076
1196
  cancelProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1197
+ watchTextEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1077
1198
  observePage(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1078
1199
  abortEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
1079
1200
  resolveApprovalEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
@@ -1108,18 +1229,22 @@ export const make = <
1108
1229
  ApplicationError,
1109
1230
  EventServices = never,
1110
1231
  EventLayerError = never,
1232
+ AlarmServices = never,
1233
+ AlarmError = never,
1111
1234
  >(
1112
1235
  applicationLayer: Layer.Layer<
1113
1236
  CloudflareDurableRuntimeServices | ApplicationServices,
1114
1237
  ApplicationError,
1115
1238
  | CloudflareBootstrapServices
1239
+ | ThreadMaintenanceAlarm
1240
+ | NoInfer<AlarmServices>
1116
1241
  | EffectCfDurableObjectState.DurableObjectState
1117
1242
  | WorkerEnvironment
1118
1243
  | DurableObjectContext
1119
1244
  | ThreadObjectNamespace
1120
1245
  >,
1121
- options: Options<ApplicationServices, EventServices, EventLayerError>,
1122
- ): Class<ApplicationServices | EventServices> => {
1246
+ options: Options<ApplicationServices, EventServices, EventLayerError, AlarmServices, AlarmError>,
1247
+ ): Class<ApplicationServices | EventServices | AlarmServices> => {
1123
1248
  const application = applicationLayer.pipe(
1124
1249
  Layer.provideMerge(layerConfig(options)),
1125
1250
  Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding, options.rpcTracing)),
@@ -1131,7 +1256,10 @@ export const make = <
1131
1256
  const runtime: Layer.Layer<
1132
1257
  RuntimeServices | ApplicationServices,
1133
1258
  ThreadObjectInitializationError | ApplicationError,
1134
- EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
1259
+ | EffectCfDurableObjectState.DurableObjectState
1260
+ | WorkerEnvironment
1261
+ | ThreadMaintenanceAlarm
1262
+ | AlarmServices
1135
1263
  > = Layer.effectContext(
1136
1264
  Effect.gen(function* () {
1137
1265
  const state = yield* EffectCfDurableObjectState.DurableObjectState;
@@ -1163,15 +1291,17 @@ export const make = <
1163
1291
  RuntimeServices | ApplicationServices,
1164
1292
  EventServices,
1165
1293
  EventLayerError,
1166
- typeof rpc
1294
+ typeof rpc,
1295
+ ThreadMaintenanceAlarm | AlarmServices
1167
1296
  >;
1168
1297
 
1169
- const EffectCfThreadObject = EffectCfDurableObject.make<
1298
+ return EffectCfDurableObject.make<
1170
1299
  RuntimeServices | ApplicationServices,
1171
1300
  ThreadObjectInitializationError | ApplicationError,
1172
1301
  EventServices,
1173
1302
  EventLayerError,
1174
- typeof rpc
1303
+ typeof rpc,
1304
+ ThreadMaintenanceAlarm | AlarmServices
1175
1305
  >(runtime, {
1176
1306
  ...(options.rpcTracing === true ? { rpcTracing: { service: options.namespaceBinding } } : {}),
1177
1307
  ...(options.eventLayer === undefined ? {} : { eventLayer: options.eventLayer }),
@@ -1179,20 +1309,10 @@ export const make = <
1179
1309
  // in each bounded pass so cross-Object initialization cannot deadlock.
1180
1310
  initialize: Effect.void,
1181
1311
  rpc,
1182
- alarm: () => alarmEndpoint,
1183
- // This host owns the raw alarm and supplies event services through options.eventLayer.
1312
+ alarms:
1313
+ options.alarms === undefined ? alarms : DurableObjectAlarm.mergeAll(options.alarms, alarms),
1314
+ // The named registration supplies its scheduler before the constructor builds application services.
1184
1315
  // Upstream's conditional alarm-registration check cannot reduce over generic application
1185
1316
  // services. Options and the rpc satisfies check above retain their Effect requirements.
1186
1317
  } as NativeOptions);
1187
-
1188
- // effect-cf's class type keeps `alarm` optional even when the handler option is present. This
1189
- // concrete override reflects this factory's stronger contract while delegating execution to
1190
- // the effect-cf runtime unchanged.
1191
- class ThreadObject extends EffectCfThreadObject {
1192
- override alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void {
1193
- return super.alarm?.(alarmInfo);
1194
- }
1195
- }
1196
-
1197
- return ThreadObject;
1198
1318
  };
@@ -11,8 +11,8 @@ import {
11
11
  import { safeCauseMessage } from "./internal/boundary.ts";
12
12
 
13
13
  /**
14
- * Bounded in-memory wake buffer for same-incarnation `awaitSettlement` subscribers. Wake
15
- * hints are droppable by contract (consumers pair every subscription with ledger polls), so
14
+ * Bounded in-memory wake buffer for same-incarnation workers. Wake
15
+ * hints are droppable by contract (consumers pair hints with durable authority), so
16
16
  * a full buffer slides out the oldest hint and an eviction simply loses the buffer — the
17
17
  * poll interval and the persisted alarm keep liveness (persistence §14).
18
18
  */
@@ -28,14 +28,15 @@ class RemoteWakeDropped extends Schema.TaggedError<RemoteWakeDropped>()("RemoteW
28
28
  /**
29
29
  * The DC `WakeScheduler` (plan §1.4):
30
30
  *
31
- * - `notify(local)` → `setAlarm(now)` — durable, cheap, coalescing (the alarm slot keeps the
32
- * earliest deadline) — plus a same-incarnation PubSub hint for `awaitSettlement` waiters.
31
+ * - `notify(local)` wakes progress and settlement registrations, publishes a worker hint, and
32
+ * schedules the earliest alarm. Processing an owned Thread defers immediate alarms until
33
+ * processing exits; pre-armed recovery deadlines remain active. Progress hints skip settlement
34
+ * registrations only.
33
35
  * - `notify(remote)` → fire-and-forget `wake()` on the owning Object's stub with every error
34
36
  * swallowed and logged: hints are droppable, and the target's own alarm/scan pairing (the
35
37
  * maintenance pass re-polls unsettled children) guarantees liveness without this call.
36
- * - `wakes` → the bounded sliding PubSub only. No important state lives in memory: the
37
- * subscription accelerates waiters within one incarnation and the settlement poll interval
38
- * is the correctness path.
38
+ * - `wakes` → the bounded sliding PubSub only. Thread waiters use Scope-owned one-shot hubs;
39
+ * the settlement poll interval and persisted alarm retain liveness after hint loss.
39
40
  */
40
41
  export const cloudflareWakeSchedulerLayer: Layer.Layer<
41
42
  WakeScheduler,
@@ -48,18 +49,23 @@ export const cloudflareWakeSchedulerLayer: Layer.Layer<
48
49
  const namespace = yield* ThreadObjectNamespace;
49
50
  const hints = yield* PubSub.sliding<ThreadId>(WAKE_BUFFER_CAPACITY);
50
51
  const progress = yield* makeWakeSubscriptionHub;
52
+ const settlements = yield* makeWakeSubscriptionHub;
51
53
 
52
54
  yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
53
55
 
54
- const notifyLocal = (threadId: ThreadId) =>
55
- progress.notify(threadId).pipe(
56
+ const scheduleLocal = alarm.scheduleNow.pipe(
57
+ Effect.catch((error) =>
58
+ Effect.logWarning("CloudflareWakeScheduler: local alarm wake failed", error),
59
+ ),
60
+ );
61
+
62
+ const notifyLocal = (threadId: ThreadId, kind?: "progress") =>
63
+ (kind === "progress"
64
+ ? progress.notify(threadId)
65
+ : settlements.notify(threadId).pipe(Effect.andThen(progress.notify(threadId)))
66
+ ).pipe(
56
67
  Effect.andThen(PubSub.publish(hints, threadId)),
57
- Effect.andThen(alarm.scheduleNow),
58
- Effect.catch((error) =>
59
- // `notify` never fails by contract; a failed alarm write degrades to "hint lost"
60
- // and the pass re-arm (or the next entry point's pre-arm) restores the invariant.
61
- Effect.logWarning("CloudflareWakeScheduler: local alarm wake failed", error),
62
- ),
68
+ Effect.andThen(scheduleLocal),
63
69
  Effect.asVoid,
64
70
  );
65
71
 
@@ -82,9 +88,14 @@ export const cloudflareWakeSchedulerLayer: Layer.Layer<
82
88
  );
83
89
 
84
90
  return WakeScheduler.of({
85
- notify: (threadId) =>
86
- placement.ownsThread(threadId) ? notifyLocal(threadId) : notifyRemote(threadId),
87
- subscribe: progress.subscribe,
91
+ withProcessing: (threadId, body) =>
92
+ placement.ownsThread(threadId)
93
+ ? alarm.withHintsDeferred(body).pipe(Effect.ensuring(scheduleLocal))
94
+ : body,
95
+ notify: (threadId, kind) =>
96
+ placement.ownsThread(threadId) ? notifyLocal(threadId, kind) : notifyRemote(threadId),
97
+ subscribe: (threadId, kind) =>
98
+ (kind === "settlement" ? settlements : progress).subscribe(threadId),
88
99
  wakes: Stream.fromPubSub(hints),
89
100
  });
90
101
  }),
package/src/index.ts CHANGED
@@ -12,3 +12,4 @@ export * as WakeScheduler from "./WakeScheduler.ts";
12
12
  export * as CloudflareAiGateway from "./CloudflareAiGateway.ts";
13
13
  export * as BrowserSession from "./BrowserSession.ts";
14
14
  export * as BrowserCredentials from "./BrowserCredentials.ts";
15
+ export * as BrowserUse from "./BrowserUse.ts";