@demlik/tea 0.1.0 → 0.1.2

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 (243) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/index.d.ts +907 -0
  3. package/dist/agent/index.js +20 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/authed-call/index.d.ts +119 -0
  6. package/dist/authed-call/index.js +148 -0
  7. package/dist/authed-call/index.js.map +1 -0
  8. package/dist/await-terminal/index.d.ts +99 -0
  9. package/dist/await-terminal/index.js +75 -0
  10. package/dist/await-terminal/index.js.map +1 -0
  11. package/dist/batch-window/index.d.ts +259 -0
  12. package/dist/batch-window/index.js +43 -0
  13. package/dist/batch-window/index.js.map +1 -0
  14. package/dist/cache/index.d.ts +2 -2
  15. package/dist/cache/index.js +2 -50
  16. package/dist/cache/index.js.map +1 -1
  17. package/dist/chunk-27N2SPTN.js +222 -0
  18. package/dist/chunk-27N2SPTN.js.map +1 -0
  19. package/dist/chunk-2YOE5PIS.js +45 -0
  20. package/dist/chunk-2YOE5PIS.js.map +1 -0
  21. package/dist/chunk-3545BOYL.js +65 -0
  22. package/dist/chunk-3545BOYL.js.map +1 -0
  23. package/dist/chunk-3XJWC7VW.js +61 -0
  24. package/dist/chunk-3XJWC7VW.js.map +1 -0
  25. package/dist/chunk-5DEQ5I3U.js +132 -0
  26. package/dist/chunk-5DEQ5I3U.js.map +1 -0
  27. package/dist/chunk-5VNWZADP.js +49 -0
  28. package/dist/chunk-5VNWZADP.js.map +1 -0
  29. package/dist/chunk-6RGBKODV.js +268 -0
  30. package/dist/chunk-6RGBKODV.js.map +1 -0
  31. package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
  32. package/dist/chunk-7FYQGDMF.js.map +1 -0
  33. package/dist/chunk-7W4LMPUW.js +186 -0
  34. package/dist/chunk-7W4LMPUW.js.map +1 -0
  35. package/dist/chunk-BYQUGDD3.js +16 -0
  36. package/dist/chunk-BYQUGDD3.js.map +1 -0
  37. package/dist/{chunk-PYBQREFU.js → chunk-DILMKOZ7.js} +22 -26
  38. package/dist/chunk-DILMKOZ7.js.map +1 -0
  39. package/dist/chunk-EHIX7NYZ.js +93 -0
  40. package/dist/chunk-EHIX7NYZ.js.map +1 -0
  41. package/dist/chunk-HCEVHFJ7.js +54 -0
  42. package/dist/chunk-HCEVHFJ7.js.map +1 -0
  43. package/dist/chunk-HJO6VNQI.js +15 -0
  44. package/dist/chunk-HJO6VNQI.js.map +1 -0
  45. package/dist/chunk-IN33Q4VJ.js +52 -0
  46. package/dist/chunk-IN33Q4VJ.js.map +1 -0
  47. package/dist/chunk-J6527VRQ.js +56 -0
  48. package/dist/chunk-J6527VRQ.js.map +1 -0
  49. package/dist/chunk-JELSZN25.js +47 -0
  50. package/dist/chunk-JELSZN25.js.map +1 -0
  51. package/dist/chunk-K575PT4S.js +58 -0
  52. package/dist/chunk-K575PT4S.js.map +1 -0
  53. package/dist/chunk-K7Q23EQ4.js +20 -0
  54. package/dist/chunk-K7Q23EQ4.js.map +1 -0
  55. package/dist/chunk-LEC4U6CX.js +91 -0
  56. package/dist/chunk-LEC4U6CX.js.map +1 -0
  57. package/dist/{chunk-PYOCUBQJ.js → chunk-LP7VQBRG.js} +22 -15
  58. package/dist/chunk-LP7VQBRG.js.map +1 -0
  59. package/dist/{chunk-CMG2KMWX.js → chunk-MMH7CW5K.js} +220 -189
  60. package/dist/chunk-MMH7CW5K.js.map +1 -0
  61. package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
  62. package/dist/chunk-NSDFA6M6.js.map +1 -0
  63. package/dist/chunk-ON24RKSE.js +60 -0
  64. package/dist/chunk-ON24RKSE.js.map +1 -0
  65. package/dist/chunk-PS23H6KQ.js +87 -0
  66. package/dist/chunk-PS23H6KQ.js.map +1 -0
  67. package/dist/chunk-QLA76GIT.js +162 -0
  68. package/dist/chunk-QLA76GIT.js.map +1 -0
  69. package/dist/chunk-S3FQN4SL.js +506 -0
  70. package/dist/chunk-S3FQN4SL.js.map +1 -0
  71. package/dist/chunk-SC3FAFZN.js +53 -0
  72. package/dist/chunk-SC3FAFZN.js.map +1 -0
  73. package/dist/chunk-SINXE47Y.js +55 -0
  74. package/dist/chunk-SINXE47Y.js.map +1 -0
  75. package/dist/chunk-SQTCS44W.js +33 -0
  76. package/dist/chunk-SQTCS44W.js.map +1 -0
  77. package/dist/chunk-TGG26XMK.js +52 -0
  78. package/dist/chunk-TGG26XMK.js.map +1 -0
  79. package/dist/chunk-UV6UWGEO.js +147 -0
  80. package/dist/chunk-UV6UWGEO.js.map +1 -0
  81. package/dist/chunk-UYWUVEP6.js +158 -0
  82. package/dist/chunk-UYWUVEP6.js.map +1 -0
  83. package/dist/chunk-VLGARN6H.js +202 -0
  84. package/dist/chunk-VLGARN6H.js.map +1 -0
  85. package/dist/circuit-breaker/index.js +1 -51
  86. package/dist/circuit-breaker/index.js.map +1 -1
  87. package/dist/core-BoB6t7dB.d.ts +188 -0
  88. package/dist/deadline/index.d.ts +24 -5
  89. package/dist/deadline/index.js +4 -18
  90. package/dist/deadline/index.js.map +1 -1
  91. package/dist/debounce/index.d.ts +6 -0
  92. package/dist/debounce/index.js +1 -54
  93. package/dist/debounce/index.js.map +1 -1
  94. package/dist/devtools/index.d.ts +1 -1
  95. package/dist/devtools/index.js +18 -6
  96. package/dist/devtools/index.js.map +1 -1
  97. package/dist/do/index.d.ts +1723 -92
  98. package/dist/do/index.js +673 -31
  99. package/dist/do/index.js.map +1 -1
  100. package/dist/durable-effects-CO9Uh971.d.ts +180 -0
  101. package/dist/extension/index.d.ts +31 -13
  102. package/dist/extension/index.js +5 -3
  103. package/dist/extension/index.js.map +1 -1
  104. package/dist/extension/react.js +8 -3
  105. package/dist/extension/react.js.map +1 -1
  106. package/dist/extension/subs/index.d.ts +2 -2
  107. package/dist/extension/subs/index.js.map +1 -1
  108. package/dist/extension/test-utils.js +11 -3
  109. package/dist/extension/test-utils.js.map +1 -1
  110. package/dist/fan-out/index.d.ts +239 -0
  111. package/dist/fan-out/index.js +5 -0
  112. package/dist/fan-out/index.js.map +1 -0
  113. package/dist/idempotency/adapter.d.ts +62 -0
  114. package/dist/idempotency/adapter.js +4 -0
  115. package/dist/idempotency/adapter.js.map +1 -0
  116. package/dist/idempotency/index.js +1 -56
  117. package/dist/idempotency/index.js.map +1 -1
  118. package/dist/idempotent-intake/index.d.ts +186 -0
  119. package/dist/idempotent-intake/index.js +7 -0
  120. package/dist/idempotent-intake/index.js.map +1 -0
  121. package/dist/index.d.ts +341 -191
  122. package/dist/index.js +2 -1
  123. package/dist/llm-call/index.d.ts +309 -0
  124. package/dist/llm-call/index.js +14 -0
  125. package/dist/llm-call/index.js.map +1 -0
  126. package/dist/machine-viz/index.d.ts +4 -4
  127. package/dist/machine-viz/index.js +5 -9
  128. package/dist/machine-viz/index.js.map +1 -1
  129. package/dist/mem/index.d.ts +1 -0
  130. package/dist/monitored-run/index.d.ts +253 -0
  131. package/dist/monitored-run/index.js +10 -0
  132. package/dist/monitored-run/index.js.map +1 -0
  133. package/dist/node/index.d.ts +5 -5
  134. package/dist/node/index.js.map +1 -1
  135. package/dist/paginated-walk/index.d.ts +234 -0
  136. package/dist/paginated-walk/index.js +15 -0
  137. package/dist/paginated-walk/index.js.map +1 -0
  138. package/dist/paginator/index.d.ts +271 -0
  139. package/dist/paginator/index.js +3 -0
  140. package/dist/paginator/index.js.map +1 -0
  141. package/dist/parity/index.d.ts +92 -0
  142. package/dist/parity/index.js +78 -0
  143. package/dist/parity/index.js.map +1 -0
  144. package/dist/pbt/arbitraries/index.d.ts +1 -1
  145. package/dist/pbt/arbitraries/index.js +1 -1
  146. package/dist/pbt/index.d.ts +15 -8
  147. package/dist/pbt/index.js +6 -11
  148. package/dist/pbt/index.js.map +1 -1
  149. package/dist/pbt/runners/index.d.ts +1 -1
  150. package/dist/pbt/runners/index.js +3 -2
  151. package/dist/poller/index.d.ts +288 -0
  152. package/dist/poller/index.js +79 -0
  153. package/dist/poller/index.js.map +1 -0
  154. package/dist/prediction/index.d.ts +130 -0
  155. package/dist/prediction/index.js +4 -0
  156. package/dist/prediction/index.js.map +1 -0
  157. package/dist/protocol-B5YmJr0d.d.ts +45 -0
  158. package/dist/pure/index.d.ts +2 -0
  159. package/dist/pure/index.js +4 -0
  160. package/dist/pure/index.js.map +1 -0
  161. package/dist/raft/demo.d.ts +105 -0
  162. package/dist/raft/demo.js +172 -0
  163. package/dist/raft/demo.js.map +1 -0
  164. package/dist/raft/index.d.ts +548 -0
  165. package/dist/raft/index.js +7 -0
  166. package/dist/raft/index.js.map +1 -0
  167. package/dist/raft/sim.d.ts +222 -0
  168. package/dist/raft/sim.js +8 -0
  169. package/dist/raft/sim.js.map +1 -0
  170. package/dist/raft/timeline.d.ts +46 -0
  171. package/dist/raft/timeline.js +62 -0
  172. package/dist/raft/timeline.js.map +1 -0
  173. package/dist/rate-limit/index.js +1 -43
  174. package/dist/rate-limit/index.js.map +1 -1
  175. package/dist/react/index.d.ts +2 -1
  176. package/dist/react/index.js +43 -20
  177. package/dist/react/index.js.map +1 -1
  178. package/dist/reconciler/index.d.ts +192 -0
  179. package/dist/reconciler/index.js +166 -0
  180. package/dist/reconciler/index.js.map +1 -0
  181. package/dist/recorder/index.d.ts +7 -3
  182. package/dist/recorder/index.js +1 -155
  183. package/dist/recorder/index.js.map +1 -1
  184. package/dist/resilient-call/index.d.ts +294 -0
  185. package/dist/resilient-call/index.js +13 -0
  186. package/dist/resilient-call/index.js.map +1 -0
  187. package/dist/retry-backoff/index.d.ts +42 -5
  188. package/dist/retry-backoff/index.js +1 -35
  189. package/dist/retry-backoff/index.js.map +1 -1
  190. package/dist/retry-to-success/index.d.ts +109 -0
  191. package/dist/retry-to-success/index.js +35 -0
  192. package/dist/retry-to-success/index.js.map +1 -0
  193. package/dist/saga/index.d.ts +238 -0
  194. package/dist/saga/index.js +150 -0
  195. package/dist/saga/index.js.map +1 -0
  196. package/dist/snapshot/index.d.ts +334 -0
  197. package/dist/snapshot/index.js +5 -0
  198. package/dist/snapshot/index.js.map +1 -0
  199. package/dist/subs/index.d.ts +48 -10
  200. package/dist/subs/index.js +1 -1
  201. package/dist/testing/index.d.ts +107 -2
  202. package/dist/testing/index.js +204 -6
  203. package/dist/testing/index.js.map +1 -1
  204. package/dist/throttle/index.d.ts +9 -0
  205. package/dist/throttle/index.js +1 -52
  206. package/dist/throttle/index.js.map +1 -1
  207. package/dist/throttled-input/index.d.ts +380 -0
  208. package/dist/throttled-input/index.js +88 -0
  209. package/dist/throttled-input/index.js.map +1 -0
  210. package/dist/token-refresh/index.d.ts +215 -0
  211. package/dist/token-refresh/index.js +6 -0
  212. package/dist/token-refresh/index.js.map +1 -0
  213. package/dist/trace-replay/index.d.ts +11 -2
  214. package/dist/trace-replay/index.js +3 -55
  215. package/dist/trace-replay/index.js.map +1 -1
  216. package/dist/{types-CwBC5wMn.d.ts → types-B-8bcAp8.d.ts} +1 -1
  217. package/dist/with-deadline/index.d.ts +196 -0
  218. package/dist/with-deadline/index.js +111 -0
  219. package/dist/with-deadline/index.js.map +1 -0
  220. package/dist/with-resilience/index.d.ts +205 -0
  221. package/dist/with-resilience/index.js +251 -0
  222. package/dist/with-resilience/index.js.map +1 -0
  223. package/dist/with-telemetry/index.d.ts +149 -0
  224. package/dist/with-telemetry/index.js +70 -0
  225. package/dist/with-telemetry/index.js.map +1 -0
  226. package/dist/work-queue/adapter.d.ts +82 -0
  227. package/dist/work-queue/adapter.js +4 -0
  228. package/dist/work-queue/adapter.js.map +1 -0
  229. package/dist/work-queue/index.d.ts +25 -11
  230. package/dist/work-queue/index.js +1 -46
  231. package/dist/work-queue/index.js.map +1 -1
  232. package/dist/work-queue/ops.d.ts +69 -0
  233. package/dist/work-queue/ops.js +3 -0
  234. package/dist/work-queue/ops.js.map +1 -0
  235. package/dist/workflow/index.d.ts +450 -0
  236. package/dist/workflow/index.js +225 -0
  237. package/dist/workflow/index.js.map +1 -0
  238. package/package.json +168 -25
  239. package/dist/chunk-CMG2KMWX.js.map +0 -1
  240. package/dist/chunk-PYBQREFU.js.map +0 -1
  241. package/dist/chunk-PYOCUBQJ.js.map +0 -1
  242. package/dist/chunk-Y5JM3UUH.js.map +0 -1
  243. package/dist/chunk-ZRUST6BK.js.map +0 -1
