@swirls/looms 0.0.2 → 0.2.0

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 (375) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/actor/cell.d.ts +9 -4
  3. package/dist/actor/cell.js +7 -11
  4. package/dist/actor/host.d.ts +7 -3
  5. package/dist/actor/host.js +57 -33
  6. package/dist/actor/index.d.ts +1 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +12 -1
  9. package/dist/actor/routing.js +57 -51
  10. package/dist/agent/agent-session.d.ts +5 -0
  11. package/dist/agent/agent-session.js +270 -0
  12. package/dist/agent/debugger/agent-chat.d.ts +2 -0
  13. package/dist/agent/debugger/agent-chat.js +176 -0
  14. package/dist/agent/debugger/index.d.ts +5 -0
  15. package/dist/agent/debugger/index.js +19 -0
  16. package/dist/agent/debugger/summarize.d.ts +2 -0
  17. package/dist/agent/debugger/summarize.js +51 -0
  18. package/dist/agent/debugger/token-usage.d.ts +3 -0
  19. package/dist/agent/debugger/token-usage.js +11 -0
  20. package/dist/agent/definitions-store.d.ts +2 -3
  21. package/dist/agent/definitions-store.js +2 -4
  22. package/dist/agent/definitions.d.ts +47 -29
  23. package/dist/agent/definitions.js +39 -12
  24. package/dist/agent/effects.d.ts +359 -2
  25. package/dist/agent/effects.js +60 -34
  26. package/dist/agent/events.d.ts +175 -19
  27. package/dist/agent/events.js +45 -10
  28. package/dist/agent/index.d.ts +1 -0
  29. package/dist/agent/index.js +1 -0
  30. package/dist/agent/llm.d.ts +11 -2
  31. package/dist/agent/llm.js +13 -3
  32. package/dist/agent/module.d.ts +812 -78
  33. package/dist/agent/module.js +16 -4
  34. package/dist/agent/scope.d.ts +174 -18
  35. package/dist/agent/signals.d.ts +26 -1
  36. package/dist/agent/signals.js +79 -0
  37. package/dist/agent/threads.d.ts +3 -33
  38. package/dist/agent/threads.js +20 -25
  39. package/dist/agent/tool-schema.d.ts +1 -17
  40. package/dist/agent/tool-schema.js +3 -43
  41. package/dist/agent/types.d.ts +49 -4
  42. package/dist/agent/types.js +5 -5
  43. package/dist/ai-vercel/index.d.ts +1 -0
  44. package/dist/ai-vercel/index.js +1 -0
  45. package/dist/ai-vercel/messages.js +22 -7
  46. package/dist/ai-vercel/vercel-evaluator.d.ts +36 -0
  47. package/dist/ai-vercel/vercel-evaluator.js +73 -0
  48. package/dist/ai-vercel/vercel-llm.js +64 -44
  49. package/dist/approval/debugger/approvals-view.d.ts +3 -0
  50. package/dist/approval/debugger/approvals-view.js +16 -0
  51. package/dist/approval/debugger/index.d.ts +4 -0
  52. package/dist/approval/debugger/index.js +11 -0
  53. package/dist/approval/debugger/summarize.d.ts +2 -0
  54. package/dist/approval/debugger/summarize.js +17 -0
  55. package/dist/approval/effects.d.ts +89 -0
  56. package/dist/approval/module.d.ts +179 -29
  57. package/dist/approval/projections.d.ts +0 -14
  58. package/dist/approval/projections.js +20 -3
  59. package/dist/approval/signals.d.ts +2 -0
  60. package/dist/approval/signals.js +18 -2
  61. package/dist/cli/cli.js +2 -0
  62. package/dist/cli/commands.js +12 -16
  63. package/dist/client/client.d.ts +1213 -18
  64. package/dist/client/client.js +120 -120
  65. package/dist/client/index.d.ts +1 -1
  66. package/dist/client/index.js +1 -1
  67. package/dist/client/sse.d.ts +13 -0
  68. package/dist/client/sse.js +38 -44
  69. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  70. package/dist/cloudflare/alarm-scheduler.js +34 -7
  71. package/dist/cloudflare/durable-object.d.ts +10 -2
  72. package/dist/cloudflare/durable-object.js +59 -28
  73. package/dist/cloudflare/event-store.d.ts +7 -2
  74. package/dist/cloudflare/event-store.js +8 -7
  75. package/dist/cloudflare/index.d.ts +2 -2
  76. package/dist/cloudflare/index.js +2 -2
  77. package/dist/cloudflare/router.d.ts +1 -1
  78. package/dist/cloudflare/router.js +15 -16
  79. package/dist/core/catalog.d.ts +3 -3
  80. package/dist/core/catalog.js +2 -5
  81. package/dist/core/coalesce.js +5 -3
  82. package/dist/core/compose.d.ts +2 -1
  83. package/dist/core/compose.js +19 -2
  84. package/dist/core/debugger-host.d.ts +16 -0
  85. package/dist/core/debugger-host.js +1 -0
  86. package/dist/core/definition-store.d.ts +11 -0
  87. package/dist/core/definition-store.js +6 -0
  88. package/dist/core/effects.d.ts +127 -15
  89. package/dist/core/effects.js +89 -17
  90. package/dist/core/envelope.d.ts +27 -7
  91. package/dist/core/envelope.js +20 -6
  92. package/dist/core/event-codec.d.ts +4 -2
  93. package/dist/core/event-codec.js +10 -5
  94. package/dist/core/event-index.js +1 -3
  95. package/dist/core/fold.d.ts +3 -0
  96. package/dist/core/fold.js +171 -24
  97. package/dist/core/ids.d.ts +5 -0
  98. package/dist/core/ids.js +35 -2
  99. package/dist/core/index.d.ts +7 -1
  100. package/dist/core/index.js +6 -1
  101. package/dist/core/kind.d.ts +22 -0
  102. package/dist/core/kind.js +17 -0
  103. package/dist/core/limits.d.ts +34 -0
  104. package/dist/core/limits.js +46 -0
  105. package/dist/core/match.d.ts +1 -0
  106. package/dist/core/match.js +10 -1
  107. package/dist/core/module-scope.d.ts +22 -20
  108. package/dist/core/module-scope.js +38 -28
  109. package/dist/core/module.d.ts +18 -5
  110. package/dist/core/module.js +4 -0
  111. package/dist/core/projection.d.ts +13 -13
  112. package/dist/core/projection.js +9 -5
  113. package/dist/core/protocol.d.ts +68 -11
  114. package/dist/core/protocol.js +82 -11
  115. package/dist/core/published.d.ts +43 -0
  116. package/dist/core/published.js +44 -0
  117. package/dist/core/replay.d.ts +312 -3
  118. package/dist/core/replay.js +11 -1
  119. package/dist/core/schema.d.ts +11 -20
  120. package/dist/core/schema.js +63 -80
  121. package/dist/core/serialize.js +2 -4
  122. package/dist/core/snapshot-store.d.ts +6 -1
  123. package/dist/core/snapshot-store.js +15 -2
  124. package/dist/core/snapshots.d.ts +5 -1
  125. package/dist/core/snapshots.js +10 -15
  126. package/dist/core/sqlite-store.js +95 -11
  127. package/dist/core/state.d.ts +142 -4
  128. package/dist/core/state.js +78 -0
  129. package/dist/core/store.d.ts +38 -3
  130. package/dist/core/store.js +77 -22
  131. package/dist/core/thread.d.ts +10 -9
  132. package/dist/core/types.d.ts +5 -8
  133. package/dist/core/types.js +28 -24
  134. package/dist/core/validate-event.js +14 -4
  135. package/dist/core/worker-callback.d.ts +17 -0
  136. package/dist/core/worker-callback.js +1 -0
  137. package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
  138. package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
  139. package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  140. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
  141. package/dist/debugger/app/index.html +14 -0
  142. package/dist/debugger/components/composer.d.ts +17 -0
  143. package/dist/debugger/components/composer.js +25 -0
  144. package/dist/debugger/components/debugger-split.d.ts +2 -1
  145. package/dist/debugger/components/debugger-split.js +2 -2
  146. package/dist/debugger/components/event-inspector.d.ts +2 -1
  147. package/dist/debugger/components/event-inspector.js +25 -16
  148. package/dist/debugger/components/event-stream.d.ts +2 -1
  149. package/dist/debugger/components/event-stream.js +14 -74
  150. package/dist/debugger/components/follow-list.d.ts +19 -0
  151. package/dist/debugger/components/follow-list.js +96 -0
  152. package/dist/debugger/components/json-tree.d.ts +11 -0
  153. package/dist/debugger/components/json-tree.js +120 -0
  154. package/dist/debugger/components/json-view.js +2 -2
  155. package/dist/debugger/components/run-tree.js +53 -20
  156. package/dist/debugger/components/status-dot.d.ts +4 -0
  157. package/dist/debugger/components/status-dot.js +6 -2
  158. package/dist/debugger/context.d.ts +14 -0
  159. package/dist/debugger/context.js +17 -0
  160. package/dist/debugger/contracts.d.ts +7 -4
  161. package/dist/debugger/events/builtin-families.d.ts +3 -0
  162. package/dist/debugger/events/builtin-families.js +9 -0
  163. package/dist/debugger/events/catalog.d.ts +3 -4
  164. package/dist/debugger/events/catalog.js +27 -17
  165. package/dist/debugger/events/protocol-summarize.d.ts +1 -2
  166. package/dist/debugger/events/protocol-summarize.js +42 -24
  167. package/dist/debugger/index.d.ts +20 -6
  168. package/dist/debugger/index.js +19 -5
  169. package/dist/debugger/lib/cn.d.ts +1 -0
  170. package/dist/debugger/lib/cn.js +3 -0
  171. package/dist/debugger/lib/event-meta.d.ts +2 -3
  172. package/dist/debugger/lib/event-meta.js +6 -12
  173. package/dist/debugger/lib/json.d.ts +25 -0
  174. package/dist/debugger/lib/json.js +87 -0
  175. package/dist/debugger/lib/payload.d.ts +7 -0
  176. package/dist/debugger/lib/payload.js +11 -0
  177. package/dist/debugger/lib/time.d.ts +3 -0
  178. package/dist/debugger/lib/time.js +24 -0
  179. package/dist/debugger/plugin.d.ts +36 -0
  180. package/dist/debugger/plugin.js +14 -0
  181. package/dist/debugger/server/index.d.ts +14 -0
  182. package/dist/debugger/server/index.js +114 -0
  183. package/dist/debugger/shell/debugger-shell.d.ts +15 -0
  184. package/dist/debugger/shell/debugger-shell.js +43 -0
  185. package/dist/debugger/shell/definition-key.d.ts +3 -0
  186. package/dist/debugger/shell/definition-key.js +4 -0
  187. package/dist/debugger/shell/events-pane.d.ts +12 -0
  188. package/dist/debugger/shell/events-pane.js +25 -0
  189. package/dist/debugger/shell/projections-panel.d.ts +4 -0
  190. package/dist/debugger/shell/projections-panel.js +59 -0
  191. package/dist/debugger/shell/run-controls.d.ts +4 -0
  192. package/dist/debugger/shell/run-controls.js +72 -0
  193. package/dist/debugger/shell/run-search.d.ts +6 -0
  194. package/dist/debugger/shell/run-search.js +102 -0
  195. package/dist/debugger/shell/run-type-menu.d.ts +6 -0
  196. package/dist/debugger/shell/run-type-menu.js +11 -0
  197. package/dist/debugger/shell/schema-form.d.ts +31 -0
  198. package/dist/debugger/shell/schema-form.js +117 -0
  199. package/dist/debugger/shell/start-form.d.ts +3 -0
  200. package/dist/debugger/shell/start-form.js +75 -0
  201. package/dist/debugger/shell/use-start-run.d.ts +14 -0
  202. package/dist/debugger/shell/use-start-run.js +32 -0
  203. package/dist/debugger/shell/workspace.d.ts +6 -0
  204. package/dist/debugger/shell/workspace.js +15 -0
  205. package/dist/debugger/styles.css +111 -27
  206. package/dist/debugger/ui/button.d.ts +24 -0
  207. package/dist/debugger/ui/button.js +24 -0
  208. package/dist/debugger/ui/command.d.ts +9 -0
  209. package/dist/debugger/ui/command.js +24 -0
  210. package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
  211. package/dist/debugger/ui/dropdown-menu.js +37 -0
  212. package/dist/debugger/ui/panel.d.ts +14 -0
  213. package/dist/debugger/ui/panel.js +16 -0
  214. package/dist/debugger/ui/popover.d.ts +7 -0
  215. package/dist/debugger/ui/popover.js +17 -0
  216. package/dist/debugger/ui/resizable.d.ts +7 -0
  217. package/dist/debugger/ui/resizable.js +14 -0
  218. package/dist/debugger/ui/scroll-area.d.ts +2 -1
  219. package/dist/debugger/ui/scroll-area.js +5 -3
  220. package/dist/debugger/ui/select.d.ts +10 -0
  221. package/dist/debugger/ui/select.js +21 -0
  222. package/dist/debugger/ui/tabs.js +2 -2
  223. package/dist/debugger/ui/textarea.d.ts +3 -0
  224. package/dist/debugger/ui/textarea.js +7 -0
  225. package/dist/evaluator/definitions-store.d.ts +11 -0
  226. package/dist/evaluator/definitions-store.js +8 -0
  227. package/dist/evaluator/definitions.d.ts +18 -0
  228. package/dist/evaluator/definitions.js +4 -0
  229. package/dist/evaluator/effects.d.ts +226 -0
  230. package/dist/evaluator/effects.js +132 -0
  231. package/dist/evaluator/evaluator.d.ts +24 -0
  232. package/dist/evaluator/evaluator.js +67 -0
  233. package/dist/evaluator/events.d.ts +59 -0
  234. package/dist/evaluator/events.js +21 -0
  235. package/dist/evaluator/index.d.ts +11 -0
  236. package/dist/evaluator/index.js +11 -0
  237. package/dist/evaluator/module.d.ts +606 -0
  238. package/dist/evaluator/module.js +25 -0
  239. package/dist/evaluator/projections.d.ts +50 -0
  240. package/dist/evaluator/projections.js +20 -0
  241. package/dist/evaluator/scope.d.ts +59 -0
  242. package/dist/evaluator/scope.js +7 -0
  243. package/dist/evaluator/signals.d.ts +3 -0
  244. package/dist/evaluator/signals.js +17 -0
  245. package/dist/evaluator/threads.d.ts +62 -0
  246. package/dist/evaluator/threads.js +117 -0
  247. package/dist/evaluator/types.d.ts +125 -0
  248. package/dist/evaluator/types.js +51 -0
  249. package/dist/projectors/delivery.d.ts +83 -0
  250. package/dist/projectors/delivery.js +182 -0
  251. package/dist/projectors/index-model.d.ts +1 -1
  252. package/dist/projectors/index-model.js +4 -6
  253. package/dist/projectors/index.d.ts +2 -0
  254. package/dist/projectors/index.js +2 -0
  255. package/dist/projectors/memory.d.ts +1 -1
  256. package/dist/projectors/memory.js +10 -8
  257. package/dist/projectors/postgres.js +44 -58
  258. package/dist/projectors/projector.d.ts +17 -0
  259. package/dist/projectors/projector.js +26 -1
  260. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  261. package/dist/projectors/sqlite-delivery.js +147 -0
  262. package/dist/projectors/sqlite.js +23 -23
  263. package/dist/projectors/with-projectors.d.ts +7 -2
  264. package/dist/projectors/with-projectors.js +27 -28
  265. package/dist/react/notify.js +11 -8
  266. package/dist/react/react.d.ts +2 -3
  267. package/dist/react/react.js +14 -30
  268. package/dist/react/sse.d.ts +1 -1
  269. package/dist/react/sse.js +1 -1
  270. package/dist/react/store.d.ts +3 -3
  271. package/dist/react/store.js +109 -119
  272. package/dist/runtime/cancellation.d.ts +16 -0
  273. package/dist/runtime/cancellation.js +129 -0
  274. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  275. package/dist/runtime/deadline-transition-planner.js +139 -0
  276. package/dist/runtime/dispatch-effect.d.ts +3 -2
  277. package/dist/runtime/dispatch-effect.js +7 -13
  278. package/dist/runtime/errors.d.ts +51 -0
  279. package/dist/runtime/errors.js +39 -0
  280. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  281. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  282. package/dist/runtime/index.d.ts +3 -3
  283. package/dist/runtime/index.js +3 -3
  284. package/dist/runtime/ingress.d.ts +15 -0
  285. package/dist/runtime/ingress.js +42 -0
  286. package/dist/runtime/local-effect-executor.d.ts +21 -0
  287. package/dist/runtime/local-effect-executor.js +137 -0
  288. package/dist/runtime/looms.d.ts +30 -4
  289. package/dist/runtime/looms.js +57 -37
  290. package/dist/runtime/observer.d.ts +45 -0
  291. package/dist/runtime/observer.js +11 -0
  292. package/dist/runtime/repository.d.ts +55 -0
  293. package/dist/runtime/repository.js +293 -0
  294. package/dist/runtime/retry-policy.d.ts +16 -0
  295. package/dist/runtime/retry-policy.js +29 -0
  296. package/dist/runtime/run-execution-context.d.ts +30 -0
  297. package/dist/runtime/run-execution-context.js +56 -0
  298. package/dist/runtime/run-start.d.ts +4 -0
  299. package/dist/runtime/run-start.js +44 -0
  300. package/dist/runtime/runtime.d.ts +6 -87
  301. package/dist/runtime/runtime.js +248 -470
  302. package/dist/runtime/server/auth.d.ts +41 -0
  303. package/dist/runtime/server/auth.js +45 -0
  304. package/dist/runtime/server/debugger-host.d.ts +11 -0
  305. package/dist/runtime/server/debugger-host.js +32 -0
  306. package/dist/runtime/server/fetch-handler.d.ts +18 -0
  307. package/dist/runtime/server/fetch-handler.js +62 -0
  308. package/dist/runtime/server/http.d.ts +38 -0
  309. package/dist/runtime/server/http.js +66 -0
  310. package/dist/runtime/server/route-table.d.ts +55 -0
  311. package/dist/runtime/server/route-table.js +74 -0
  312. package/dist/runtime/server/routes/definitions.d.ts +3 -0
  313. package/dist/runtime/server/routes/definitions.js +20 -0
  314. package/dist/runtime/server/routes/effects.d.ts +3 -0
  315. package/dist/runtime/server/routes/effects.js +59 -0
  316. package/dist/runtime/server/routes/operations.d.ts +3 -0
  317. package/dist/runtime/server/routes/operations.js +14 -0
  318. package/dist/runtime/server/routes/runs.d.ts +3 -0
  319. package/dist/runtime/server/routes/runs.js +199 -0
  320. package/dist/runtime/server.d.ts +4 -8
  321. package/dist/runtime/server.js +6 -211
  322. package/dist/runtime/sse.d.ts +2 -1
  323. package/dist/runtime/sse.js +24 -17
  324. package/dist/runtime/types.d.ts +117 -0
  325. package/dist/runtime/types.js +1 -0
  326. package/dist/runtime/wake-engine.d.ts +20 -0
  327. package/dist/runtime/wake-engine.js +311 -0
  328. package/dist/runtime/wake-scheduler.d.ts +3 -4
  329. package/dist/runtime/wake-scheduler.js +25 -31
  330. package/dist/runtime/worker-callbacks.d.ts +16 -0
  331. package/dist/runtime/worker-callbacks.js +167 -0
  332. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  333. package/dist/runtime/worker-effect-dispatch.js +126 -0
  334. package/dist/s2/config.d.ts +1 -1
  335. package/dist/s2/config.js +1 -1
  336. package/dist/s2/lite.js +70 -82
  337. package/dist/s2/pagination.d.ts +15 -0
  338. package/dist/s2/pagination.js +36 -0
  339. package/dist/s2/projector.js +6 -5
  340. package/dist/s2/snapshot-store.js +188 -161
  341. package/dist/s2/store.d.ts +3 -21
  342. package/dist/s2/store.js +71 -165
  343. package/dist/testing/index.d.ts +15 -0
  344. package/dist/testing/index.js +53 -16
  345. package/dist/workflow/debugger/index.d.ts +4 -0
  346. package/dist/workflow/debugger/index.js +11 -0
  347. package/dist/workflow/debugger/nodes-view.d.ts +3 -0
  348. package/dist/workflow/debugger/nodes-view.js +13 -0
  349. package/dist/workflow/debugger/summarize.d.ts +2 -0
  350. package/dist/workflow/debugger/summarize.js +30 -0
  351. package/dist/workflow/definitions-store.d.ts +2 -3
  352. package/dist/workflow/definitions-store.js +2 -2
  353. package/dist/workflow/definitions.d.ts +78 -22
  354. package/dist/workflow/definitions.js +80 -14
  355. package/dist/workflow/effects.d.ts +453 -107
  356. package/dist/workflow/effects.js +214 -115
  357. package/dist/workflow/events.d.ts +149 -5
  358. package/dist/workflow/events.js +27 -5
  359. package/dist/workflow/graph.d.ts +56 -0
  360. package/dist/workflow/graph.js +207 -0
  361. package/dist/workflow/index.d.ts +1 -0
  362. package/dist/workflow/index.js +1 -0
  363. package/dist/workflow/module.d.ts +1047 -266
  364. package/dist/workflow/projections.d.ts +14 -12
  365. package/dist/workflow/projections.js +30 -8
  366. package/dist/workflow/scope.d.ts +146 -4
  367. package/dist/workflow/threads.d.ts +383 -155
  368. package/dist/workflow/threads.js +308 -110
  369. package/package.json +26 -2
  370. package/dist/debugger/events/domain-summarize.d.ts +0 -3
  371. package/dist/debugger/events/domain-summarize.js +0 -107
  372. package/dist/debugger/events/family.d.ts +0 -8
  373. package/dist/debugger/events/family.js +0 -44
  374. package/dist/runtime/events-api.d.ts +0 -4
  375. package/dist/runtime/events-api.js +0 -84