@@ -1,9 +1,1676 @@
1
- import { Store, SubId, Sub } from '../index.js';
1
+ import { Store, Runtime, BootingRuntime } from '../index.js';
2
+ import { P as PendingEffectsRecorder, b as EffectOwed, a as EffectConfirmed } from '../durable-effects-CO9Uh971.js';
3
+ export { D as DeliveryId, E as EffectLedgerEvent, O as OwedEffect, d as PendingEffectsLedger, c as applyEffectEvent, e as emptyLedger, f as foldLedger, i as isOwed, p as pendingEffectsLedger, s as survivingEffects } from '../durable-effects-CO9Uh971.js';
4
+ import { RetryPolicy, Rng } from '../retry-backoff/index.js';
5
+ import { C as Cmd, S as Sub, M as Machine } from '../core-BoB6t7dB.js';
6
+ import { AgentTurn, AgentState, AgentMachineMsg, AgentEvent, AgentStatus } from '../agent/index.js';
7
+ import '../fan-out/index.js';
8
+ import '../work-queue/index.js';
9
+ import '../llm-call/index.js';
10
+ import '../resilient-call/index.js';
11
+ import '../cache/index.js';
12
+ import '../types-B-8bcAp8.js';
13
+ import '../circuit-breaker/index.js';
14
+ import '../deadline/index.js';
15
+ import '../protocol-B5YmJr0d.js';
16
+ import '../rate-limit/index.js';
17
+ import '../monitored-run/index.js';
18
+ import '../snapshot/index.js';
19
+
20
+ /**
21
+ * @demlik/tea/do — `stepHost`, the HTTP-PULL control carrier (#92).
22
+ *
23
+ * ## Carrier selection — pick by WHO INITIATES the message
24
+ *
25
+ * `stepHost` is a SIBLING to {@link acceptCommandSocket}, never a replacement.
26
+ * Both carriers commonly run together in ONE Durable Object; choose between
27
+ * them per *plane*, by who sends the next message:
28
+ *
29
+ * - **Pull (this file — `stepHost` over `/step`)** — the CONTROL plane. One
30
+ * driver (the hands) initiates every message: it asks for the next tool,
31
+ * runs it, posts the result, asks again. There is exactly one outstanding
32
+ * question at a time and the DO is COLD between steps — nothing is owed
33
+ * while no request is in flight. Hibernation is the resting state, not a
34
+ * failure: each POST wakes the DO, advances the run one step from its
35
+ * durable checkpoint, and lets it hibernate again.
36
+ * - **Push (`acceptCommandSocket` / `sseHub` — WS + SSE)** — the VIEW /
37
+ * fan-out plane. The DO initiates UNSOLICITED messages: broadcast a frame
38
+ * to every connected client, stream a live dashboard, push an event no
39
+ * client asked for. A long-lived socket is the right carrier because the
40
+ * sender is the DO, not a polling client.
41
+ *
42
+ * The rule in one line: **pull when the client drives a cold-between-steps
43
+ * control loop; push when the DO drives unsolicited fan-out.** A hibernating
44
+ * agent whose only traffic is "run the next tool" wants pull — that is the case
45
+ * Binclusive #1812 / #1814 adopted this carrier for.
46
+ *
47
+ * ## Why pull is hibernation-correct
48
+ *
49
+ * The WS push carrier keeps a socket open across steps; if the DO evicts, the
50
+ * socket dies (or, with #91's `acceptDurableCommandSocket`, survives but still
51
+ * holds an in-flight round-trip the heap forgot). The pull carrier holds NO
52
+ * cross-step state in the heap. Every `/step` POST is a FRESH request that:
53
+ *
54
+ * 1. **validates** the per-run capability token (constant-time compare),
55
+ * 2. **re-arms** the eviction-safe give-up alarm via `ctx.storage.setAlarm`
56
+ * (the DO-native alarm API — the `do_alarm` Sub was dropped, see the
57
+ * `host.ts` header; the alarm is a host concern, not a machine Sub),
58
+ * 3. **settles** the posted tool result IDEMPOTENTLY through
59
+ * `idempotent-intake` — a re-POSTed result is absorbed exactly once and
60
+ * the duplicate replays the same next step,
61
+ * 4. **resumes** the run from its durable checkpoint (the #68 event-sourced
62
+ * store / #91 in-flight ledger) by dispatching the settle and running to
63
+ * DISPATCH-QUIESCENCE,
64
+ * 5. **returns** the next step (the next tool call) — or the terminal output
65
+ * read off `runtime.result()` — and lets the DO HIBERNATE.
66
+ *
67
+ * No warm loop is held between responses. The durable checkpoint IS the loop's
68
+ * continuation; the heap is disposable.
69
+ *
70
+ * ## It is ASSEMBLY over bricks tea already ships
71
+ *
72
+ * - `idempotent-intake` (`createIntake`) — the dedupe-by-key receive-once
73
+ * settle. The posted result's `callId` is the idempotency key, so a
74
+ * re-POST lands as a duplicate and replays the cached decision.
75
+ * - `poller` primitives (`retry-backoff`) — the hands-side {@link runStepLoop}
76
+ * pull loop: absolute deadline + backoff + dedupe-by-callId.
77
+ * - the #68/#91 durable checkpoint — read by the consumer's `resume` callback
78
+ * (it dispatches the settle into the runtime, whose `doEventSourcedStore`
79
+ * rebuilt state from the log on this cold wake).
80
+ *
81
+ * This file owns NONE of those primitives — it wires them into the `/step`
82
+ * request/response contract and the token lifecycle.
83
+ */
84
+
85
+ /**
86
+ * The tool result the hands POST back for the step they just executed. `callId`
87
+ * matches the step the previous response handed out (it is BOTH the correlation
88
+ * id and the idempotency key — a re-POST carries the same `callId` and is
89
+ * absorbed once). `result` is the opaque tool output `R`.
90
+ */
91
+ interface StepResult<R> {
92
+ readonly callId: string;
93
+ readonly result: R;
94
+ }
95
+ /**
96
+ * A `/step` request. `token` is the per-run capability (validated
97
+ * constant-time). `runId` names the run. `posted` is the result of the
98
+ * previously-returned step, or `null` on the FIRST request (no step has been
99
+ * handed out yet, so there is nothing to settle).
100
+ */
101
+ interface StepRequest<R> {
102
+ readonly token: string;
103
+ readonly runId: string;
104
+ readonly posted: StepResult<R> | null;
105
+ }
106
+ /**
107
+ * The next tool call to execute — the "continue" arm of a `/step` response.
108
+ * `callId` is the correlation/idempotency id the hands echo back in the next
109
+ * request's `posted`. `tool` + `input` describe the call (opaque to the host).
110
+ */
111
+ interface NextStep<I> {
112
+ readonly callId: string;
113
+ readonly tool: string;
114
+ readonly input: I;
115
+ }
116
+ /**
117
+ * A `/step` response — a discriminated union on `done`:
118
+ * - `{ done: false, step }` — execute `step`, POST its result, ask again.
119
+ * - `{ done: true, output }` — the run is terminal; `output` is its result.
120
+ */
121
+ type StepResponse<I, O> = {
122
+ readonly done: false;
123
+ readonly step: NextStep<I>;
124
+ } | {
125
+ readonly done: true;
126
+ readonly output: O;
127
+ };
128
+ /**
129
+ * Mint a fresh per-run capability token: 32 random bytes, hex-encoded. Uses the
130
+ * Web Crypto `crypto.getRandomValues` available in the Workers runtime (and in
131
+ * Node's test runtime via the global `crypto`). Persist the returned string in
132
+ * DO storage keyed by runId; hand the SAME string to the hands once, at run
133
+ * start.
134
+ */
135
+ declare function mintRunToken(randomBytes?: (n: number) => Uint8Array): string;
136
+ /**
137
+ * Constant-time string equality. Returns `true` iff `a` and `b` are
138
+ * byte-for-byte equal, in time that depends ONLY on `a.length` — never on WHERE
139
+ * the first mismatch is. A length mismatch still returns `false`, but the
140
+ * comparison loop always runs `a.length` iterations against a same-length view,
141
+ * so the early-exit timing oracle a naive `===` leaks is closed.
142
+ *
143
+ * The XOR-accumulate idiom: fold every position's difference into one
144
+ * accumulator and test it once at the end. No `break`, no short-circuit.
145
+ */
146
+ declare function constantTimeEqual(a: string, b: string): boolean;
147
+ /**
148
+ * The DO-native alarm slice `stepHost` re-arms. A real `DurableObjectStorage`
149
+ * satisfies this structurally; a test supplies a fake (no live Workers runtime).
150
+ * Only `setAlarm` is needed — the give-up deadline is re-armed on every step so
151
+ * a run abandoned mid-loop (the hands stop POSTing) is eventually evicted by the
152
+ * alarm firing, not left resident forever.
153
+ */
154
+ interface AlarmStorage {
155
+ setAlarm(scheduledTime: number): void | Promise<void>;
156
+ }
157
+ /** The minimal `ctx` slice `stepHost` reads — just the alarm-bearing storage. */
158
+ interface StepCtx {
159
+ readonly storage: AlarmStorage;
160
+ }
161
+ /**
162
+ * The run-specific operations `stepHost` orchestrates. The consumer implements
163
+ * these against its runtime + storage; `stepHost` never reaches into the agent
164
+ * machine itself (that lives in `../agent`, off-limits to this carrier).
165
+ */
166
+ interface StepEngine<R, I, O> {
167
+ /**
168
+ * The persisted capability token for `runId`, or `null` if the run is
169
+ * unknown. Read from DO storage. `stepHost` compares the request token
170
+ * against this constant-time.
171
+ */
172
+ tokenFor(runId: string): Promise<string | null> | string | null;
173
+ /**
174
+ * Settle the posted tool result into the run, then RESUME from the durable
175
+ * checkpoint: dispatch the settle and run to DISPATCH-QUIESCENCE (e.g.
176
+ * `await runtime.dispatch(settleMsg)` — dispatch drains the follow-up chain
177
+ * by default, #50). Called at most once per distinct
178
+ * `callId` — `stepHost` dedupes re-POSTs via `idempotent-intake` BEFORE
179
+ * calling this, so the side effect runs exactly once. On the first request
180
+ * (`posted === null`) `stepHost` skips settle and calls {@link nextStep}
181
+ * directly to hand out the first step.
182
+ */
183
+ resume(runId: string, posted: StepResult<R>): Promise<void> | void;
184
+ /**
185
+ * Read the next step the run is now waiting on — the next tool call — off the
186
+ * resumed runtime's durable state, or `null` if the run has reached its
187
+ * terminal State (then {@link terminalOutput} supplies the output).
188
+ */
189
+ nextStep(runId: string): Promise<NextStep<I> | null> | NextStep<I> | null;
190
+ /**
191
+ * Read the run's terminal output (off `runtime.result()`). Only called when
192
+ * {@link nextStep} returned `null`.
193
+ */
194
+ terminalOutput(runId: string): Promise<O> | O;
195
+ }
196
+ /** Tuning for `stepHost`'s give-up alarm. */
197
+ interface StepHostConfig {
198
+ /**
199
+ * How far ahead, in ms, to re-arm the give-up alarm on every step. A run that
200
+ * stops POSTing for longer than this is abandoned and the alarm fires (the
201
+ * consumer's `alarm()` handler tears the run down). Defaults to 5 minutes.
202
+ */
203
+ readonly giveUpAfterMs?: number;
204
+ }
205
+ /**
206
+ * A structured `/step` outcome `stepHost` returns — the response body plus the
207
+ * HTTP status the consumer's route should send. Kept transport-agnostic (no
208
+ * `Response`) so the same handler is unit-testable without a live `fetch`.
209
+ */
210
+ type StepOutcome<I, O> = {
211
+ readonly status: 200;
212
+ readonly body: StepResponse<I, O>;
213
+ } | {
214
+ readonly status: 401 | 404;
215
+ readonly body: {
216
+ readonly error: string;
217
+ };
218
+ };
219
+ /**
220
+ * Build the `/step` handler for a DO that drives one or more runs over the pull
221
+ * carrier. Returns `handle(request, now)` — call it from the DO's `/step`
222
+ * route, map the `StepOutcome` to a `Response`, and let the DO hibernate.
223
+ *
224
+ * The handler is STATELESS between calls except for its `idempotent-intake`
225
+ * dedupe ledger, which the consumer should itself persist alongside the run's
226
+ * checkpoint if cross-eviction dedupe of an identical re-POST is required; the
227
+ * in-isolate ledger here dedupes re-POSTs within one activation (the common
228
+ * "the hands' POST timed out and got retried" case), and the underlying
229
+ * `resume` is itself idempotent against the durable checkpoint, so a re-POST
230
+ * that crosses an eviction re-settles harmlessly.
231
+ *
232
+ * @param ctx the DO state slice (alarm-bearing storage)
233
+ * @param engine the run-specific operations
234
+ * @param config alarm tuning
235
+ */
236
+ declare function stepHost<R, I, O>(ctx: StepCtx, engine: StepEngine<R, I, O>, config?: StepHostConfig): {
237
+ handle(request: StepRequest<R>, now: number): Promise<StepOutcome<I, O>>;
238
+ };
239
+ /** The `/step` transport the hands POST through (injected; faked in tests). */
240
+ type StepTransport<R, I, O> = (request: StepRequest<R>) => Promise<StepResponse<I, O>>;
241
+ /** Execute one tool call and produce its result (the consumer's hands). */
242
+ type ExecuteStep<I, R> = (step: NextStep<I>) => Promise<R> | R;
243
+ /** Tuning for {@link runStepLoop}. */
244
+ interface RunStepLoopConfig {
245
+ /**
246
+ * Absolute wall-clock deadline (ms epoch). The loop stops asking once `now()`
247
+ * reaches it, surfacing a `deadline_exceeded` outcome. Mirrors the poller's
248
+ * absolute-deadline contract (a resumed loop honors the SAME moment).
249
+ */
250
+ readonly deadlineMs: number;
251
+ /** Backoff policy for FAILED `/step` POSTs. Defaults to `defaultRetryPolicy`. */
252
+ readonly retry?: RetryPolicy;
253
+ /** Clock, injected for determinism. Defaults to `Date.now`. */
254
+ readonly now?: () => number;
255
+ /** `(ms) => Promise` sleep, injected for determinism. Defaults to real sleep. */
256
+ readonly sleep?: (ms: number) => Promise<void>;
257
+ /** Backoff jitter RNG, injected for determinism. Defaults to `Math.random`. */
258
+ readonly rng?: Rng;
259
+ }
260
+ /** The terminal outcome of a full {@link runStepLoop} drive. */
261
+ type StepLoopOutcome<O> = {
262
+ readonly kind: "done";
263
+ readonly output: O;
264
+ } | {
265
+ readonly kind: "deadline_exceeded";
266
+ } | {
267
+ readonly kind: "gave_up";
268
+ readonly error: unknown;
269
+ };
270
+ /**
271
+ * Drive the pull loop to completion. Starting from `{ posted: null }`, POST
272
+ * `/step`, and for each non-terminal response execute the step and POST its
273
+ * result, until the response is terminal (`done: true`) — then return its
274
+ * output.
275
+ *
276
+ * Resilience composed from `poller`'s primitives:
277
+ * - **absolute deadline** — `config.deadlineMs`. Before each POST the loop
278
+ * checks `now() >= deadlineMs` and stops with `deadline_exceeded` rather
279
+ * than asking forever.
280
+ * - **backoff** — a FAILED POST (transport reject) is retried on the
281
+ * `retry-backoff` curve; once `shouldRetry` is exhausted the loop stops
282
+ * with `gave_up`. A success resets the retry counter.
283
+ * - **dedupe** — a step whose `callId` was already executed in THIS drive is
284
+ * not re-executed; its prior result is re-POSTed. Closes the "the host
285
+ * re-handed the same step after a duplicate settle" path (the host side is
286
+ * idempotent; the hands match it).
287
+ */
288
+ declare function runStepLoop<R, I, O>(token: string, runId: string, transport: StepTransport<R, I, O>, execute: ExecuteStep<I, R>, config: RunStepLoopConfig): Promise<StepLoopOutcome<O>>;
289
+
290
+ /**
291
+ * `@demlik/tea/do` — the durable-timer / alarm-carrier port (#180).
292
+ *
293
+ * Every native-DO grain that wants a wall-clock tick hand-rolls the SAME
294
+ * eviction-safe pattern: compute the next absolute deadline off the live state,
295
+ * push it to `ctx.storage.setAlarm`, and — when the DO `alarm()` lifecycle hook
296
+ * fires — map that fire back to a Msg, then re-arm off the post-fire state. The
297
+ * `raft/do.ts` election/heartbeat deadline does it (`rearmAlarm` + `onAlarm`),
298
+ * and the vortex arena tick (`room.ts` `armTick` + `onTick`) replicates the
299
+ * exact shape for the 18 Hz game clock. The two old `do_alarm`/`do_ws` Subs were
300
+ * dropped (see `./index`'s historical note) because `toMachine` fixes the agent
301
+ * machine's Sub type to `DeadlineSub`, so they could never union into a grain's
302
+ * Sub set — leaving the alarm re-arm a HOST concern with no first-class home.
303
+ *
304
+ * {@link durableTimer} is that home: a tiny, transport- and Msg-agnostic helper
305
+ * that owns the three repeated responsibilities and nothing else —
306
+ *
307
+ * 1. **Compute-next-deadline → `setAlarm`.** {@link DurableTimer.rearm} calls
308
+ * the injected {@link DurableTimerConfig.nextDeadline}, and arms the alarm
309
+ * at the returned absolute epoch-ms target.
310
+ * 2. **Idle-when-no-work.** `nextDeadline` returns `null` when there is nothing
311
+ * to schedule (an empty room, a node with no pending election/heartbeat),
312
+ * and `rearm` then arms NOTHING — the DO is allowed to go idle / hibernate
313
+ * instead of spinning the alarm forever. Re-arming is a positive act gated
314
+ * on real work, never an unconditional reschedule.
315
+ * 3. **Fire → Msg → re-arm.** {@link DurableTimer.onAlarm} is the body the DO
316
+ * `alarm()` hook calls: it runs the injected {@link DurableTimerConfig.onFire}
317
+ * (the consumer dispatches its timer Msg there — a `Tick`, a re-derived
318
+ * deadline Msg) and THEN re-arms off the now-advanced state. Re-arm always
319
+ * follows the fire, so a still-active grain keeps ticking and an idle one
320
+ * stops by construction.
321
+ *
322
+ * **Eviction-safe across hibernation.** A DO alarm is the one timer that
323
+ * survives eviction (an isolate `setInterval` dies the moment the isolate
324
+ * sleeps), so the same `durableTimer` re-armed on a cold wake resumes the
325
+ * schedule that a never-evicted instance would have held. The recovery contract
326
+ * is just: after the host rebuilds state (`store.load()` → `replay`), call
327
+ * {@link DurableTimer.rearm} once — it recomputes `nextDeadline` off the
328
+ * recovered state and re-arms (or idles) identically to the live path. The timer
329
+ * holds no in-memory schedule of its own; the deadline is always a pure function
330
+ * of the persisted state, which is what makes the cold-wake re-arm correct.
331
+ *
332
+ * **Dependency-injected / testable.** The alarm arrives as a {@link AlarmStorage}
333
+ * port — the SAME minimal `setAlarm` slice `stepHost` already takes (a real
334
+ * `DurableObjectStorage` satisfies it structurally; a test supplies a fake with
335
+ * no live Workers runtime), mirroring how `event-sourced-store.ts` takes its
336
+ * storage. `nextDeadline` and `onFire` are injected thunks, so the whole timer
337
+ * unit-tests with a faked alarm + a faked clock and no `cloudflare:workers`
338
+ * import. See `.patterns/tea-do/recovery.md` for the cold-wake replay contract
339
+ * the re-arm rides on.
340
+ */
341
+
342
+ /**
343
+ * The construction inputs for {@link durableTimer} — the impure edges the grain
344
+ * injects, kept out of the pure reducer exactly like raft/do's
345
+ * `RaftGrainCtx`/room's `ArenaPorts`.
346
+ */
347
+ interface DurableTimerConfig {
348
+ /**
349
+ * The DO alarm carrier. `setAlarm(atMs)` schedules the next fire at an
350
+ * absolute epoch-ms target. A real `DurableObjectStorage` satisfies this
351
+ * structurally; a test supplies a fake. Only `setAlarm` is read — never the
352
+ * whole `DurableObjectStorage`.
353
+ */
354
+ readonly alarm: AlarmStorage;
355
+ /**
356
+ * Compute the next absolute deadline (epoch ms) off the live state, or `null`
357
+ * when there is no work to schedule. Returning `null` is the idle signal:
358
+ * {@link DurableTimer.rearm} arms nothing and the DO is free to hibernate.
359
+ *
360
+ * Called fresh on every `rearm` (including the post-fire re-arm and the
361
+ * cold-wake re-arm), so it MUST read live state / the injected clock each call
362
+ * — that freshness is what keeps the schedule a pure function of the persisted
363
+ * state and the re-arm eviction-safe. Examples:
364
+ * - raft: `node.subs(state, now())[0]?.atMs ?? null` (election XOR heartbeat).
365
+ * - vortex: `connectionCount() > 0 ? now() + TICK_INTERVAL_MS : null`.
366
+ */
367
+ nextDeadline(): number | null | Promise<number | null>;
368
+ /**
369
+ * Run the timer's effect when the alarm fires — the consumer dispatches its
370
+ * timer Msg here (a `Tick`, a re-derived deadline Msg) and persists/broadcasts
371
+ * as its grain requires. {@link DurableTimer.onAlarm} awaits this BEFORE it
372
+ * re-arms, so the re-arm sees the post-fire state.
373
+ */
374
+ onFire(): void | Promise<void>;
375
+ }
376
+ /**
377
+ * The activated durable timer. Two methods, both idempotent to call from the
378
+ * host edge:
379
+ * - {@link DurableTimer.rearm} — recompute + (re-)arm or idle. Call it once
380
+ * after cold-wake recovery, and after any state transition that can change
381
+ * whether/when the next deadline should fire.
382
+ * - {@link DurableTimer.onAlarm} — the DO `alarm()` body: fire, then re-arm.
383
+ */
384
+ interface DurableTimer {
385
+ /**
386
+ * Recompute {@link DurableTimerConfig.nextDeadline} off the live state and arm
387
+ * the alarm at it — or, when it returns `null`, arm nothing (idle). Safe to
388
+ * call repeatedly; arming the same absolute target twice is a harmless
389
+ * overwrite on the DO alarm API.
390
+ */
391
+ rearm(): Promise<void>;
392
+ /**
393
+ * The body the DO `alarm()` lifecycle hook delegates to: run
394
+ * {@link DurableTimerConfig.onFire}, then {@link DurableTimer.rearm} off the
395
+ * post-fire state. A still-active grain keeps ticking; an idle one (its
396
+ * `nextDeadline` now `null`) stops here.
397
+ */
398
+ onAlarm(): Promise<void>;
399
+ }
400
+ /**
401
+ * Build a {@link DurableTimer} over an injected alarm carrier, a next-deadline
402
+ * computation, and a fire handler. The returned timer owns the
403
+ * compute-next-deadline → `setAlarm` → `alarm()` → fire → re-arm loop and holds
404
+ * no schedule of its own (the deadline is always recomputed from live state),
405
+ * which is what makes it eviction-safe across hibernation.
406
+ */
407
+ declare function durableTimer(config: DurableTimerConfig): DurableTimer;
408
+
409
+ /**
410
+ * @demlik/tea/do — event-sourced persistence mode for a Durable Object actor.
411
+ *
412
+ * `doStore` (sibling, `./index`) is the DEFAULT and stays snapshot-only: it
413
+ * writes the whole `S` blob to one cell on every transition and reads it back
414
+ * on boot. That is byte-for-byte unchanged by this file.
415
+ *
416
+ * `doEventSourcedStore` is the OPT-IN alternative. Instead of (only)
417
+ * checkpointing the latest state, it:
418
+ *
419
+ * 1. APPENDS each applied `Msg` to an append-only log in `DurableObjectStorage`
420
+ * (one cell per event, monotonically keyed).
421
+ * 2. Takes a PERIODIC SNAPSHOT every `snapshotEvery` events (count-based
422
+ * retention — mirrors Akka `RetentionCriteria.snapshotEvery`) so the log
423
+ * that must replay on the next activation is bounded. A grain that needs
424
+ * replay bounded by ELAPSED TIME/TICKS rather than raw event count drives
425
+ * `snapshotNow()` instead (or alongside) — see that method.
426
+ * 3. On activation REBUILDS state by FOLDING the log on top of the latest
427
+ * snapshot. The fold is the substrate's own `replay` — `init(snapshot)`
428
+ * then one reducer step per logged Msg. `replay` returns Cmds as data and
429
+ * never interprets them, so **side effects are suppressed on replay**
430
+ * (recovery.md). This is the same primitive `foldEvents` / the recorder
431
+ * use; it is reused here, not reinvented (ADR 0003: "the fold already
432
+ * exists; wire it to Store").
433
+ *
434
+ * The "recovered/ready" signal (recovery.md `RecoveryCompleted`) is delivered
435
+ * exactly once per activation via `onReady`, EVEN for a fresh actor with an
436
+ * empty log, and is documented to be idempotent (it may fire again on a later
437
+ * activation, so the handler must tolerate repeats).
438
+ *
439
+ * Integration with the cooperating DO:
440
+ *
441
+ * const es = doEventSourcedStore(storage, machine, ctx, {
442
+ * snapshotEvery: 100,
443
+ * parse, // optional boundary parse for the snapshot
444
+ * onReady: (state) => { ... }, // optional, idempotent end-of-recovery hook
445
+ * });
446
+ * const runtime = run(machine, { ctx, store: es.store });
447
+ * // Append every applied Msg to the log. `observe` delivers only applied
448
+ * // Msgs now (#47) — boot has no event, so there is no `null` arm to skip.
449
+ * runtime.observe((msg) => { es.append(msg); });
450
+ * await runtime.ready; // store.load() has folded; onReady has fired once.
451
+ *
452
+ * A time/tick-driven grain (e.g. one that derives simulation ticks between
453
+ * sparse intents) bounds its cold-wake replay by ELAPSED TIME rather than event
454
+ * count by calling `es.snapshotNow()` on its own cadence — every N derived ticks
455
+ * or on a wall-clock interval — so a quiet span that never trips `snapshotEvery`
456
+ * is still snapshotted before its replay tail grows unbounded (#190):
457
+ *
458
+ * // in the grain's tick loop, after the turn's appends have settled:
459
+ * if (tick % SNAPSHOT_EVERY_TICKS === 0) await es.snapshotNow();
460
+ */
461
+
462
+ /**
463
+ * Options for {@link doEventSourcedStore}.
464
+ */
465
+ interface EventSourcedOptions<S, M> {
466
+ /**
467
+ * Take a snapshot every N appended events (count-based retention, mirrors
468
+ * Akka `RetentionCriteria.snapshotEvery`). A snapshot bounds how many events
469
+ * replay on the next activation — it never changes the fold's result, only
470
+ * its length (recovery.md). Must be >= 1. Default 100.
471
+ */
472
+ readonly snapshotEvery?: number;
473
+ /**
474
+ * Boundary parse for the persisted SNAPSHOT cell (the `S` blob). Same
475
+ * contract as `doStore`'s `parse`: returns `S` on a recognized shape, `null`
476
+ * on an unrecognized one (boot fresh), and must NOT throw. Default accepts
477
+ * any non-null, non-array object as `S`.
478
+ */
479
+ readonly parse?: (raw: unknown) => S | null;
480
+ /**
481
+ * Boundary parse for a persisted EVENT (one logged `Msg`). Returns the `M` on
482
+ * a recognized shape; returning `null` DROPS the event from replay (use for a
483
+ * removed Msg variant). Must NOT throw. Default accepts any object carrying a
484
+ * string `type` as `M`.
485
+ */
486
+ readonly parseEvent?: (raw: unknown) => M | null;
487
+ /**
488
+ * End-of-recovery hook (recovery.md `RecoveryCompleted`). Fires EXACTLY ONCE
489
+ * per activation, after the fold completes, carrying the recovered state —
490
+ * even when the log was empty (fresh actor). It may fire again on a later
491
+ * activation, so it MUST be idempotent. Side effects that belong "once after
492
+ * recovery" go here, never in the reducer (the reducer re-runs on every
493
+ * replay).
494
+ */
495
+ readonly onReady?: (state: S) => void;
496
+ /** Cell-key overrides (rarely needed; for coexisting actors in one DO). */
497
+ readonly keys?: {
498
+ readonly snapshot?: string;
499
+ readonly meta?: string;
500
+ readonly eventPrefix?: string;
501
+ };
502
+ }
503
+ /**
504
+ * One persisted log entry handed to a {@link EventSourcedStore.readEvents}
505
+ * consumer: the monotonic `seq` the event was appended under, paired with the
506
+ * decoded `event`. `seq` is the store's own append counter (the key the event
507
+ * lives under), so it both orders the stream and bounds an {@link EventLogRange}.
508
+ */
509
+ interface PersistedEvent<M> {
510
+ /** The append sequence number this event was logged under (1-based, gap-free). */
511
+ readonly seq: number;
512
+ /** The decoded `Msg`, parsed through the store's `parseEvent` boundary. */
513
+ readonly event: M;
514
+ }
515
+ /**
516
+ * Optional inclusive bounds for {@link EventSourcedStore.readEvents}. Both ends
517
+ * are inclusive and match against the event's `seq`; omitting an end leaves it
518
+ * unbounded. `{ fromSeq: 5, toSeq: 10 }` yields seqs 5..10; `{}` (or no
519
+ * argument) yields the whole log.
520
+ */
521
+ interface EventLogRange {
522
+ /** Lowest `seq` to include (inclusive). Default: the first event. */
523
+ readonly fromSeq?: number;
524
+ /** Highest `seq` to include (inclusive). Default: the last event. */
525
+ readonly toSeq?: number;
526
+ }
527
+ /**
528
+ * The handle returned by {@link doEventSourcedStore}: a `Store<S>` to hand to
529
+ * `run(...)`, plus the append + recovery surface the cooperating DO drives.
530
+ */
531
+ interface EventSourcedStore<S, M> {
532
+ /**
533
+ * The `Store<S>` to pass as `run(machine, { ctx, store })`. Its `load()`
534
+ * performs the fold (snapshot + log replay); its `save(state)` is the
535
+ * count-based snapshot writer. `migrate` forwards to `parse`.
536
+ */
537
+ readonly store: Store<S>;
538
+ /**
539
+ * Append one applied `Msg` to the log. Call from `runtime.observe` for every
540
+ * non-null msg. Resolves once the event is durably written. Triggers a
541
+ * snapshot when the event count crosses a `snapshotEvery` boundary.
542
+ */
543
+ append(msg: M): Promise<void>;
544
+ /**
545
+ * Take a snapshot NOW, independent of the count-based `snapshotEvery` trigger
546
+ * (a time/tick-based retention trigger, #190). The count-based trigger bounds
547
+ * the replay tail by raw EVENT COUNT; a grain that derives many state
548
+ * transitions per logged event (e.g. simulation ticks between sparse intents)
549
+ * needs the tail bounded by ELAPSED TIME instead — so it calls `snapshotNow()`
550
+ * on its own cadence (every N derived ticks, or on a wall-clock interval) to
551
+ * checkpoint the current folded state without injecting filler events purely
552
+ * to advance the counter.
553
+ *
554
+ * It checkpoints the latest state the substrate handed `save()` at the current
555
+ * highest sequence, then advances the count-based baseline (`snapshotEvery`
556
+ * counts afresh from here, so a manual snapshot defers the next automatic one).
557
+ *
558
+ * Returns `true` if a snapshot was written, `false` on a no-op: nothing has
559
+ * been applied yet (fresh actor), or no new event exists since the last
560
+ * snapshot (already current). Like the count-based path, it pairs the held
561
+ * state with the highest sequence, so the caller MUST invoke it between
562
+ * settled transitions — after the turn's `append()`s have resolved — never
563
+ * mid-transition (the same boundary discipline `append`'s own snapshot relies
564
+ * on). Resolves once the snapshot is durably written.
565
+ */
566
+ snapshotNow(): Promise<boolean>;
567
+ /**
568
+ * Stream the persisted event log in `seq` order, optionally bounded by an
569
+ * inclusive {@link EventLogRange}. This is the PUBLIC read side of the
570
+ * event-sourced write model — the surface replay / projection / audit
571
+ * consumers (CQRS reads, recovery.md / projections.md) read through, instead
572
+ * of mirroring the store's private `@@es/evt/` key convention.
573
+ *
574
+ * The log is APPEND-ONLY and is never truncated by snapshotting (a snapshot
575
+ * only bounds the *replay* tail, never the log), so this yields EVERY event
576
+ * in range — including ones a snapshot already folds. Each entry carries its
577
+ * `seq`, so a consumer can resume from a known offset (`{ fromSeq }`) or read
578
+ * a window (`{ fromSeq, toSeq }`). An empty (or fully out-of-range) log yields
579
+ * nothing. Events whose `parseEvent` returns `null` (a retired Msg variant)
580
+ * are dropped, identical to the recovery fold.
581
+ *
582
+ * Read-only: it never appends, snapshots, or mutates store state, so streaming
583
+ * the log can never perturb the live actor.
584
+ */
585
+ readEvents(range?: EventLogRange): AsyncIterable<PersistedEvent<M>>;
586
+ }
587
+ /**
588
+ * Build an event-sourced `Store<S>` over `DurableObjectStorage`.
589
+ *
590
+ * The substrate calls `store.load()` once at boot — that is where the fold
591
+ * happens — and `store.save(state)` after every transition — that is where the
592
+ * count-based snapshot is taken. The cooperating DO additionally calls
593
+ * `append(msg)` from `runtime.observe` to grow the log.
594
+ *
595
+ * `machine` and `ctx` are required because the fold is `replay(machine, {
596
+ * loaded: snapshot, msgs: log, ctx })` — the same pure reducer the live runtime
597
+ * uses, so the rebuilt state is identical to the never-evicted state by
598
+ * construction. `ctx` is only used to satisfy `init`'s signature during the
599
+ * fold; an event-sourced machine's `init` rehydrate branch is a pure
600
+ * passthrough (TEA invariant 2), so the fold touches nothing in `ctx`.
601
+ */
602
+ declare function doEventSourcedStore<S, M extends {
603
+ type: string;
604
+ }, Ctx, C extends Cmd = Cmd, U extends Sub = Sub>(storage: DurableObjectStorage, machine: Machine<S, M, C, U, Ctx>, ctx: Ctx, opts?: EventSourcedOptions<S, M>): EventSourcedStore<S, M>;
605
+
606
+ /**
607
+ * @demlik/tea/do — CQRS projections as a first-class seam.
608
+ *
609
+ * TODAY (`./host`), "Model → views" is ad-hoc: `sseHub` is a hand-rolled
610
+ * sink-set fanning `runtime.observe` events out. It re-derives a common shape:
611
+ * subscribe to `observe`, fold each update into a private view, push the view
612
+ * at a sink. (The run's terminal output was once another such observe-driven
613
+ * read model — `captureLastTurn` — but that is now modelled first-class on the
614
+ * write model itself as `state.output` / `runtime.result()`, #46.)
615
+ *
616
+ * This file names that shape. A `Projection<Model, View>` is the canon's
617
+ * "one write model, many projections" contract made concrete on the tea
618
+ * substrate (`.patterns/tea-do/projections.md`):
619
+ *
620
+ * 1. ONE WRITE MODEL, MANY PROJECTIONS. The write model is the agent's `run()`
621
+ * State (`Model`) plus its `Msg` stream. Each projection is an INDEPENDENT
622
+ * `(events|Model) → view` derivation into its OWN read model, with its own
623
+ * id-scoped offset. Adding a projection never touches the write model or
624
+ * any other projection — it only registers another consumer of `observe`.
625
+ *
626
+ * 2. OFFSET-RESUME + IDEMPOTENT APPLY ARE ONE UNIT. A durable projection
627
+ * tracks an EXCLUSIVE offset — the last applied position. On restart it
628
+ * resumes STRICTLY AFTER the stored offset (never ±1); at-least-once
629
+ * delivery means the same update may be re-presented, so `apply` must be
630
+ * idempotent (an upsert of the derived view, never a blind increment).
631
+ * WHEN the offset is committed relative to the view write IS the delivery
632
+ * semantic (`.patterns/tea-do/delivery-semantics.md`):
633
+ * - commit AFTER the view write → at-least-once (may reprocess)
634
+ * - commit in the SAME step → exactly-once (the default here)
635
+ * We persist offset-with-view as one unit (a single `OffsetView` cell), so
636
+ * a durable projection is exactly-once by construction; the at-least-once
637
+ * replay window is still exercised because `drive()` may re-present an
638
+ * update at/below the stored offset (boot reconcile, double-observe), and
639
+ * the exclusive-offset guard makes that a no-op.
640
+ *
641
+ * 3. A VIEW IS ALWAYS REBUILDABLE. Clearing the view and resetting the offset
642
+ * to the start (`rebuild`) replays the `Msg`/`Model` stream from zero. The
643
+ * live view and the rebuilt view are equal by construction — both are the
644
+ * same pure `apply` fold over the same ordered updates.
645
+ *
646
+ * Relationship to `./host`: `sseHub` is re-expressed here as ONE projection
647
+ * (`sseProjection`) WITHOUT changing its public API — `host.sseHub()` keeps
648
+ * working for existing callers (it is the volatile, offset-free case: a live
649
+ * SSE stream has no durable read model to resume).
650
+ *
651
+ * Relationship to `./event-sourced-store`: a durable projection resumes over
652
+ * the SAME append-only event log `doEventSourcedStore` (#68) writes. The fold
653
+ * is the projection's `apply`, scoped to the projection's offset — independent
654
+ * of the write model's snapshot/replay. This file does NOT import the store; it
655
+ * is offset-shaped so a consumer can wire either the live `observe` stream or a
656
+ * replay over the log into the same `apply`.
657
+ */
658
+ /** A projection's identity: `name` (the view) + `key` (the instance). */
659
+ interface ProjectionId {
660
+ readonly name: string;
661
+ readonly key: string;
662
+ }
663
+ /** Render a `ProjectionId` to its canonical `name-key` string. */
664
+ declare function projectionIdString(id: ProjectionId): string;
665
+ /**
666
+ * One unit the projection driver presents to a projection's `apply`. Mirrors
667
+ * the `(msg, model)` pair `runtime.observe` fans out, plus the exclusive
668
+ * `offset` the driver assigns. `msg === null` is the boot update (no event).
669
+ */
670
+ interface ProjectionUpdate<Model, Msg> {
671
+ /** The applied event, or `null` on the boot update (initial state). */
672
+ readonly msg: Msg | null;
673
+ /** The post-transition write-model state. */
674
+ readonly model: Model;
675
+ /**
676
+ * The exclusive position of this update in the ordered stream — monotonic,
677
+ * 1-based; 0 is the boot update (the `NoOffset` start). The stored offset is
678
+ * EXCLUSIVE: re-presenting an update whose offset is `<=` the stored offset
679
+ * must be a no-op (the at-least-once replay window). Do NOT ±1 it.
680
+ */
681
+ readonly offset: number;
682
+ }
683
+ /**
684
+ * A named CQRS projection: an independent fold of the write model's
685
+ * `(Msg|Model)` stream into a private `View`, plus a sink to publish it.
686
+ *
687
+ * @typeParam Model The write-model state (`run()`'s `S`).
688
+ * @typeParam Msg The write-model event (`run()`'s `M`).
689
+ * @typeParam View The read model this projection derives.
690
+ */
691
+ interface Projection<Model, Msg extends {
692
+ type: string;
693
+ }, View> {
694
+ /** This projection's id (offset-isolation unit). */
695
+ readonly id: ProjectionId;
696
+ /** The seed view for a fresh or rebuilt run (the `NoOffset` start). */
697
+ readonly initial: View;
698
+ /**
699
+ * The per-envelope reducer: derive the next view from the previous view and
700
+ * one update. Pure; MUST be idempotent — re-applying an update at the same
701
+ * offset yields the same view (upsert the derived value, never a blind
702
+ * increment). Returning the SAME view reference (or an equal one) for an
703
+ * update the view does not care about is the canonical "skip".
704
+ */
705
+ apply(view: View, update: ProjectionUpdate<Model, Msg>): View;
706
+ /**
707
+ * The sink — publish the derived view. SSE frame, durable cell, report row.
708
+ * Fired by the driver after `apply` produces a new view. A throwing `emit` is
709
+ * isolated by the driver (it must not strand sibling projections).
710
+ */
711
+ emit(view: View): void;
712
+ }
713
+ /**
714
+ * A live, running projection instance: the projection's current `View`, its
715
+ * exclusive stored `offset`, and the `present`/`reset` operations the driver
716
+ * (or a rebuild) calls.
717
+ */
718
+ interface ProjectionRunner<Model, Msg extends {
719
+ type: string;
720
+ }, View> {
721
+ readonly id: ProjectionId;
722
+ /** The current derived view (the read model). */
723
+ view(): View;
724
+ /**
725
+ * The exclusive stored offset — the position of the LAST applied update.
726
+ * Resume reads strictly after this; `0` is the `NoOffset` start.
727
+ */
728
+ offset(): number;
729
+ /**
730
+ * Present one update to the projection. At-least-once entry point:
731
+ * - if `update.offset <= storedOffset` the update was already applied →
732
+ * NO-OP (idempotent replay window; offset stays, view stays, no emit).
733
+ * - otherwise fold via `apply`, advance the offset TO `update.offset`, and
734
+ * `emit` the new view. Offset-and-view advance together (one unit) →
735
+ * exactly-once: the stored offset always matches the folded view.
736
+ * Returns true iff the update was applied (false on the no-op replay).
737
+ */
738
+ present(update: ProjectionUpdate<Model, Msg>): boolean;
739
+ /**
740
+ * Clear the view to `initial` and the offset to 0 — the canonical rebuild
741
+ * reset (offset-tracking.md: "resetting it to NoOffset is the view-rebuild
742
+ * operation"). After `reset`, re-presenting the full stream rebuilds the view.
743
+ */
744
+ reset(): void;
745
+ }
746
+ /**
747
+ * Build a {@link ProjectionRunner} for a projection, starting from a stored
748
+ * offset (default 0 = `NoOffset`, a fresh run). The view is seeded to
749
+ * `projection.initial`; the caller resumes by presenting updates strictly after
750
+ * `startOffset` (the driver's offset guard enforces exclusivity even if an
751
+ * already-applied update is re-presented).
752
+ *
753
+ * `emitOnStart`/`emitOnReset` default false: a fresh runner holds `initial` but
754
+ * does not push it at the sink until the first real update lands (an SSE client
755
+ * that just connected wants the next event, not a replay of the seed).
756
+ */
757
+ declare function runProjection<Model, Msg extends {
758
+ type: string;
759
+ }, View>(projection: Projection<Model, Msg, View>, startOffset?: number): ProjectionRunner<Model, Msg, View>;
760
+ /**
761
+ * A driver over a set of projections sharing one write-model stream. Holds the
762
+ * monotonic offset counter; presents each observed update to every registered
763
+ * runner independently.
764
+ */
765
+ interface ProjectionRegistry<Model, Msg extends {
766
+ type: string;
767
+ }> {
768
+ /** Register a projection; returns its live runner. */
769
+ register<View>(projection: Projection<Model, Msg, View>, startOffset?: number): ProjectionRunner<Model, Msg, View>;
770
+ /**
771
+ * Present one update to EVERY registered runner. Each folds independently; an
772
+ * `apply`/`emit` throw in one runner is caught so it cannot strand the others
773
+ * (errors-are-data: a broken projection is dropped from THIS update, not
774
+ * allowed to corrupt siblings). Returns the offset assigned to this update.
775
+ */
776
+ dispatch(msg: Msg | null, model: Model): number;
777
+ /** The current monotonic offset (the last dispatched update's position). */
778
+ offset(): number;
779
+ /** The registered runners (introspection / tests). */
780
+ runners(): readonly ProjectionRunner<Model, Msg, unknown>[];
781
+ }
782
+ /** Build an empty {@link ProjectionRegistry}. */
783
+ declare function projectionRegistry<Model, Msg extends {
784
+ type: string;
785
+ }>(): ProjectionRegistry<Model, Msg>;
786
+ /**
787
+ * Wire a {@link ProjectionRegistry} to a runtime's transition stream. The boot
788
+ * update (the initial state, offset 0) is presented via `onBoot`; every applied
789
+ * transition (offset 1, 2, …) via `observe` — together they reproduce the
790
+ * "every transition including boot" stream the single `observe(msg | null)`
791
+ * channel used to carry, now that `observe`'s `msg` is total (#47). The registry
792
+ * still assigns its monotonic exclusive offset (boot → 0, first Msg → 1).
793
+ * Returns a cleanup that detaches both subscriptions.
794
+ *
795
+ * Typed structurally against `{ observe, onBoot }` so it accepts any
796
+ * `Runtime<Model, Msg>` without importing the substrate's `Runtime` (types-only
797
+ * boundary).
798
+ */
799
+ declare function driveProjections<Model, Msg extends {
800
+ type: string;
801
+ }>(registry: ProjectionRegistry<Model, Msg>, runtime: {
802
+ observe(observer: (msg: Msg, model: Model) => void): () => void;
803
+ onBoot(handler: (model: Model) => void): () => void;
804
+ }): () => void;
805
+ /**
806
+ * Rebuild a projection's view by folding an ordered event stream from the
807
+ * `NoOffset` start. The runner is seeded to `projection.initial` (the implicit
808
+ * boot / `NoOffset` state), then each `updates[i]` is presented at offset
809
+ * `i + 1` — so the FIRST event (`updates[0]`) lands at offset 1 and is folded,
810
+ * exactly mirroring the live driver, which assigns the first applied `Msg`
811
+ * offset 1 (the boot observe is offset 0 and never enters this list). Returns
812
+ * the rebuilt runner whose `view()` equals the live runner's view for the same
813
+ * ordered stream.
814
+ *
815
+ * Pass the ordered events only; do NOT prepend a boot (`msg === null`) entry.
816
+ * With the `i + 1` offsets a leading boot entry would be applied at offset 1
817
+ * like any event (the seed already covers the `NoOffset` start), not skipped —
818
+ * the offset-0 skip that drops the boot is the live driver's, not this fold's.
819
+ *
820
+ * This is the rebuild-equivalence oracle: a view cleared and replayed from the
821
+ * stream equals the live view.
822
+ */
823
+ declare function rebuildProjection<Model, Msg extends {
824
+ type: string;
825
+ }, View>(projection: Projection<Model, Msg, View>, updates: readonly {
826
+ readonly msg: Msg | null;
827
+ readonly model: Model;
828
+ }[]): ProjectionRunner<Model, Msg, View>;
829
+
830
+ /**
831
+ * @demlik/tea/do — the minimum-viable Durable-Object HOST for a `@demlik/tea`
832
+ * `createAgent` runtime.
833
+ *
834
+ * `doStore` (in `./index`) is the durability half. This file is the rest of the
835
+ * boilerplate a DO consumer hand-rolls every time it drives a tea agent over a
836
+ * remote transport: the deferred-tool gateway, auto-boot on rehydrate, the
837
+ * command-runner WS accept, the runtime→SSE plumbing, and a clean read of a
838
+ * run's terminal output. Each export below names exactly the hand-rolled piece
839
+ * it deletes from the consumer.
840
+ *
841
+ * ## The transport model: ONE Sub type, gateway-bridged I/O
842
+ *
843
+ * `createAgent().toMachine()` fixes the machine's Sub type to `DeadlineSub` —
844
+ * the agent owns the retry + watchdog timers and nothing else. That is the
845
+ * WHOLE Sub model for a DO-hosted agent, and it is coherent because the
846
+ * transport deliberately does NOT live in the Sub system:
847
+ *
848
+ * - Inbound WebSocket frames are bridged straight into `gateway.settle(...)`
849
+ * from the DO's `webSocketMessage` lifecycle method — a direct dispatch,
850
+ * not a Sub.
851
+ * - The per-tool deadline is a `setTimeout` inside the gateway — a timer, not
852
+ * a DO alarm.
853
+ *
854
+ * The deferred-tool gateway owns each tool round-trip as a Promise the interpret
855
+ * cell awaits; the transport bridges results into `dispatch` from outside the
856
+ * Sub system. The agent's Sub type stays exactly `DeadlineSub` — there is no
857
+ * competing DO-native Sub variant to union in, and the host needs none. This is
858
+ * the single transport model for `@demlik/tea/do` (#52); see
859
+ * `.patterns/tea/durable-actors.md` for the host-layer north star.
860
+ */
861
+
862
+ /**
863
+ * The deferred-tool gateway. `R` is the consumer's tool-result type (what
864
+ * `agent_tool_ok` carries).
865
+ *
866
+ * - `await(callId, send, deadlineMs)` — register the round-trip, fire `send`,
867
+ * return the Promise the reply (or the deadline) settles. Idempotent per
868
+ * `callId`: a second call for an in-flight id returns the SAME promise
869
+ * without re-firing `send` (the boot-reconcile re-fire is a no-op at the
870
+ * gateway, mirroring the agent's idempotent boot).
871
+ * - `settle(callId, result)` — the transport calls this on a reply; resolves
872
+ * the awaited promise. A settle for an unknown / already-settled id is a
873
+ * no-op (the callId guard the consumer used to keep in its pending Map).
874
+ * - `fail(callId, reason)` — settle the round-trip as an error on demand
875
+ * (e.g. a transport-level close). Same unknown-id no-op.
876
+ * - `inFlight()` — the callIds with an open round-trip (tests / introspection).
877
+ *
878
+ * The deadline → reject path is what makes "timeout is data": the tool cell's
879
+ * `catch` maps the rejection to `agent_tool_err`, recorded on the conversation,
880
+ * never a hang.
881
+ */
882
+ interface DeferredGateway<R> {
883
+ await(callId: string, send: () => void, deadlineMs: number): Promise<R>;
884
+ settle(callId: string, result: R): void;
885
+ fail(callId: string, reason: string): void;
886
+ inFlight(): readonly string[];
887
+ }
888
+ /**
889
+ * Build a deferred-tool gateway. No clock / RNG captured at construction — the
890
+ * deadline is per-call (`await(..., deadlineMs)`), so one gateway serves every
891
+ * tool family with its own timeout.
892
+ *
893
+ * Deletes from the consumer: the `pending` Map, the `Pending` interface, the
894
+ * `awaitClient` method, the `forceTimeout` plumbing, and the `deliverResult`
895
+ * callId-guard body — all of it collapses to this object plus the tool cell's
896
+ * `await` + try/catch.
897
+ */
898
+ declare function deferredGateway<R>(): DeferredGateway<R>;
899
+ /** A `callId`-keyed durable round-trip: maps the gateway's string id to the
900
+ * ledger's monotonic delivery id so a `settle(callId)` finds the owed entry. */
901
+ interface DurableDeferredGateway<R> extends DeferredGateway<R> {
902
+ /** The ledger recorder this gateway feeds (for `surviving()` on activation). */
903
+ readonly recorder: PendingEffectsRecorder<{
904
+ callId: string;
905
+ }>;
906
+ }
907
+ /**
908
+ * Wrap a `DeferredGateway<R>` so every round-trip is also recorded in a durable
909
+ * `PendingEffectsRecorder`. The in-memory gateway is the fast path; the recorder
910
+ * is the durability slice.
911
+ *
912
+ * - `recordOwed(callId, event)` — fires from `await`, BEFORE `send`, carrying
913
+ * the `effect_owed` event the consumer must persist (then deliver).
914
+ * - `recordConfirmed(callId, event)` — fires from `settle`/`fail` for a known
915
+ * id, carrying the `effect_confirmed` event to persist.
916
+ * - `reissue(callId, send, deadlineMs)` — re-arm a surviving round-trip on
917
+ * activation. Idempotent: the gateway's own `await` returns the open promise
918
+ * without re-firing `send` for an in-flight `callId`, so re-issuing an id
919
+ * that is somehow still pending is a no-op (mirrors the boot-reconcile
920
+ * re-fire). The effect is already owed, so no second `effect_owed` is
921
+ * recorded.
922
+ *
923
+ * The effect payload the ledger stores is the `callId` (the correlation key the
924
+ * consumer round-trips); the consumer maps it back to its tool-call context.
925
+ */
926
+ declare function durableDeferredGateway<R>(inner: DeferredGateway<R>, recorder: PendingEffectsRecorder<{
927
+ callId: string;
928
+ }>, hooks: {
929
+ recordOwed(callId: string, event: EffectOwed<{
930
+ callId: string;
931
+ }>): void;
932
+ recordConfirmed(callId: string, event: EffectConfirmed): void;
933
+ }): DurableDeferredGateway<R>;
934
+ /**
935
+ * The typed cold-wake resume port `bootResume` fires through — the agent's
936
+ * `AgentBootPort` (issue #60) generalized to any DO-hosted machine (issue #231).
937
+ *
938
+ * - `isResumable(state)` — true iff the rehydrated State is mid-loop and needs a
939
+ * resume dispatch. False on a fresh boot, which makes `bootResume` a no-op.
940
+ * - `resumeMsg(now)` — the TYPED constructor for the single resume Msg (never a
941
+ * hand-built `{ type: … }` literal); the `agentBootMsg` shape lifted out so a
942
+ * non-agent grain supplies its own boot Msg constructor.
943
+ */
944
+ interface ResumePort<S, M extends {
945
+ type: string;
946
+ }> {
947
+ readonly isResumable: (state: S) => boolean;
948
+ readonly resumeMsg: (now: number) => M;
949
+ }
950
+ /**
951
+ * After `runtime.ready`, derive the single resume Msg from the rehydrated State
952
+ * via `port` and dispatch it exactly once — the generalized AgentBoot. On a
953
+ * fresh (non-rehydrated) machine `port.isResumable` is false and this is a
954
+ * no-op. Call once right after building the runtime; it standardizes the
955
+ * hand-written `if (isResumable(state)) await runtime.dispatch(bootMsg(now()))`
956
+ * every DO-hosted machine otherwise re-derives.
957
+ *
958
+ * `await ready` is the single boot gate (issue #45): `getState()` is total only
959
+ * on the booted `Runtime`, so state cannot be inspected before boot populates
960
+ * it. `now` is injected (the sole boot clock read) so a test can pin it.
961
+ */
962
+ declare function bootResume<S, M extends {
963
+ type: string;
964
+ }, E extends {
965
+ type: string;
966
+ } = never>(booting: BootingRuntime<S, M, E>, port: ResumePort<S, M>, now?: () => number): Promise<void>;
967
+ /**
968
+ * True iff an agent slice loaded from storage is mid-loop (running + awaiting
969
+ * tools) — the resumable case `agent_boot` re-fires.
970
+ *
971
+ * The resumability question IS "is the run suspended on tools?" — so this
972
+ * delegates to the agent's own typed `status` channel (issue #49) rather than
973
+ * re-deriving the private `run.phase` / `conversation.awaiting` shape by hand.
974
+ * The host no longer leaks the agent's internal slice structure; a change to
975
+ * `Awaiting` surfaces as a compile error in `status`, not a silent break here.
976
+ */
977
+ declare function agentIsResumable<Stage, P extends string, O extends Record<P, unknown>, R>(state: AgentState<Stage, P, O, R>): boolean;
978
+ /**
979
+ * The AGENT specialization of {@link bootResume} (issue #231): after
980
+ * `runtime.ready`, self-dispatch `agent_boot` iff the rehydrated slice is
981
+ * resumable, a no-op on a fresh DO. Wires the agent slice's `agentIsResumable`
982
+ * predicate + the agent-owned `agentBootMsg` typed port (issue #60) into the
983
+ * generalized helper, so the resume dispatch lives in ONE place — see
984
+ * `bootResume`'s docblock for the boot-gate / init-purity rationale.
985
+ *
986
+ * `now` is injected (the host's only clock read for boot) so a test can pin it.
987
+ */
988
+ declare function autoBoot<Stage, P extends string, O extends Record<P, unknown>, R, E extends {
989
+ type: string;
990
+ } = never>(booting: BootingRuntime<AgentState<Stage, P, O, R>, AgentMachineMsg<P, O, R>, E>, now?: () => number): Promise<void>;
991
+ /**
992
+ * Accept a command-runner WebSocket on the DO's `fetch`. Adds the server end to
993
+ * `clients`, wires `message` → `onFrame(data)` (string frames only) and
994
+ * `close` → deregister, and returns the 101 upgrade Response.
995
+ *
996
+ * The consumer's `onFrame` parses the frame and calls `gateway.settle(callId,
997
+ * result)` — the inbound bridge. Deletes the consumer's `acceptClient` method.
998
+ */
999
+ declare function acceptCommandSocket(clients: Set<WebSocket>, onFrame: (data: string) => void): Response;
1000
+ /**
1001
+ * Broadcast a JSON frame to every connected command-runner socket. A dead
1002
+ * socket is skipped (dropped on its own `close` event). Lifts the consumer's
1003
+ * fan-out loop in `sendCommand`.
1004
+ */
1005
+ declare function broadcast(clients: Set<WebSocket>, frame: unknown): void;
1006
+ /**
1007
+ * The slice of `DurableObjectState` the durable carrier needs: the Hibernation
1008
+ * API accept + the registry of hibernatable sockets. A real `DurableObjectState`
1009
+ * satisfies this structurally; a test supplies a fake (no live Workers runtime
1010
+ * needed to exercise the accept/registry seam).
1011
+ */
1012
+ interface HibernatableCtx {
1013
+ acceptWebSocket(ws: WebSocket): void;
1014
+ getWebSockets(): WebSocket[];
1015
+ }
1016
+ /**
1017
+ * Accept a command-runner WebSocket using the Cloudflare **Hibernation API** so
1018
+ * the socket survives DO eviction. Unlike {@link acceptCommandSocket}, this does
1019
+ * NOT attach an isolate-resident `message` listener (that listener is what dies
1020
+ * on eviction) — instead `ctx.acceptWebSocket(server)` hands the socket to the
1021
+ * runtime, which re-creates the DO and calls its `webSocketMessage(ws, data)`
1022
+ * lifecycle method on the next inbound frame. The consumer routes that method to
1023
+ * its `onFrame` (which calls `carrier.settle(callId, result)`).
1024
+ *
1025
+ * Returns the 101 upgrade Response. There is no `clients` registry to keep — the
1026
+ * live sockets are `ctx.getWebSockets()` (see {@link broadcastHibernatable}),
1027
+ * which the runtime repopulates after a wake.
1028
+ */
1029
+ declare function acceptDurableCommandSocket(ctx: HibernatableCtx): Response;
1030
+ /**
1031
+ * Broadcast a JSON frame to every hibernatable command-runner socket. The live
1032
+ * set is read from `ctx.getWebSockets()` (the runtime repopulates it after a
1033
+ * wake), NOT a resident `Set<WebSocket>` — so it is correct immediately after an
1034
+ * eviction, before any other lifecycle method has run. A dead socket is skipped.
1035
+ */
1036
+ declare function broadcastHibernatable(ctx: HibernatableCtx, frame: unknown): void;
1037
+ /**
1038
+ * The durable command carrier — a {@link DurableDeferredGateway} whose every
1039
+ * tool round-trip is also a durable owed effect.
1040
+ *
1041
+ * A consumer's tool interpret cell still calls `carrier.await(callId, send,
1042
+ * deadlineMs)` and awaits the Promise (mapping Ok/Err to `agent_tool_ok` /
1043
+ * `agent_tool_err`) — the SAME fast path as the volatile gateway. The addition
1044
+ * is durability: `await` hands the consumer an `effect_owed` event (via
1045
+ * `recordOwed`) to PERSIST before the send, and `settle`/`fail` hand it an
1046
+ * `effect_confirmed` (via `recordConfirmed`) to persist. On the next activation
1047
+ * the consumer reads its persisted ledger events back and calls
1048
+ * {@link reissueSurvivingEffects} to re-fire the survivors.
1049
+ *
1050
+ * It extends `DurableDeferredGateway` (so it is a drop-in for the gateway the
1051
+ * interpret cell already awaits) and exposes the underlying `recorder` for a
1052
+ * host that wants to read `surviving()` directly.
1053
+ */
1054
+ interface DurableCommandCarrier<R> extends DeferredGateway<R> {
1055
+ /** The ledger recorder backing this carrier (for `surviving()` introspection). */
1056
+ readonly recorder: PendingEffectsRecorder<{
1057
+ callId: string;
1058
+ }>;
1059
+ }
1060
+ /**
1061
+ * Build a durable command carrier over a (volatile) `DeferredGateway<R>` and a
1062
+ * `PendingEffectsRecorder`. Thin assembly over {@link durableDeferredGateway}:
1063
+ * it names the #91 piece — "the command carrier whose round-trips survive
1064
+ * eviction" — and keeps the recorder reachable for the activation-time re-emit.
1065
+ *
1066
+ * - `inner` — the volatile gateway whose in-isolate Promise the interpret cell
1067
+ * awaits (typically `deferredGateway<R>()`).
1068
+ * - `recorder` — the durable-effects recorder (typically
1069
+ * `pendingEffectsLedger({ events, lastId })` rehydrated from the persisted
1070
+ * log on a wake, or a fresh `pendingEffectsLedger()` on a cold boot).
1071
+ * - `hooks.recordOwed` / `hooks.recordConfirmed` — the persist callbacks the
1072
+ * consumer points at its event log (so the ledger survives eviction).
1073
+ *
1074
+ * The carrier never persists anything itself (it does not own the consumer's
1075
+ * log) — it hands the consumer the events, faithful to "persist the intent
1076
+ * before deliver".
1077
+ */
1078
+ declare function durableCommandCarrier<R>(inner: DeferredGateway<R>, recorder: PendingEffectsRecorder<{
1079
+ callId: string;
1080
+ }>, hooks: {
1081
+ recordOwed(callId: string, event: EffectOwed<{
1082
+ callId: string;
1083
+ }>): void;
1084
+ recordConfirmed(callId: string, event: EffectConfirmed): void;
1085
+ }): DurableCommandCarrier<R>;
1086
+ /**
1087
+ * RE-EMIT ON ACTIVATION (the #91 wake path). Given a durable carrier whose
1088
+ * recorder was rehydrated from the persisted ledger events (e.g.
1089
+ * `durableCommandCarrier(deferredGateway(), pendingEffectsLedger({ events }),
1090
+ * …)` on a wake), re-fire every owed-but-unconfirmed round-trip via
1091
+ * `reissue(callId)` — the consumer's "re-send the command frame for this
1092
+ * `callId`" callback. The survivors are read straight off the carrier's own
1093
+ * recorder (`recorder.surviving()`), which folded the restore log at
1094
+ * construction — the carrier is the single source of the in-flight set, so the
1095
+ * caller does not re-fold the log here.
1096
+ *
1097
+ * Idempotent by construction:
1098
+ * - the ledger's monotonic `deliveryId` is the dedup key the RECEIVER uses, so
1099
+ * a late/duplicate reply for an already-resolved round-trip is absorbed
1100
+ * (at-least-once delivery — see `survivingEffects`);
1101
+ * - re-issuing the SAME survivor twice within one activation re-sends once —
1102
+ * if `reissue` routes through the carrier's `await`, the gateway returns the
1103
+ * open Promise for an in-flight `callId` without re-firing `send`.
1104
+ *
1105
+ * Returns the `callId`s re-emitted (oldest-first by id) — the host can log /
1106
+ * assert the re-emit set. On a fresh or fully-confirmed carrier this is empty
1107
+ * (nothing re-fired).
1108
+ *
1109
+ * Call this once, right after the runtime's boot gate resolves (alongside
1110
+ * {@link autoBoot}): `autoBoot` re-fires the agent's own `agent_boot`;
1111
+ * `reissueSurvivingEffects` re-fires the in-flight TOOL round-trips. Together
1112
+ * they restore a suspended run's full in-flight state after a wake.
1113
+ */
1114
+ declare function reissueSurvivingEffects<R>(carrier: DurableCommandCarrier<R>, reissue: (callId: string) => void): readonly string[];
1115
+ /**
1116
+ * A set of SSE sinks plus the plumbing to fan an event out to all of them and
1117
+ * to open a `text/event-stream` Response wired to a fresh sink.
1118
+ *
1119
+ * The consumer:
1120
+ * 1. holds one `SseHub<E>` per DO,
1121
+ * 2. calls `hub.emit(event)` from its Msg→event mapping (driven off
1122
+ * `runtime.observe`), and
1123
+ * 3. returns `hub.open()` from the `/sse` route.
1124
+ *
1125
+ * `register(sink)` exists for tests: subscribe a sink directly and observe the
1126
+ * exact event sequence a live client would receive (a live `text/event-stream`
1127
+ * fights vitest-pool-workers teardown). Deletes the consumer's `sseSinks` set,
1128
+ * `openSse`, `emitSse`, and `onSseEvent`.
1129
+ */
1130
+ interface SseHub<E> {
1131
+ emit(event: E): void;
1132
+ open(): Response;
1133
+ register(sink: (event: E) => void): () => void;
1134
+ }
1135
+ /** Build an SSE hub for event type `E` (the consumer's domain event shape). */
1136
+ declare function sseHub<E>(): SseHub<E>;
1137
+ /**
1138
+ * Express an {@link SseHub} as a {@link Projection}. The projection's view is
1139
+ * the latest emitted SSE event (or `null` before the first); `apply` maps each
1140
+ * write-model update to an event via `toEvent` and emits it through `hub.emit`,
1141
+ * so the SSE stream is driven by the SAME hub `sseHub()` returns — no API
1142
+ * change for the `/sse` route (`hub.open()`) or any existing caller.
1143
+ *
1144
+ * - `toEvent(msg, model)` returns the SSE event for this update, or `null` to
1145
+ * skip it (the view stays, nothing is emitted).
1146
+ * - `name`/`key` default to `"sse"`/`"main"`; override for a second SSE view.
1147
+ *
1148
+ * @example
1149
+ * const hub = sseHub<MyEvent>();
1150
+ * const registry = projectionRegistry<MyState, MyMsg>();
1151
+ * registry.register(sseProjection(hub, (msg) =>
1152
+ * msg?.type === "turn_done" ? { kind: "turn", ... } : null,
1153
+ * ));
1154
+ * driveProjections(registry, runtime); // SSE is now one projection
1155
+ * // /sse route unchanged:
1156
+ * return hub.open();
1157
+ */
1158
+ declare function sseProjection<Model, Msg extends {
1159
+ type: string;
1160
+ }, E>(hub: SseHub<E>, toEvent: (msg: Msg | null, model: Model) => E | null, id?: {
1161
+ name?: string;
1162
+ key?: string;
1163
+ }): Projection<Model, Msg, E | null>;
1164
+ /**
1165
+ * Wire an {@link SseHub} to a runtime's semantic {@link AgentEvent} stream (#47).
1166
+ * For each event `type` the runtime can emit, subscribes via `runtime.on` and
1167
+ * pushes `toFrame(event)` at the hub (returning `null` skips that event). The
1168
+ * runtime MUST have been built with `run(machine, { events: agentEvents() })`,
1169
+ * which is what makes `on` deliver the `AgentEvent`s. Returns a cleanup that
1170
+ * detaches every subscription.
1171
+ *
1172
+ * This is the runtime→SSE adapter expressed over the typed event channel: no
1173
+ * `observe` firehose, no private-Msg coupling. `hub.open()` and every existing
1174
+ * `sseHub` caller are unchanged — only the SOURCE of `hub.emit` moves from a
1175
+ * hand-rolled Msg switch to `on`.
1176
+ *
1177
+ * @example
1178
+ * const hub = sseHub<MyFrame>();
1179
+ * const runtime = await run(machine, { ctx, events: agentEvents() }).ready;
1180
+ * const off = sseFromAgentEvents(runtime, hub, (e) =>
1181
+ * e.type === "RunDone" ? { kind: "done", output: e.output } : null,
1182
+ * );
1183
+ * // /sse route unchanged:
1184
+ * return hub.open();
1185
+ */
1186
+ declare function sseFromAgentEvents<R, Frame>(runtime: {
1187
+ on<K extends AgentEvent<R>["type"]>(type: K, handler: (event: Extract<AgentEvent<R>, {
1188
+ type: K;
1189
+ }>) => void): () => void;
1190
+ }, hub: SseHub<Frame>, toFrame: (event: AgentEvent<R>) => Frame | null): () => void;
1191
+ /**
1192
+ * What the consumer supplies to {@link createAgentHost} — the domain mappings,
1193
+ * nothing of the wiring. `Stage`/`P`/`O`/`R` are the agent slice's type
1194
+ * parameters (the consumer names them once); `Frame` is its SSE event shape.
1195
+ * `C`/`U`/`Ctx` are the machine's own Cmd/Sub/Ctx — inferred from
1196
+ * `buildMachine` at the `createAgentHost` call site (never name them by hand;
1197
+ * an erased `any` here cost consumers all checking on this layer — #278).
1198
+ */
1199
+ interface AgentHostConfig<Stage, P extends string, O extends Record<P, AgentTurn>, R, Frame, C extends Cmd = Cmd, U extends Sub = Sub, Ctx = unknown> {
1200
+ /**
1201
+ * Build the wired agent machine. Called once per host build (per activation),
1202
+ * AFTER the previous runtime — if any — was torn down. The consumer wires its
1203
+ * per-tool interpret here (`agent.toMachine({ toolInterpret })`).
1204
+ */
1205
+ readonly buildMachine: () => Machine<AgentState<Stage, P, O, R>, AgentMachineMsg<P, O, R>, C, U, Ctx>;
1206
+ /** The durable `Store` for the agent slice (typically `doStore(storage, parse)`). */
1207
+ readonly store: Store<AgentState<Stage, P, O, R>>;
1208
+ /** The Ctx the machine threads to its interpret cells — checked against `buildMachine`'s. */
1209
+ readonly ctx: Ctx;
1210
+ /**
1211
+ * The run-terminality predicate (#46) — what makes `result()` first-class.
1212
+ * Defaults to the agent's own terminal phases (`done` / `failed`).
1213
+ */
1214
+ readonly terminal?: (state: AgentState<Stage, P, O, R>) => boolean;
1215
+ /**
1216
+ * Map one semantic {@link AgentEvent} to the consumer's SSE frame, or `null`
1217
+ * to skip it. This is the ONLY place the consumer touches the SSE seam — the
1218
+ * host owns the subscription wiring (`sseFromAgentEvents`) and the hub.
1219
+ */
1220
+ readonly toSseFrame: (event: AgentEvent<R>) => Frame | null;
1221
+ /** Clock injected for `autoBoot` (the host's only boot clock read). */
1222
+ readonly now?: () => number;
1223
+ }
1224
+ /**
1225
+ * The assembled host: the runtime cell + the SSE hub + the framework test seam,
1226
+ * owned ONCE. A DO holds one `AgentHost` and delegates to it; its own surface
1227
+ * shrinks to the domain (the gateway, the WS bridge, the command-send mapping).
1228
+ *
1229
+ * @typeParam Stage Pipeline stage type.
1230
+ * @typeParam P Brain-call purposes.
1231
+ * @typeParam O Per-purpose outputs (bound to {@link AgentTurn}).
1232
+ * @typeParam R Tool result type.
1233
+ * @typeParam Frame The consumer's SSE event shape.
1234
+ */
1235
+ interface AgentHost<Stage, P extends string, O extends Record<P, AgentTurn>, R, Frame> {
1236
+ /**
1237
+ * Get (or build) the booted runtime. Build-once-boot-once: the first call
1238
+ * builds the machine, wires SSE off the semantic event stream, awaits the boot
1239
+ * gate, runs the `autoBoot` re-fire for a rehydrated suspended run, and caches
1240
+ * the booted `Runtime`; subsequent calls return the cached handle. The single
1241
+ * assembly every consumer used to hand-roll in `getRuntime()`.
1242
+ */
1243
+ runtime(): Promise<Runtime<AgentState<Stage, P, O, R>, AgentMachineMsg<P, O, R>, AgentEvent<R>>>;
1244
+ /**
1245
+ * The agent's lifecycle status (#49) — the ONE typed channel a consumer reads
1246
+ * instead of re-deriving `run.phase` / `awaiting` by hand. Ensures the runtime
1247
+ * is built + boot-reconciled first. Replaces the per-consumer `isSuspended` /
1248
+ * `runPhase` test methods (read `status().kind`).
1249
+ */
1250
+ status(): Promise<AgentStatus<Stage>>;
1251
+ /**
1252
+ * The run's terminal State (#46), or `undefined` while in flight. Read the
1253
+ * run's product off it (e.g. `result()?.output`). Replaces the per-consumer
1254
+ * `verdict` plumbing's source. Ensures the runtime is built first.
1255
+ */
1256
+ result(): Promise<AgentState<Stage, P, O, R> | undefined>;
1257
+ /**
1258
+ * The SSE hub — the route (`host.sse.open()`) AND the test seam
1259
+ * (`host.sse.register(sink)`). The host drives `hub.emit` off the semantic
1260
+ * event stream; the consumer never touches it except to open / register.
1261
+ */
1262
+ readonly sse: SseHub<Frame>;
1263
+ /**
1264
+ * Tear down the cached runtime (drain its tail via `stop()`, drop the handle)
1265
+ * so the next `runtime()` rebuilds from storage. The framework `settle` test
1266
+ * seam, owned once.
1267
+ */
1268
+ reset(): Promise<void>;
1269
+ }
1270
+ /**
1271
+ * Build an {@link AgentHost} from the consumer's domain mappings. Owns the
1272
+ * runtime+events+autoBoot assembly and the framework test seam — the wiring
1273
+ * issue #53 had every consumer re-write.
1274
+ *
1275
+ * @example
1276
+ * const host = createAgentHost<Stage, Purpose, Outputs, ClientResult, SseEvent>({
1277
+ * buildMachine: () => agent.toMachine<Ctx>({ toolInterpret }),
1278
+ * store: doStore(storage, parse),
1279
+ * ctx,
1280
+ * toSseFrame: (e) =>
1281
+ * e.type === "ToolSettled" ? { kind: "result", ...} :
1282
+ * e.type === "TurnSettled" && e.turn.toolCalls.length === 0
1283
+ * ? { kind: "verdict", verdict: e.turn.content } :
1284
+ * e.type === "RunDone" ? { kind: "phase", phase: "done" } : null,
1285
+ * });
1286
+ * // route: return host.sse.open();
1287
+ * // start: await (await host.runtime()).dispatch({ type: "agent_start", ... });
1288
+ * // test: (await host.status()).kind === "suspended"
1289
+ */
1290
+ declare function createAgentHost<Stage, P extends string, O extends Record<P, AgentTurn>, R, Frame, C extends Cmd = Cmd, U extends Sub = Sub, Ctx = unknown>(config: AgentHostConfig<Stage, P, O, R, Frame, C, U, Ctx>): AgentHost<Stage, P, O, R, Frame>;
1291
+
1292
+ /**
1293
+ * @demlik/tea/do — the idempotent-effect brick (the receiver/effect-side dedup).
1294
+ *
1295
+ * ## The half this closes (do NOT re-implement the ledger)
1296
+ *
1297
+ * The runtime's `stepDispatch` step order is SAVE-BEFORE-EFFECTS: state is
1298
+ * persisted (`store.save(next)`) *before* its commands are interpreted
1299
+ * (`packages/tea/src/index.ts` — "Save-before-effects is the hard ordering;
1300
+ * tests pin it"). So a DO evicted after `save` but before an effect's result is
1301
+ * folded RE-FIRES that effect on the next activation.
1302
+ *
1303
+ * `durable-effects.ts` (sibling) owns the SENDER half of this: the at-least-once
1304
+ * `pendingEffectsLedger`, whose surviving owed entries `reissueSurvivingEffects`
1305
+ * re-fires on activation. Its own header states the contract this file fulfils:
1306
+ * "At-least-once ⇒ a confirmed-but-not-yet-persisted delivery WILL be re-sent
1307
+ * after a crash, so the receiver must deduplicate by id" and "the receiver owns
1308
+ * its own dedup." This module IS that receiver-side dedup — the piece that
1309
+ * upgrades the ledger's at-least-once re-fire to **effectively-once at the
1310
+ * external write**:
1311
+ *
1312
+ * - ledger (`survivingEffects` / `reissueSurvivingEffects`) = at-least-once re-fire;
1313
+ * - this brick (`idempotentEffect` + `appliedEffects`) = effect-side dedup
1314
+ * ⇒ together, effectively-once.
1315
+ *
1316
+ * It COMPLEMENTS the ledger; it does not re-implement it. The ledger keys owed
1317
+ * effects by a monotonic `DeliveryId` (its private sender bookkeeping); this
1318
+ * brick keys applied effects by a caller-supplied durable `EffectKey` (e.g.
1319
+ * `(runId, violationId)`), because dedup identity must be stable across the
1320
+ * re-fire — the same external write must resolve to the same key on every
1321
+ * activation, which a fresh monotonic id cannot guarantee.
1322
+ *
1323
+ * ## The load-bearing rule (mirrors `durable-effects.ts`)
1324
+ *
1325
+ * The applied-marker set is a PURE FOLD over the same kind of event stream the
1326
+ * actor already replays — it is **NOT** a separately-persisted side table:
1327
+ *
1328
+ * - `effect_applied` — `{ key }` ADDS the key to the applied set.
1329
+ * - `effect_forgotten` — `{ key }` REMOVES it (retention — see below).
1330
+ *
1331
+ * `foldApplied(events)` is `events.reduce(applyAppliedEvent, empty)` — the same
1332
+ * fold shape `replay` / `foldLedger` use. This makes the marker live "alongside
1333
+ * State (single authority)" for BOTH persistence modes:
1334
+ *
1335
+ * - `doEventSourcedStore`: persist the two events into the SAME log it already
1336
+ * appends; folding the log on activation rebuilds the applied set for free.
1337
+ * - default snapshot `doStore`: carry the `AppliedEffects` set as a field of
1338
+ * the Model, round-tripped by the store's `serialize`/`parse` hooks (a `Set`
1339
+ * flattens under `JSON.stringify`, exactly the `Map` sharp edge #182), so it
1340
+ * is checkpointed with the rest of `S`.
1341
+ *
1342
+ * Either way the marker is folded/snapshotted with State, never a side channel.
1343
+ *
1344
+ * ## Ordering — why the marker is recorded AFTER `fn` (at-least-once, not at-most)
1345
+ *
1346
+ * `run` invokes `fn` and only THEN yields the `effect_applied` event to persist.
1347
+ * An eviction in the window between `fn` succeeding and that event being folded
1348
+ * re-fires the effect (at-least-once) — correct, because recording the marker
1349
+ * BEFORE `fn` would let a crash strand the marker with the write never made
1350
+ * (at-most-once ⇒ a silently-dropped effect). Once the marker is folded, every
1351
+ * subsequent re-fire of that key is a proven no-op: `fn` is not invoked again.
1352
+ * The ledger guarantees the re-fire; this brick swallows the duplicate.
1353
+ *
1354
+ * ## Retention — pruning mirrors the ledger's confirm-removes-entry (not a new policy)
1355
+ *
1356
+ * An applied-marker is only needed while its effect can still re-fire. The
1357
+ * ledger stops re-firing an effect once its `effect_confirmed` removes the owed
1358
+ * entry — so at that same point the marker is dead weight and is pruned via
1359
+ * `effect_forgotten` (`guard.forget(key)`), keeping the applied set bounded. This
1360
+ * deliberately reuses the ledger's confirm-removes-entry retention shape rather
1361
+ * than inventing a second policy (the #227 triage's one open question).
1362
+ */
1363
+
1364
+ /**
1365
+ * The caller-supplied durable dedup identity for an effect — stable across the
1366
+ * re-fire (e.g. `` `${runId}:${violationId}` ``). Unlike the ledger's monotonic
1367
+ * `DeliveryId`, it is chosen by the domain so the SAME external write resolves
1368
+ * to the SAME key on every activation.
1369
+ */
1370
+ type EffectKey = string;
1371
+ /** An effect keyed `key` has been APPLIED. Adds `key` to the set when folded. */
1372
+ interface EffectApplied extends Cmd<"effect_applied"> {
1373
+ readonly key: EffectKey;
1374
+ }
1375
+ /**
1376
+ * An applied-marker is no longer needed (its effect can no longer re-fire, e.g.
1377
+ * its ledger entry was confirmed) and may be pruned. Removes `key` when folded.
1378
+ */
1379
+ interface EffectForgotten extends Cmd<"effect_forgotten"> {
1380
+ readonly key: EffectKey;
1381
+ }
1382
+ /** The event union the applied-marker set folds over. */
1383
+ type AppliedEffectsEvent = EffectApplied | EffectForgotten;
1384
+ /** The set of keys whose effect has been applied. A pure fold result. */
1385
+ type AppliedEffects = ReadonlySet<EffectKey>;
1386
+ /** The empty applied set — the fold's seed (a fresh actor has applied nothing). */
1387
+ declare function emptyApplied(): AppliedEffects;
1388
+ /**
1389
+ * Apply ONE event — the reducer at the heart of the fold.
1390
+ *
1391
+ * - `effect_applied` → add `key` (a duplicate add is a no-op — a re-fired
1392
+ * already-applied key tolerated by at-least-once).
1393
+ * - `effect_forgotten` → remove `key` (a no-op if already absent).
1394
+ *
1395
+ * Pure: returns a NEW set on change, never mutates the input — so folding the
1396
+ * same events twice (live and post-eviction) yields equal results.
1397
+ */
1398
+ declare function applyAppliedEvent(applied: AppliedEffects, event: AppliedEffectsEvent): AppliedEffects;
1399
+ /**
1400
+ * THE FOLD. Rebuild the applied set from an event stream — the same fold shape
1401
+ * `replay`/`foldLedger` use. After a simulated eviction this is called over the
1402
+ * persisted events and yields a set equal to the never-evicted one, because a
1403
+ * pure fold over identical input is identical. This is what proves "no side
1404
+ * table": the set is `events` reduced, nothing more.
1405
+ */
1406
+ declare function foldApplied(events: Iterable<AppliedEffectsEvent>): AppliedEffects;
1407
+ /** Has this key's effect already been applied (folded into the set)? */
1408
+ declare function isApplied(applied: AppliedEffects, key: EffectKey): boolean;
1409
+ /**
1410
+ * A keyed external effect. `key` is the durable dedup identity; `fn` is the
1411
+ * irreversible external write (HTTP write, DB upsert, finding/ticket publish).
1412
+ */
1413
+ interface IdempotentEffect<A> {
1414
+ readonly key: EffectKey;
1415
+ readonly fn: () => Promise<A> | A;
1416
+ }
1417
+ /**
1418
+ * Pair a durable `key` with an external effect `fn` — the brick's primary
1419
+ * surface. Run it through {@link appliedEffects}'s guard; a re-fire whose key is
1420
+ * already applied is a proven no-op (`fn` is not invoked a second time).
1421
+ */
1422
+ declare function idempotentEffect<A>(key: EffectKey, fn: () => Promise<A> | A): IdempotentEffect<A>;
1423
+ /**
1424
+ * The outcome of running a keyed effect through the guard. A boolean-discriminated
1425
+ * union so the skipped arm cannot carry a (never-produced) `result`/`event`:
1426
+ *
1427
+ * - `ran: true` — first application: `fn` ran, `result` is its value, and
1428
+ * `event` is the `effect_applied` marker the caller MUST persist (fold into
1429
+ * State) so future re-fires are swallowed.
1430
+ * - `ran: false` — the key was already applied: `fn` was NOT invoked, the
1431
+ * duplicate is swallowed, and there is no new event to persist.
1432
+ */
1433
+ type IdempotentOutcome<A> = {
1434
+ readonly ran: true;
1435
+ readonly key: EffectKey;
1436
+ readonly result: A;
1437
+ readonly event: EffectApplied;
1438
+ } | {
1439
+ readonly ran: false;
1440
+ readonly key: EffectKey;
1441
+ };
1442
+ /**
1443
+ * A live guard over an applied-marker set. Mirrors `pendingEffectsLedger`'s
1444
+ * recorder shape: it keeps an in-memory mirror folded from the same events the
1445
+ * caller persists, so the dedup decision is available without a re-read.
1446
+ *
1447
+ * - `run(effect)` — apply-once: skip (no `fn`) if the key is already applied,
1448
+ * else await `fn` and yield the `effect_applied` event to PERSIST.
1449
+ * - `forget(key)` — prune a marker once its effect can no longer re-fire;
1450
+ * yields the `effect_forgotten` event to PERSIST.
1451
+ * - `applied()` — the current folded set.
1452
+ * - `isApplied(key)`— convenience membership test against the mirror.
1453
+ */
1454
+ interface AppliedEffectsGuard {
1455
+ run<A>(effect: IdempotentEffect<A>): Promise<IdempotentOutcome<A>>;
1456
+ forget(key: EffectKey): {
1457
+ readonly event: EffectForgotten;
1458
+ };
1459
+ applied(): AppliedEffects;
1460
+ isApplied(key: EffectKey): boolean;
1461
+ }
1462
+ /**
1463
+ * Build a live guard. `restore` rehydrates it after an eviction: pass the events
1464
+ * read back from the log (or the applied set carried in the reloaded snapshot,
1465
+ * as single-element `effect_applied` events) so the mirror reflects what was
1466
+ * already applied. Omit `restore` for a fresh actor.
1467
+ */
1468
+ declare function appliedEffects(restore?: {
1469
+ readonly events?: Iterable<AppliedEffectsEvent>;
1470
+ }): AppliedEffectsGuard;
1471
+
1472
+ /**
1473
+ * @demlik/tea/do — generic hibernatable PRESENCE + BROADCAST for a NATIVE
1474
+ * (non-agent) Durable-Object grain.
1475
+ *
1476
+ * ## The gap this fills
1477
+ *
1478
+ * `./host` ships `acceptCommandSocket` / `acceptDurableCommandSocket` /
1479
+ * `broadcast` / `broadcastHibernatable`, but those are framed for — and live
1480
+ * alongside — the LLM-agent **deferred-tool gateway** (`createAgentHost`): the
1481
+ * socket is "the command-runner socket", the frame is "a command", and the
1482
+ * fan-out is the agent's `sendCommand` loop. A plain realtime grain — a game
1483
+ * room, a presence list, a collab document — does not have an agent, a gateway,
1484
+ * or commands. It just wants to accept hibernatable client sockets and fan a
1485
+ * frame out to whoever is connected. Today every such grain (vortex's
1486
+ * `ArenaGrain`, #162) hand-rolls the same `ctx.acceptWebSocket(server)` +
1487
+ * `for (const ws of ctx.getWebSockets()) ws.send(...)` boilerplate (#181).
1488
+ *
1489
+ * This module lifts exactly that, decoupled from the agent host:
1490
+ *
1491
+ * - {@link broadcastFrame} — the fan-out, with the **socket set as the input**
1492
+ * (not a `ctx`), so it is a pure function testable with fake sockets and
1493
+ * reusable over any iterable (`ctx.getWebSockets()`, a filtered subset, a
1494
+ * test array). It serializes once, sends to every OPEN socket, and skips a
1495
+ * closed/errored one — returning a {@link BroadcastReport} count rather than
1496
+ * swallowing the skip silently (errors are data).
1497
+ * - {@link registerHibernatableSocket} — register an already-created server
1498
+ * socket on the DO via the Cloudflare **Hibernation API**
1499
+ * (`ctx.acceptWebSocket`), optionally tagging it and persisting a
1500
+ * per-socket attachment that survives eviction. Pure over the `ctx` +
1501
+ * socket seam, so the registration logic is testable with fakes.
1502
+ * - {@link acceptPresenceSocket} — the full upgrade: build the
1503
+ * `WebSocketPair`, register the server end, and return the `server` socket
1504
+ * plus the 101 upgrade `Response`. The one piece that touches the
1505
+ * runtime-only `WebSocketPair` (so it is exercised by type, not by a unit
1506
+ * test without workerd) — the registration it delegates to *is* unit-tested.
1507
+ * - {@link presenceCount} — the live connection count off `ctx.getWebSockets`,
1508
+ * the trivial roster-size lift a presence grain reaches for.
1509
+ *
1510
+ * It is transport/Msg-agnostic: `frame` is `unknown` (JSON-serialized by
1511
+ * default, or via a caller `serialize`), and nothing here references an agent,
1512
+ * a gateway, a Sub, or a Msg. It is ADDITIVE — `./host`'s agent-coupled
1513
+ * helpers keep working untouched for the agent consumer.
1514
+ */
1515
+ /**
1516
+ * The minimal "send a string frame" surface {@link broadcastFrame} needs. A real
1517
+ * Cloudflare `WebSocket` satisfies it structurally (its `send` accepts a string
1518
+ * and it carries a numeric `readyState`), so `ctx.getWebSockets()` flows in
1519
+ * directly; a test supplies a fake with just `send` (and optionally
1520
+ * `readyState`).
1521
+ *
1522
+ * `readyState` is OPTIONAL: when present and not {@link WS_READY_STATE_OPEN},
1523
+ * the socket is skipped WITHOUT calling `send`; when absent, `send` is attempted
1524
+ * and a throw is caught (a fake that models only liveness via a throwing `send`
1525
+ * needs no `readyState`).
1526
+ */
1527
+ interface PresenceSocket {
1528
+ send(message: string): void;
1529
+ readonly readyState?: number;
1530
+ }
1531
+ /**
1532
+ * The Hibernation slice of `DurableObjectState` a presence grain needs — the
1533
+ * accept that hands the socket to the runtime (surviving eviction) and the
1534
+ * getter that repopulates the live set after a wake. A real `DurableObjectState`
1535
+ * satisfies this structurally; a test supplies a fake (no live Workers runtime
1536
+ * needed to exercise the accept/registry seam).
1537
+ */
1538
+ interface PresenceCtx {
1539
+ acceptWebSocket(ws: WebSocket, tags?: string[]): void;
1540
+ getWebSockets(tag?: string): WebSocket[];
1541
+ }
1542
+ /**
1543
+ * The OPEN `readyState` value (`WebSocket.READY_STATE_OPEN` in the Cloudflare
1544
+ * runtime). Named here so {@link broadcastFrame} can skip a non-open socket
1545
+ * without referencing the runtime constant (keeping the fold pure + testable).
1546
+ */
1547
+ declare const WS_READY_STATE_OPEN = 1;
1548
+ /**
1549
+ * What a {@link broadcastFrame} fan-out did, surfaced rather than swallowed:
1550
+ * `sent` is the number of sockets the frame reached; `skipped` is the number
1551
+ * passed over (not OPEN, errored on `send`, or the `except` socket). A caller
1552
+ * can log or assert these — a silent fan-out hides a room going dark.
1553
+ */
1554
+ interface BroadcastReport {
1555
+ readonly sent: number;
1556
+ readonly skipped: number;
1557
+ }
1558
+ /** Optional knobs for {@link broadcastFrame}. */
1559
+ interface BroadcastOptions<S extends PresenceSocket> {
1560
+ /**
1561
+ * A socket to exclude from the fan-out — typically the sender, so a collab /
1562
+ * chat frame is not echoed back to its originator. Compared by identity.
1563
+ */
1564
+ readonly except?: S;
1565
+ /**
1566
+ * How to turn `frame` into the wire string. Defaults to `JSON.stringify`.
1567
+ * Override for a non-JSON wire format (e.g. a pre-encoded string passthrough).
1568
+ */
1569
+ readonly serialize?: (frame: unknown) => string;
1570
+ }
1571
+ /**
1572
+ * Serialize `frame` ONCE and send it to every OPEN socket in `sockets`, skipping
1573
+ * any that is closed, errors on `send`, or is the `except` socket. The socket
1574
+ * set is the INPUT (not a `ctx`), so this is a pure function: pass
1575
+ * `ctx.getWebSockets()` in a live grain, a filtered subset for a targeted
1576
+ * fan-out, or a fake array in a test.
1577
+ *
1578
+ * Skip semantics (a dead socket never crashes the broadcast):
1579
+ * - `readyState` present and not {@link WS_READY_STATE_OPEN} → skipped, `send`
1580
+ * not called;
1581
+ * - `send` throws (a socket closing mid-fan-out) → skipped, the throw is
1582
+ * caught (the close lifecycle reaps it);
1583
+ * - the `except` socket → skipped.
1584
+ *
1585
+ * Returns a {@link BroadcastReport} of `{ sent, skipped }` — the skip is data,
1586
+ * not silence.
1587
+ */
1588
+ declare function broadcastFrame<S extends PresenceSocket>(sockets: Iterable<S>, frame: unknown, options?: BroadcastOptions<S>): BroadcastReport;
1589
+ /** Optional knobs for {@link registerHibernatableSocket} / {@link acceptPresenceSocket}. */
1590
+ interface RegisterOptions<A> {
1591
+ /**
1592
+ * Hibernation tags for the socket — the same strings `ctx.getWebSockets(tag)`
1593
+ * filters on, so a grain can fan out to a subset (e.g. a team, a channel).
1594
+ */
1595
+ readonly tags?: readonly string[];
1596
+ /**
1597
+ * A per-socket value to persist via `server.serializeAttachment` so it
1598
+ * survives DO eviction (e.g. the player/session id the grain re-reads in
1599
+ * `webSocketMessage`/`webSocketClose`). Omitted ⇒ no attachment written.
1600
+ */
1601
+ readonly attachment?: A;
1602
+ }
1603
+ /**
1604
+ * The subset of `WebSocket` {@link registerHibernatableSocket} writes to — the
1605
+ * attachment serializer. A real server `WebSocket` satisfies it; a test supplies
1606
+ * a fake that records the serialized attachment.
1607
+ */
1608
+ interface AttachableSocket {
1609
+ serializeAttachment(value: unknown): void;
1610
+ }
1611
+ /**
1612
+ * Register an already-created `server` socket on the DO via the Cloudflare
1613
+ * **Hibernation API**: `ctx.acceptWebSocket(server, tags)` hands the socket to
1614
+ * the runtime (it survives eviction and re-delivers inbound frames to the DO's
1615
+ * `webSocketMessage` lifecycle method), and — when an `attachment` is supplied —
1616
+ * persists it via `server.serializeAttachment` so it survives the wake.
1617
+ *
1618
+ * Pure over the `ctx` + socket seam (no `WebSocketPair` construction), so the
1619
+ * registration logic — that accept is called with the right tags, that the
1620
+ * attachment is serialized iff present — is unit-testable with fakes.
1621
+ * {@link acceptPresenceSocket} is the thin wrapper that also builds the pair.
1622
+ */
1623
+ declare function registerHibernatableSocket<S extends AttachableSocket, A>(ctx: PresenceCtx, server: S, options?: RegisterOptions<A>): void;
1624
+ /** What {@link acceptPresenceSocket} hands back: the live server end (to send
1625
+ * initial frames on) and the 101 upgrade `Response` (to return from `fetch`). */
1626
+ interface PresenceUpgrade {
1627
+ /** The server end of the pair — send the welcome / initial snapshot on it. */
1628
+ readonly server: WebSocket;
1629
+ /** The 101 Switching-Protocols upgrade response carrying the client end. */
1630
+ readonly response: Response;
1631
+ }
1632
+ /**
1633
+ * Accept a client WebSocket upgrade on a native DO and register it for
1634
+ * hibernation. Builds the `WebSocketPair`, registers the server end via
1635
+ * {@link registerHibernatableSocket} (tags + optional attachment), and returns
1636
+ * the `server` socket plus the 101 upgrade {@link PresenceUpgrade.response}.
1637
+ *
1638
+ * The caller's `fetch` collapses to: send any initial frames on `server`
1639
+ * (welcome, current roster/snapshot), then `return upgrade.response`. This lifts
1640
+ * the `new WebSocketPair()` + `ctx.acceptWebSocket` + `serializeAttachment` +
1641
+ * `new Response(null, { status: 101, webSocket })` boilerplate a presence grain
1642
+ * hand-rolls.
1643
+ *
1644
+ * NOTE: `WebSocketPair` is a runtime-only global (no workerd in unit tests), so
1645
+ * this assembly is exercised by TYPE here; its registration seam is unit-tested
1646
+ * via {@link registerHibernatableSocket}.
1647
+ *
1648
+ * @example
1649
+ * async fetch(request: Request): Promise<Response> {
1650
+ * if (request.headers.get("Upgrade") !== "websocket") {
1651
+ * return new Response("expected a websocket upgrade", { status: 426 });
1652
+ * }
1653
+ * const { server, response } = acceptPresenceSocket(this.ctx, {
1654
+ * attachment: { playerId },
1655
+ * });
1656
+ * server.send(JSON.stringify(welcome));
1657
+ * server.send(JSON.stringify(room.snapshot()));
1658
+ * return response;
1659
+ * }
1660
+ */
1661
+ declare function acceptPresenceSocket<A>(ctx: PresenceCtx, options?: RegisterOptions<A>): PresenceUpgrade;
1662
+ /**
1663
+ * The live connection count for a presence grain — `ctx.getWebSockets(tag)
1664
+ * .length`, repopulated by the runtime after a wake. Pass a `tag` to count only
1665
+ * the sockets registered under it. The trivial roster-size lift a presence /
1666
+ * game grain reaches for (vortex's `connectionCount`).
1667
+ */
1668
+ declare function presenceCount(ctx: PresenceCtx, tag?: string): number;
2
1669
 
3
1670
  /**
4
1671
  * @demlik/tea/do — Durable Object adapter for `@demlik/tea`.
5
1672
  *
6
- * Two exports drive the integration:
1673
+ * The integration is built on ONE transport model and one durability primitive:
7
1674
  *
8
1675
  * 1. `doStore<S>(storage, parse, key?)` — `Store<S>` impl backed by
9
1676
  * `DurableObjectStorage`. JSON-stringifies on save, JSON-parses on load
@@ -15,31 +1682,46 @@ import { Store, SubId, Sub } from '../index.js';
15
1682
  * synchronously"). Shape mismatch is NOT a throw — `parse` returns
16
1683
  * `null` and the substrate boots fresh.
17
1684
  *
18
- * 2. `doSubscribe<M, Ctx>()` — handler registry for the two DO-native sub
19
- * types this package owns:
20
- * - `do_alarm` — schedules a real DO alarm via `state.storage.setAlarm`;
21
- * the cooperating DO's `alarm()` lifecycle method dispatches the sub's
22
- * msg. Cleanup clears the alarm.
23
- * - `do_ws` — registers a WebSocket-id'd listener in a per-DO registry
24
- * that the cooperating DO's `webSocketMessage()` consults. Cleanup
25
- * deregisters.
26
- *
27
- * BOTH `do_alarm` and `do_ws` rely on the host DO cooperating: alarm and
28
- * webSocketMessage are DO lifecycle methods, not JS events the handler can
29
- * subscribe to from the outside. The cooperating DO must:
30
- *
31
- * - Maintain a `Ctx` that exposes `state: DurableObjectState` AND the
32
- * subscriber registries (`wsRegistry`, `alarmRegistry`). The handlers read
33
- * from `ctx`.
34
- * - Forward `alarm()` callbacks into the alarm registry by id, dispatching
35
- * the registered msg.
36
- * - Forward `webSocketMessage(ws, data)` callbacks into the ws registry,
37
- * dispatching `sub.msg(data)` for matching `socketId`s.
38
- *
39
- * `src/test-worker.ts` is the canonical example of this cooperation. See
40
- * `test/README.md` for the convention.
1685
+ * 2. `deferredGateway` + `createAgentHost` (from `./host`) — THE transport for
1686
+ * a DO-hosted agent. A `createAgent().toMachine()` machine owns exactly one
1687
+ * Sub type, `DeadlineSub` (the agent's own retry + watchdog timers); it has
1688
+ * no DO-native Sub variant to compose, and it needs none. The gateway owns
1689
+ * each deferred tool round-trip as a Promise the interpret cell awaits:
1690
+ * - Inbound WebSocket frames are bridged straight into `gateway.settle(...)`
1691
+ * from the DO's `webSocketMessage` lifecycle method — a direct dispatch,
1692
+ * NOT a Sub.
1693
+ * - The per-tool deadline is a `setTimeout` inside the gateway, NOT a DO
1694
+ * alarm.
1695
+ * One model: the transport lives outside the Sub system, bridged into
1696
+ * `dispatch` by the gateway. See `./host`'s module doc for the full rationale
1697
+ * and `.patterns/tea/durable-actors.md` for the host-layer north star.
1698
+ *
1699
+ * (Historical note: this module previously also shipped `do_ws` / `do_alarm`
1700
+ * Subs + a `doSubscribe` registry. Because `toMachine` fixes the agent machine's
1701
+ * Sub type to `DeadlineSub`, those Subs could never union into an agent host's
1702
+ * Sub set — they were structurally unusable by the flagship consumer and had no
1703
+ * callers. They were dropped in favor of the gateway as the single transport.)
41
1704
  */