@@ -1,6 +1,54 @@
1
- import { Effect, Predicate } from 'effect';
2
- import { validateInputEffect } from './schema.js';
1
+ import { Data, Effect, Predicate, Schema } from 'effect';
2
+ import { EventInputSchema } from './envelope.js';
3
+ import { DEFAULT_DEFINITION_VERSION } from './module.js';
4
+ import { validateInputEffect, } from './schema.js';
3
5
  import { asJson } from './types.js';
6
+ export const WaitConditionSchema = Schema.Union([
7
+ Schema.Struct({
8
+ type: Schema.Union([Schema.String, Schema.Array(Schema.String)]),
9
+ match: Schema.optional(Schema.Json),
10
+ }),
11
+ Schema.Struct({ timerAt: Schema.Finite }),
12
+ ]);
13
+ export const RuntimeEffectSchema = Schema.Union([
14
+ Schema.Struct({
15
+ type: Schema.Literal('runtime.spawn'),
16
+ childThreadId: Schema.String,
17
+ kind: Schema.String,
18
+ definitionName: Schema.String,
19
+ definitionVersion: Schema.String.pipe(Schema.withDecodingDefaultKey(Effect.succeed(DEFAULT_DEFINITION_VERSION))),
20
+ input: Schema.Json,
21
+ }),
22
+ Schema.Struct({
23
+ type: Schema.Literal('runtime.wait'),
24
+ waitId: Schema.String,
25
+ on: WaitConditionSchema,
26
+ tag: Schema.optional(Schema.Json),
27
+ }),
28
+ Schema.Struct({
29
+ type: Schema.Literal('runtime.emit'),
30
+ event: EventInputSchema,
31
+ tag: Schema.optional(Schema.String),
32
+ }),
33
+ Schema.Struct({
34
+ type: Schema.Literal('runtime.complete'),
35
+ output: Schema.Json,
36
+ }),
37
+ Schema.Struct({
38
+ type: Schema.Literal('runtime.fail'),
39
+ error: Schema.String,
40
+ }),
41
+ Schema.Struct({
42
+ type: Schema.Literal('runtime.cancel'),
43
+ threadId: Schema.String,
44
+ tag: Schema.optional(Schema.String),
45
+ }),
46
+ Schema.Struct({
47
+ type: Schema.String,
48
+ input: Schema.Json,
49
+ tag: Schema.optional(Schema.String),
50
+ }),
51
+ ]);
4
52
  export function isPrimitiveEffect(effect) {
5
53
  switch (effect.type) {
6
54
  case 'runtime.spawn':
@@ -27,35 +75,40 @@ export function withdrawnError(siblingEffectId) {
27
75
  export function isWithdrawnError(error) {
28
76
  return error.startsWith(WITHDRAWN_ERROR_PREFIX);
29
77
  }
78
+ export class EffectHandlerError extends Data.TaggedError('EffectHandlerError') {
79
+ constructor(cause) {
80
+ super({
81
+ cause,
82
+ message: cause instanceof Error ? cause.message : String(cause),
83
+ });
84
+ this.name = 'EffectHandlerError';
85
+ }
86
+ }
30
87
  function liftHandlerResult(result) {
31
88
  if (Effect.isEffect(result)) {
32
- return result;
89
+ return result.pipe(Effect.mapError((cause) => new EffectHandlerError(cause)));
33
90
  }
34
91
  if (result instanceof Promise) {
35
92
  return Effect.tryPromise({
36
93
  try: () => result,
37
- catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
94
+ catch: (cause) => new EffectHandlerError(cause),
38
95
  });
39
96
  }
40
97
  return Effect.succeed(result);
41
98
  }
42
- /**
43
- * Define an effect handler. A run's actor cell executes each effect at most
44
- * once while it is alive. If the cell dies mid-execution, the replacement
45
- * actor may re-run the handler (at-least-once across failover). Keep handlers
46
- * idempotent when they have external side effects.
47
- */
48
99
  export function defineEffect(def) {
49
100
  const schema = def.input;
50
- // SAFETY: execute validates raw JsonValue against the optional schema before the typed handler.
101
+ const handler = def.execute;
102
+ const noOutput = [];
51
103
  return {
52
104
  type: def.type,
53
105
  input: schema,
54
106
  retry: def.retry,
107
+ execution: def.execution,
108
+ hasHandler: handler !== undefined,
55
109
  execute: (raw, ctx) => Effect.gen(function* () {
56
110
  const input = schema ? yield* validateInputEffect(schema, raw) : raw;
57
- // SAFETY: input has been validated against schema or is unconstrained raw input
58
- return yield* liftHandlerResult(def.execute(input, ctx));
111
+ return handler ? yield* liftHandlerResult(handler(input, ctx)) : noOutput;
59
112
  }),
60
113
  };
61
114
  }
@@ -66,6 +119,7 @@ export function spawn(args) {
66
119
  childThreadId,
67
120
  kind: args.kind,
68
121
  definitionName: args.definitionName,
122
+ definitionVersion: args.definitionVersion ?? DEFAULT_DEFINITION_VERSION,
69
123
  input: args.input,
70
124
  };
71
125
  }
@@ -80,8 +134,24 @@ export function wait(args) {
80
134
  }
81
135
  return effect;
82
136
  }
83
- export function emit(event) {
84
- return { type: 'runtime.emit', event };
137
+ /**
138
+ * Reads the outcome of a satisfied wait. The wait fails when the satisfying event's payload has an
139
+ * `error`, or when the waiter put an `error` on the wait's tag (such as a timeout that loses a
140
+ * race). Otherwise the result is the payload's `output`, else the whole payload.
141
+ */
142
+ export function waitOutcome(satisfied, tag, fallback = null) {
143
+ const payload = Predicate.isObject(satisfied) && Predicate.isObject(satisfied.payload) ? satisfied.payload : {};
144
+ const tagError = Predicate.isObject(tag) && Predicate.isString(tag.error) ? tag.error : null;
145
+ const error = Predicate.isString(payload.error) ? payload.error : tagError;
146
+ if (error !== null) {
147
+ return { result: null, error };
148
+ }
149
+ const result = payload.output ?? (Predicate.isObject(satisfied) ? satisfied.payload : fallback);
150
+ // SAFETY: wait results are persisted JSON event payloads.
151
+ return { result: asJson(result) ?? null, error: null };
152
+ }
153
+ export function emit(event, tag) {
154
+ return tag === undefined ? { type: 'runtime.emit', event } : { type: 'runtime.emit', event, tag };
85
155
  }
86
156
  export function complete(output) {
87
157
  return { type: 'runtime.complete', output };
@@ -89,8 +159,10 @@ export function complete(output) {
89
159
  export function fail(error) {
90
160
  return { type: 'runtime.fail', error };
91
161
  }
92
- export function cancel(threadId) {
93
- return { type: 'runtime.cancel', threadId };
162
+ export function cancel(threadId, tag) {
163
+ return tag === undefined
164
+ ? { type: 'runtime.cancel', threadId }
165
+ : { type: 'runtime.cancel', threadId, tag };
94
166
  }
95
167
  export function invoke(typeOrDef, input, tag) {
96
168
  const type = Predicate.isString(typeOrDef) ? typeOrDef : typeOrDef.type;
@@ -1,5 +1,5 @@
1
1
  import { Schema } from 'effect';
2
- import type { JsonValue } from './types';
2
+ import { type JsonValue } from './types';
3
3
  export declare const JsonValueSchema: Schema.Codec<Schema.Json, Schema.Json, never, never>;
4
4
  export declare const EventOriginSchema: Schema.Union<readonly [Schema.Struct<{
5
5
  readonly type: Schema.Literal<"thread">;
@@ -14,8 +14,8 @@ export type EventOrigin = Schema.Schema.Type<typeof EventOriginSchema>;
14
14
  export declare const EventEnvelopeSchema: Schema.Struct<{
15
15
  readonly id: Schema.String;
16
16
  readonly runId: Schema.String;
17
- readonly seq: Schema.Number;
18
- readonly ts: Schema.Number;
17
+ readonly seq: Schema.Finite;
18
+ readonly ts: Schema.Finite;
19
19
  readonly type: Schema.String;
20
20
  readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
21
21
  readonly threadId: Schema.NullOr<Schema.String>;
@@ -35,6 +35,28 @@ export declare const EventEnvelopeSchema: Schema.Struct<{
35
35
  readonly ephemeral: Schema.optional<Schema.Boolean>;
36
36
  readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
37
37
  }>;
38
+ export declare const EventInputSchema: Schema.Struct<{
39
+ readonly type: Schema.String;
40
+ readonly payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
41
+ readonly threadId: Schema.optional<Schema.NullOr<Schema.String>>;
42
+ readonly parentThreadId: Schema.optional<Schema.NullOr<Schema.String>>;
43
+ readonly causationId: Schema.optional<Schema.NullOr<Schema.String>>;
44
+ readonly correlationId: Schema.optional<Schema.NullOr<Schema.String>>;
45
+ readonly effectId: Schema.optional<Schema.NullOr<Schema.String>>;
46
+ readonly origin: Schema.optional<Schema.Union<readonly [Schema.Struct<{
47
+ readonly type: Schema.Literal<"thread">;
48
+ readonly threadId: Schema.String;
49
+ }>, Schema.Struct<{
50
+ readonly type: Schema.Literal<"external">;
51
+ readonly actorId: Schema.optional<Schema.String>;
52
+ }>, Schema.Struct<{
53
+ readonly type: Schema.Literal<"system">;
54
+ }>]>>;
55
+ readonly ephemeral: Schema.optional<Schema.Boolean>;
56
+ readonly id: Schema.optional<Schema.String>;
57
+ readonly ts: Schema.optional<Schema.Finite>;
58
+ readonly idempotencyKey: Schema.optional<Schema.NullOr<Schema.String>>;
59
+ }>;
38
60
  export type EventEnvelope<TType extends string = string, TPayload = JsonValue> = {
39
61
  id: string;
40
62
  runId: string;
@@ -71,9 +93,7 @@ export type AppendableEvent = Omit<EventEnvelope, 'seq'> & {
71
93
  };
72
94
  export declare function payloadAsJson(payload: JsonValue): JsonValue;
73
95
  export declare function withAssignedSeq(partial: AppendableEvent, runId: string, seq: number): EventEnvelope;
74
- export declare function createEvent<TType extends string>(runId: string, input: Omit<EventInput, 'type'> & {
75
- type: TType;
76
- }, options?: {
96
+ export declare function createEvent<TType extends string, TPayload = JsonValue>(runId: string, input: EventInput<TType, TPayload>, options?: {
77
97
  seq?: number;
78
- }): EventEnvelope<TType>;
98
+ }): EventEnvelope<TType, TPayload>;
79
99
  export declare function fromWireEvent(wire: Schema.Schema.Type<typeof EventEnvelopeSchema>): EventEnvelope;
@@ -1,5 +1,6 @@
1
- import { Schema } from 'effect';
1
+ import { DateTime, Schema } from 'effect';
2
2
  import { createEventId } from './ids.js';
3
+ import { cleanUndefined } from './types.js';
3
4
  export const JsonValueSchema = Schema.Json;
4
5
  export const EventOriginSchema = Schema.Union([
5
6
  Schema.Struct({
@@ -17,8 +18,8 @@ export const EventOriginSchema = Schema.Union([
17
18
  export const EventEnvelopeSchema = Schema.Struct({
18
19
  id: Schema.String,
19
20
  runId: Schema.String,
20
- seq: Schema.Number,
21
- ts: Schema.Number,
21
+ seq: Schema.Finite,
22
+ ts: Schema.Finite,
22
23
  type: Schema.String,
23
24
  payload: JsonValueSchema,
24
25
  threadId: Schema.NullOr(Schema.String),
@@ -30,9 +31,22 @@ export const EventEnvelopeSchema = Schema.Struct({
30
31
  ephemeral: Schema.optional(Schema.Boolean),
31
32
  idempotencyKey: Schema.optional(Schema.NullOr(Schema.String)),
32
33
  });
34
+ export const EventInputSchema = Schema.Struct({
35
+ type: Schema.String,
36
+ payload: JsonValueSchema,
37
+ threadId: Schema.optional(Schema.NullOr(Schema.String)),
38
+ parentThreadId: Schema.optional(Schema.NullOr(Schema.String)),
39
+ causationId: Schema.optional(Schema.NullOr(Schema.String)),
40
+ correlationId: Schema.optional(Schema.NullOr(Schema.String)),
41
+ effectId: Schema.optional(Schema.NullOr(Schema.String)),
42
+ origin: Schema.optional(EventOriginSchema),
43
+ ephemeral: Schema.optional(Schema.Boolean),
44
+ id: Schema.optional(Schema.String),
45
+ ts: Schema.optional(Schema.Finite),
46
+ idempotencyKey: Schema.optional(Schema.NullOr(Schema.String)),
47
+ });
33
48
  export function payloadAsJson(payload) {
34
- // SAFETY: round-trip through JSON yields JsonValue.
35
- return JSON.parse(JSON.stringify(payload));
49
+ return cleanUndefined(payload);
36
50
  }
37
51
  export function withAssignedSeq(partial, runId, seq) {
38
52
  return { ...partial, runId, seq };
@@ -44,7 +58,7 @@ export function createEvent(runId, input, options) {
44
58
  id: input.id ?? createEventId(),
45
59
  runId,
46
60
  seq: options?.seq ?? 0,
47
- ts: input.ts ?? Date.now(),
61
+ ts: input.ts ?? DateTime.toEpochMillis(DateTime.nowUnsafe()),
48
62
  type: input.type,
49
63
  payload: input.payload,
50
64
  threadId,
@@ -2,7 +2,9 @@ import type { AppendableEvent, EventEnvelope } from './envelope';
2
2
  import type { JsonValue } from './types';
3
3
  /** JSON-safe event envelope for SSE and pull responses. */
4
4
  export type EncodedLoomsEvent = EventEnvelope;
5
+ type EventWireInput = JsonValue | Partial<EncodedLoomsEvent>;
5
6
  export declare function encodeLoomsEvent(event: EventEnvelope): EncodedLoomsEvent;
6
7
  export declare function encodeAppendableEvent(event: AppendableEvent): EncodedLoomsEvent;
7
- export declare function decodeLoomsEvent(raw: JsonValue | EncodedLoomsEvent, runId: string): EventEnvelope;
8
- export declare function decodeAppendableEvent(raw: JsonValue | EncodedLoomsEvent, runId: string): AppendableEvent | undefined;
8
+ export declare function decodeLoomsEvent(raw: EventWireInput, runId: string): EventEnvelope;
9
+ export declare function decodeAppendableEvent(raw: EventWireInput, runId: string): AppendableEvent | undefined;
10
+ export {};
@@ -1,6 +1,7 @@
1
- import { Predicate } from 'effect';
1
+ import { DateTime, Predicate, Schema } from 'effect';
2
2
  import { createEvent, payloadAsJson } from './envelope.js';
3
3
  import { createEventId } from './ids.js';
4
+ const decodeJsonValue = Schema.decodeUnknownSync(Schema.Json);
4
5
  export function encodeLoomsEvent(event) {
5
6
  return {
6
7
  ...event,
@@ -12,7 +13,7 @@ export function encodeAppendableEvent(event) {
12
13
  id: event.id ?? createEventId(),
13
14
  runId: event.runId,
14
15
  seq: event.seq ?? 0,
15
- ts: event.ts ?? Date.now(),
16
+ ts: event.ts ?? DateTime.toEpochMillis(DateTime.nowUnsafe()),
16
17
  type: event.type,
17
18
  payload: payloadAsJson(event.payload),
18
19
  threadId: event.threadId ?? null,
@@ -25,6 +26,9 @@ export function encodeAppendableEvent(event) {
25
26
  };
26
27
  }
27
28
  function readOrigin(raw) {
29
+ if (!Predicate.isReadonlyObject(raw)) {
30
+ return { type: 'system' };
31
+ }
28
32
  if ('type' in raw && raw.type === 'thread') {
29
33
  const threadId = 'threadId' in raw && Predicate.isString(raw.threadId) ? raw.threadId : '';
30
34
  return { type: 'thread', threadId };
@@ -41,10 +45,11 @@ export function decodeLoomsEvent(raw, runId) {
41
45
  }
42
46
  if ('type' in raw && Predicate.isString(raw.type)) {
43
47
  const rawPayload = 'payload' in raw ? raw.payload : {};
44
- // SAFETY: payload is JSON-compatible.
45
- const payload = rawPayload;
48
+ const payload = decodeJsonValue(rawPayload);
46
49
  const id = 'id' in raw && Predicate.isString(raw.id) ? raw.id : createEventId();
47
- const ts = 'ts' in raw && Predicate.isNumber(raw.ts) ? raw.ts : Date.now();
50
+ const ts = 'ts' in raw && Predicate.isNumber(raw.ts)
51
+ ? raw.ts
52
+ : DateTime.toEpochMillis(DateTime.nowUnsafe());
48
53
  const seq = 'seq' in raw && Predicate.isNumber(raw.seq) ? raw.seq : 0;
49
54
  const ephemeral = 'ephemeral' in raw && Predicate.isBoolean(raw.ephemeral) ? raw.ephemeral : false;
50
55
  const itemRunId = 'runId' in raw && Predicate.isString(raw.runId) ? raw.runId : runId;
@@ -1,4 +1,3 @@
1
- const EMPTY_EVENTS = Object.freeze([]);
2
1
  /** Thread bucket key used by run-tree counts (`threadId` or `'run'` for run-scoped). */
3
2
  export function eventThreadKey(event) {
4
3
  return event.threadId ?? 'run';
@@ -60,8 +59,7 @@ export class EventIndex {
60
59
  }
61
60
  getByThread(threadId) {
62
61
  this.publish();
63
- // SAFETY: EMPTY_EVENTS is an empty frozen array compatible with any TEvent[].
64
- return this.byThreadSnapshots.get(threadId) ?? EMPTY_EVENTS;
62
+ return this.byThreadSnapshots.get(threadId) ?? [];
65
63
  }
66
64
  getBySeq(seq) {
67
65
  return this.bySeqMap.get(seq);
@@ -1,10 +1,13 @@
1
1
  import { Stream, type Effect } from 'effect';
2
+ import { type RuntimeEffect } from './effects';
2
3
  import type { EventEnvelope } from './envelope';
3
4
  import type { RunState } from './state';
4
5
  import { type ThreadDefinition } from './thread';
5
6
  export interface FoldRegistry {
6
7
  readonly threads: ReadonlyMap<string, ThreadDefinition>;
7
8
  }
9
+ /** The id an effect keeps across folds when it names itself by tag, wait id, or child thread. */
10
+ export declare function stableEffectId(threadId: string, effect: RuntimeEffect): string | undefined;
8
11
  export declare function foldEvent(state: RunState, event: EventEnvelope, registry: FoldRegistry): RunState;
9
12
  export declare function foldRun(events: readonly EventEnvelope[], registry: FoldRegistry, options?: {
10
13
  runId?: string;
package/dist/core/fold.js CHANGED
@@ -1,8 +1,10 @@
1
- import { Predicate, Stream } from 'effect';
2
- import { isWithdrawnError } from './effects.js';
1
+ import { Option, Predicate, Schema, Stream } from 'effect';
2
+ import { isWithdrawnError, WaitConditionSchema } from './effects.js';
3
3
  import { createEffectId } from './ids.js';
4
+ import { DEFAULT_DEFINITION_VERSION } from './module.js';
4
5
  import { emptyRunState } from './state.js';
5
6
  import { isTerminalStatus } from './thread.js';
7
+ const decodeWaitCondition = Schema.decodeUnknownOption(WaitConditionSchema);
6
8
  function payloadObject(event) {
7
9
  const payload = event.payload;
8
10
  if (payload === null || Array.isArray(payload)) {
@@ -22,9 +24,11 @@ function cloneState(state) {
22
24
  runId: state.runId,
23
25
  status: state.status,
24
26
  rootThreadId: state.rootThreadId,
27
+ startIdentity: state.startIdentity,
25
28
  threads: { ...state.threads },
26
29
  waits: { ...state.waits },
27
30
  outstandingEffects: [...state.outstandingEffects],
31
+ effectExecutions: { ...state.effectExecutions },
28
32
  completedEffectIds: state.completedEffectIds ? [...state.completedEffectIds] : [],
29
33
  processedIdempotencyKeys: state.processedIdempotencyKeys
30
34
  ? [...state.processedIdempotencyKeys]
@@ -33,9 +37,11 @@ function cloneState(state) {
33
37
  }
34
38
  function completeEffect(state, effectId) {
35
39
  const completed = state.completedEffectIds ?? [];
40
+ const { [effectId]: _completedExecution, ...effectExecutions } = state.effectExecutions ?? {};
36
41
  return {
37
42
  ...state,
38
43
  outstandingEffects: state.outstandingEffects.filter((item) => item.effectId !== effectId),
44
+ effectExecutions,
39
45
  completedEffectIds: completed.includes(effectId) ? completed : [...completed, effectId],
40
46
  };
41
47
  }
@@ -75,18 +81,41 @@ function applyProtocol(state, event, registry) {
75
81
  const payload = payloadObject(event);
76
82
  switch (event.type) {
77
83
  case 'runtime.run.started': {
78
- const rootThreadId = readString(payload, 'rootThreadId') ?? null;
79
- return { ...state, runId: event.runId, rootThreadId, status: 'running' };
84
+ const rootThreadId = readString(payload, 'rootThreadId');
85
+ const kind = readString(payload, 'kind');
86
+ const definitionName = readString(payload, 'definitionName');
87
+ const definitionVersion = readString(payload, 'definitionVersion') ?? DEFAULT_DEFINITION_VERSION;
88
+ const startIdentity = rootThreadId && kind && definitionName && definitionVersion
89
+ ? {
90
+ kind,
91
+ definitionName,
92
+ definitionVersion,
93
+ input: payload.input ?? null,
94
+ requestedThreadId: readString(payload, 'requestedThreadId') ?? null,
95
+ idempotencyKey: event.idempotencyKey ?? null,
96
+ rootThreadId,
97
+ }
98
+ : null;
99
+ return {
100
+ ...state,
101
+ runId: event.runId,
102
+ rootThreadId: rootThreadId ?? null,
103
+ startIdentity,
104
+ status: 'running',
105
+ };
80
106
  }
81
107
  case 'runtime.run.completed': {
82
108
  const error = payload.error;
83
109
  const status = Predicate.isString(error) && error.length > 0 ? 'failed' : 'completed';
84
110
  return { ...state, status };
85
111
  }
112
+ case 'runtime.run.cancelled':
113
+ return { ...state, status: 'cancelled' };
86
114
  case 'runtime.thread.started': {
87
115
  const threadId = readString(payload, 'threadId') ?? event.threadId;
88
116
  const kind = readString(payload, 'kind');
89
117
  const definitionName = readString(payload, 'definitionName');
118
+ const definitionVersion = readString(payload, 'definitionVersion') ?? DEFAULT_DEFINITION_VERSION;
90
119
  if (!threadId || !kind || !definitionName) {
91
120
  return state;
92
121
  }
@@ -98,6 +127,7 @@ function applyProtocol(state, event, registry) {
98
127
  threadId,
99
128
  kind,
100
129
  definitionName,
130
+ definitionVersion,
101
131
  parentThreadId,
102
132
  status: definition ? 'running' : 'failed',
103
133
  input,
@@ -109,6 +139,7 @@ function applyProtocol(state, event, registry) {
109
139
  threadId,
110
140
  parentThreadId,
111
141
  definitionName,
142
+ definitionVersion,
112
143
  input,
113
144
  })
114
145
  : {},
@@ -156,7 +187,7 @@ function applyProtocol(state, event, registry) {
156
187
  return state;
157
188
  }
158
189
  const existing = state.threads[threadId];
159
- if (!existing) {
190
+ if (!existing || isTerminalStatus(existing.status)) {
160
191
  return state;
161
192
  }
162
193
  return dropThreadWork(putThread(state, {
@@ -165,6 +196,111 @@ function applyProtocol(state, event, registry) {
165
196
  error: readString(payload, 'reason') ?? existing.error,
166
197
  }), threadId);
167
198
  }
199
+ case 'runtime.effect.attempt.started': {
200
+ const effectId = readString(payload, 'effectId');
201
+ const attempt = payload.attempt;
202
+ if (!effectId || !Predicate.isNumber(attempt)) {
203
+ return state;
204
+ }
205
+ return {
206
+ ...state,
207
+ effectExecutions: {
208
+ ...state.effectExecutions,
209
+ [effectId]: {
210
+ effectId,
211
+ attempt,
212
+ status: 'running',
213
+ nextAttemptAt: null,
214
+ deadlineAt: null,
215
+ startedAt: event.ts,
216
+ lastHeartbeatAt: null,
217
+ lastError: null,
218
+ },
219
+ },
220
+ };
221
+ }
222
+ case 'runtime.effect.queued':
223
+ case 'runtime.effect.dispatched':
224
+ case 'runtime.effect.worker.started':
225
+ case 'runtime.effect.heartbeat':
226
+ case 'runtime.effect.cancel.requested':
227
+ case 'runtime.effect.timed_out':
228
+ case 'runtime.effect.ambiguous': {
229
+ const effectId = readString(payload, 'effectId');
230
+ const attempt = payload.attempt;
231
+ if (!effectId || !Predicate.isNumber(attempt)) {
232
+ return state;
233
+ }
234
+ const status = event.type === 'runtime.effect.queued'
235
+ ? 'queued'
236
+ : event.type === 'runtime.effect.dispatched'
237
+ ? 'dispatched'
238
+ : event.type === 'runtime.effect.worker.started'
239
+ ? 'started'
240
+ : event.type === 'runtime.effect.heartbeat'
241
+ ? 'heartbeat'
242
+ : event.type === 'runtime.effect.cancel.requested'
243
+ ? 'cancel_requested'
244
+ : event.type === 'runtime.effect.timed_out'
245
+ ? 'timed_out'
246
+ : 'ambiguous';
247
+ const deadlineAt = payload.deadlineAt;
248
+ const heartbeatAt = payload.heartbeatAt;
249
+ const previous = state.effectExecutions[effectId];
250
+ const sameAttempt = previous?.attempt === attempt;
251
+ return {
252
+ ...state,
253
+ effectExecutions: {
254
+ ...state.effectExecutions,
255
+ [effectId]: {
256
+ effectId,
257
+ attempt,
258
+ status,
259
+ nextAttemptAt: null,
260
+ deadlineAt: Predicate.isNumber(deadlineAt) ? deadlineAt : null,
261
+ startedAt: event.type === 'runtime.effect.worker.started'
262
+ ? event.ts
263
+ : sameAttempt
264
+ ? previous.startedAt
265
+ : null,
266
+ lastHeartbeatAt: Predicate.isNumber(heartbeatAt)
267
+ ? heartbeatAt
268
+ : sameAttempt
269
+ ? previous.lastHeartbeatAt
270
+ : null,
271
+ lastError: readString(payload, 'error') ?? null,
272
+ },
273
+ },
274
+ };
275
+ }
276
+ case 'runtime.effect.cancelled': {
277
+ const effectId = readString(payload, 'effectId');
278
+ return effectId ? completeEffect(state, effectId) : state;
279
+ }
280
+ case 'runtime.effect.retry.scheduled': {
281
+ const effectId = readString(payload, 'effectId');
282
+ const attempt = payload.attempt;
283
+ const nextAttemptAt = payload.nextAttemptAt;
284
+ if (!effectId || !Predicate.isNumber(attempt) || !Predicate.isNumber(nextAttemptAt)) {
285
+ return state;
286
+ }
287
+ return {
288
+ ...state,
289
+ effectExecutions: {
290
+ ...state.effectExecutions,
291
+ [effectId]: {
292
+ effectId,
293
+ attempt,
294
+ status: 'retry_wait',
295
+ nextAttemptAt,
296
+ deadlineAt: nextAttemptAt,
297
+ startedAt: null,
298
+ lastHeartbeatAt: null,
299
+ lastError: readString(payload, 'error') ?? 'effect failed',
300
+ },
301
+ },
302
+ };
303
+ }
168
304
  case 'runtime.wait.registered': {
169
305
  const waitId = readString(payload, 'waitId');
170
306
  const threadId = readString(payload, 'threadId') ?? event.threadId;
@@ -172,11 +308,14 @@ function applyProtocol(state, event, registry) {
172
308
  if (!waitId || !threadId || !on || !Predicate.isObject(on)) {
173
309
  return state;
174
310
  }
175
- // SAFETY: wait.registered payload.on is a WaitCondition written by the runtime.
311
+ const waitCondition = decodeWaitCondition(on);
312
+ if (Option.isNone(waitCondition)) {
313
+ return state;
314
+ }
176
315
  const record = {
177
316
  waitId,
178
317
  threadId,
179
- on: on,
318
+ on: waitCondition.value,
180
319
  };
181
320
  if (payload.tag !== undefined) {
182
321
  record.tag = payload.tag;
@@ -195,6 +334,16 @@ function applyProtocol(state, event, registry) {
195
334
  }
196
335
  const existingWait = state.waits[waitId];
197
336
  let next = removeWait(state, waitId);
337
+ const existingTag = Predicate.isObject(existingWait?.tag) ? existingWait.tag : undefined;
338
+ const raceId = existingTag && Predicate.isString(existingTag.raceId) ? existingTag.raceId : undefined;
339
+ if (raceId) {
340
+ for (const sibling of Object.values(next.waits)) {
341
+ const siblingTag = Predicate.isObject(sibling.tag) ? sibling.tag : undefined;
342
+ if (siblingTag?.raceId === raceId) {
343
+ next = removeWait(next, sibling.waitId);
344
+ }
345
+ }
346
+ }
198
347
  const threadId = existingWait?.threadId ?? event.threadId;
199
348
  if (!threadId) {
200
349
  return next;
@@ -206,16 +355,19 @@ function applyProtocol(state, event, registry) {
206
355
  return next;
207
356
  }
208
357
  case 'runtime.snapshot.taken':
358
+ case 'runtime.thread.cancel.requested':
209
359
  case 'runtime.timer.set':
210
360
  case 'runtime.timer.fired':
211
361
  case 'runtime.effect.failed':
362
+ case 'runtime.effect.completed':
212
363
  case 'runtime.signal.received':
213
364
  return state;
214
365
  default:
215
366
  return state;
216
367
  }
217
368
  }
218
- function deriveEffectId(threadId, causingSeq, index, effect, existingId) {
369
+ /** The id an effect keeps across folds when it names itself by tag, wait id, or child thread. */
370
+ export function stableEffectId(threadId, effect) {
219
371
  if ('tag' in effect && Predicate.isString(effect.tag)) {
220
372
  return createEffectId(threadId, effect.tag);
221
373
  }
@@ -225,29 +377,21 @@ function deriveEffectId(threadId, causingSeq, index, effect, existingId) {
225
377
  if ('childThreadId' in effect && Predicate.isString(effect.childThreadId)) {
226
378
  return createEffectId(threadId, `spawn_${effect.childThreadId}`);
227
379
  }
228
- if (existingId) {
229
- return existingId;
380
+ if (effect.type === 'runtime.cancel' &&
381
+ 'threadId' in effect &&
382
+ Predicate.isString(effect.threadId)) {
383
+ return createEffectId(threadId, `cancel_${effect.threadId}`);
230
384
  }
231
- return createEffectId(threadId, causingSeq, index);
385
+ return undefined;
232
386
  }
233
387
  function mapEffects(threadId, event, effects, completed, existingForThread) {
234
388
  const remainingExisting = [...existingForThread];
235
389
  return effects
236
390
  .map((effect, index) => {
237
- const matchIndex = remainingExisting.findIndex((item) => {
238
- if ('tag' in effect && Predicate.isString(effect.tag)) {
239
- return item.effectId === createEffectId(threadId, effect.tag);
240
- }
241
- if ('waitId' in effect && Predicate.isString(effect.waitId)) {
242
- return item.effectId === createEffectId(threadId, effect.waitId);
243
- }
244
- if ('childThreadId' in effect && Predicate.isString(effect.childThreadId)) {
245
- return item.effectId === createEffectId(threadId, `spawn_${effect.childThreadId}`);
246
- }
247
- return item.effect.type === effect.type;
248
- });
391
+ const stableId = stableEffectId(threadId, effect);
392
+ const matchIndex = remainingExisting.findIndex((item) => stableId === undefined ? item.effect.type === effect.type : item.effectId === stableId);
249
393
  const existing = matchIndex >= 0 ? remainingExisting.splice(matchIndex, 1)[0] : undefined;
250
- const effectId = deriveEffectId(threadId, event.seq, index, effect, existing?.effectId);
394
+ const effectId = stableId ?? existing?.effectId ?? createEffectId(threadId, event.seq, index);
251
395
  return (existing ?? {
252
396
  effectId,
253
397
  threadId,
@@ -285,6 +429,9 @@ function deliver(state, event, registry) {
285
429
  if (!record) {
286
430
  return state;
287
431
  }
432
+ if (isTerminalStatus(record.status)) {
433
+ return state;
434
+ }
288
435
  const definition = registry.threads.get(record.kind);
289
436
  if (!definition) {
290
437
  return state;