42
1705
 
1706
+ /**
1707
+ * Options for {@link doStore}. Mirrors the optional-bag shape
1708
+ * `doEventSourcedStore` already uses for its `parse`/`keys`, so the two DO
1709
+ * stores read alike.
1710
+ */
1711
+ interface DoStoreOptions<S> {
1712
+ /** Storage key the snapshot cell lives at. Defaults to `@@state`. */
1713
+ readonly key?: string;
1714
+ /**
1715
+ * The serialize half of the boundary (#182) — the INVERSE of `doStore`'s
1716
+ * `parse`. Maps the live `S` to a JSON-safe carrier before `JSON.stringify`,
1717
+ * so a Model holding a `Map`/`Set`/`Date` (which `JSON.stringify` would
1718
+ * silently flatten) round-trips: `parse` reconstructs the rich `S` from the
1719
+ * carrier on load. Omit for a plain-JSON Model (the identity default — the
1720
+ * `Record`-not-`Map` constraint, unchanged). When present, `parse` MUST
1721
+ * accept whatever `serialize` produces.
1722
+ */
1723
+ readonly serialize?: (state: S) => unknown;
1724
+ }
43
1725
  /**
44
1726
  * `Store<S>` over `DurableObjectStorage`.
45
1727
  *
@@ -62,74 +1744,23 @@ import { Store, SubId, Sub } from '../index.js';
62
1744
  * behavior in `init`/`update`. After this, the boundary parse rejects the
63
1745
  * old shape (returns null) and the DO boots fresh, picking up its new
64
1746
  * schema from the next dispatch onward.
65
- */
66
- declare function doStore<S>(storage: DurableObjectStorage, parse: (raw: unknown) => S | null, key?: string): Store<S>;
67
- /**
68
- * A DO alarm sub: fires `msg` at `firesAt` (epoch ms). The substrate diffs
69
- * subs by `id`; same id across transitions = same alarm. To reschedule, emit
70
- * a DIFFERENT id (e.g., `audit-timeout-v2`).
71
- */
72
- type DoAlarmSub<M> = {
73
- id: SubId;
74
- type: "do_alarm";
75
- firesAt: number;
76
- msg: M;
77
- };
78
- /**
79
- * A DO WebSocket sub: routes incoming messages on `socketId` through
80
- * `sub.msg(data)` to dispatch.
81
- */
82
- type DoWsSub<M> = {
83
- id: SubId;
84
- type: "do_ws";
85
- socketId: string;
86
- msg: (data: string) => M;
87
- };
88
- type DoSub<M> = DoAlarmSub<M> | DoWsSub<M>;
89
- type AssertDoSubIsSub<T extends Sub = DoSub<unknown>> = T;
90
- /**
91
- * Registry of pending alarm dispatches keyed by sub `id`. The cooperating DO's
92
- * `alarm()` lifecycle method drains this and dispatches each msg.
93
- *
94
- * One alarm-queue cell at a time per DO (DO has exactly one alarm slot). We
95
- * still key by `id` so multiple alarm subs scheduled in the same reconcile
96
- * pass don't clobber each other — the earliest `firesAt` wins (DO native
97
- * behavior) and `alarm()` drains everything due by the fire time.
98
- */
99
- type DoAlarmRegistry<M> = Map<string, {
100
- firesAt: number;
101
- msg: M;
102
- }>;
103
- /**
104
- * Registry of WebSocket message subscribers keyed by sub `id`. The cooperating
105
- * DO's `webSocketMessage(ws, data)` consults this and dispatches
106
- * `sub.msg(data)` for entries matching the incoming `socketId`.
107
- */
108
- type DoWsRegistry<M> = Map<string, {
109
- socketId: string;
110
- msg: (data: string) => M;
111
- }>;
112
- /**
113
- * Minimum shape a cooperating DO must put on its `Ctx` for `doSubscribe`
114
- * handlers to wire up. Users may extend with anything else they like.
115
- */
116
- interface DoSubscribeCtx<M> {
117
- state: DurableObjectState;
118
- alarmRegistry: DoAlarmRegistry<M>;
119
- wsRegistry: DoWsRegistry<M>;
120
- }
121
- /**
122
- * Build the `subscribe` handler registry for `do_alarm` and `do_ws` subs.
123
1747
  *
124
- * Returned object shape matches `Machine#subscribe`'s `[K in U['type']]:
125
- * (sub, ctx, dispatch) => () => void` contract for `U = DoSub<M>`.
1748
+ * Serialization sharp edge (#182): `save` JSON-serializes the Model, and
1749
+ * `JSON.stringify` does NOT survive a `Map`/`Set`/`Date` (a `Map` stringifies
1750
+ * to `{}`, silently losing every entry). A Model that wants a `Map` (the
1751
+ * natural "roster keyed by id") therefore supplies a `serialize` hook — the
1752
+ * INVERSE of `parse` — so the boundary owns both directions: `serialize` turns
1753
+ * the live `S` into a JSON-safe carrier on the way out, `parse` reconstructs
1754
+ * the rich `S` from that carrier on the way back in. They are a matched pair:
1755
+ * `parse(JSON.parse(JSON.stringify(serialize(state))))` must round-trip to a
1756
+ * value equal to `state`. Omit `serialize` and the Model must stay plain-JSON
1757
+ * (the `Record`-not-`Map` constraint the default imposes, unchanged).
126
1758
  *
127
- * Generic `Ctx` is user-defined and must extend `DoSubscribeCtx<M>` so handlers
128
- * can reach `state.storage`, `alarmRegistry`, and `wsRegistry`.
1759
+ * @param parse - boundary parse, the deserialize half (`unknown -> S | null`).
1760
+ * @param keyOrOptions - either the storage key (legacy positional form,
1761
+ * defaults to `@@state`) or a {@link DoStoreOptions} bag carrying `key`
1762
+ * and/or the `serialize` hook. A bare string is treated as `{ key }`.
129
1763
  */
130
- declare function doSubscribe<M, Ctx extends DoSubscribeCtx<M>>(): {
131
- do_alarm: (sub: DoAlarmSub<M>, ctx: Ctx, dispatch: (msg: M) => void) => () => void;
132
- do_ws: (sub: DoWsSub<M>, ctx: Ctx, dispatch: (msg: M) => void) => () => void;
133
- };
1764
+ declare function doStore<S>(storage: DurableObjectStorage, parse: (raw: unknown) => S | null, keyOrOptions?: string | DoStoreOptions<S>): Store<S>;
134
1765
 
135
- export { type AssertDoSubIsSub, type DoAlarmRegistry, type DoAlarmSub, type DoSub, type DoSubscribeCtx, type DoWsRegistry, type DoWsSub, doStore, doSubscribe };
1766
+ export { type AgentHost, type AgentHostConfig, type AlarmStorage, type AppliedEffects, type AppliedEffectsEvent, type AppliedEffectsGuard, type AttachableSocket, type BroadcastOptions, type BroadcastReport, type DeferredGateway, type DoStoreOptions, type DurableCommandCarrier, type DurableTimer, type DurableTimerConfig, type EffectApplied, EffectConfirmed, type EffectForgotten, type EffectKey, EffectOwed, type EventLogRange, type EventSourcedOptions, type EventSourcedStore, type ExecuteStep, type HibernatableCtx, type IdempotentEffect, type IdempotentOutcome, type NextStep, PendingEffectsRecorder, type PersistedEvent, type PresenceCtx, type PresenceSocket, type PresenceUpgrade, type Projection, type ProjectionId, type ProjectionRegistry, type ProjectionRunner, type ProjectionUpdate, type RegisterOptions, type ResumePort, type RunStepLoopConfig, type SseHub, type StepCtx, type StepEngine, type StepHostConfig, type StepLoopOutcome, type StepOutcome, type StepRequest, type StepResponse, type StepResult, type StepTransport, WS_READY_STATE_OPEN, acceptCommandSocket, acceptDurableCommandSocket, acceptPresenceSocket, agentIsResumable, appliedEffects, applyAppliedEvent, autoBoot, bootResume, broadcast, broadcastFrame, broadcastHibernatable, constantTimeEqual, createAgentHost, deferredGateway, doEventSourcedStore, doStore, driveProjections, durableCommandCarrier, durableDeferredGateway, durableTimer, emptyApplied, foldApplied, idempotentEffect, isApplied, mintRunToken, presenceCount, projectionIdString, projectionRegistry, rebuildProjection, registerHibernatableSocket, reissueSurvivingEffects, runProjection, runStepLoop, sseFromAgentEvents, sseHub, sseProjection, stepHost };