experimental-a2 0.16.1 → 0.18.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 (406) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +1 -1
  3. package/dist/{actor-ohPC-81x.d.ts → actor-Bjce9_5U.d.ts} +21 -11
  4. package/dist/actor-Bjce9_5U.d.ts.map +1 -0
  5. package/dist/actor-client.d.ts +2 -5
  6. package/dist/actor-client.d.ts.map +1 -1
  7. package/dist/actor-client.js +13 -4
  8. package/dist/actor-client.js.map +1 -1
  9. package/dist/actor-react.d.ts +5 -3
  10. package/dist/actor-react.d.ts.map +1 -1
  11. package/dist/actor-react.js +20 -10
  12. package/dist/actor-react.js.map +1 -1
  13. package/dist/{actor-shared-B5tJfzt-.js → actor-shared-t7w8xIKP.js} +12 -7
  14. package/dist/actor-shared-t7w8xIKP.js.map +1 -0
  15. package/dist/actor.d.ts +1 -1
  16. package/dist/actor.js +42 -23
  17. package/dist/actor.js.map +1 -1
  18. package/dist/{ai-DgOBltJ_.js → ai-BpOGm-zB.js} +621 -216
  19. package/dist/ai-BpOGm-zB.js.map +1 -0
  20. package/dist/{ai-CrEf6p_W.d.ts → ai-DrUaMyRt.d.ts} +114 -100
  21. package/dist/ai-DrUaMyRt.d.ts.map +1 -0
  22. package/dist/ai-client.d.ts +27 -0
  23. package/dist/ai-client.d.ts.map +1 -0
  24. package/dist/ai-client.js +29 -0
  25. package/dist/ai-client.js.map +1 -0
  26. package/dist/ai-fx.browser.d.ts +1 -0
  27. package/dist/ai-fx.browser.js +6 -0
  28. package/dist/ai-fx.browser.js.map +1 -0
  29. package/dist/ai-fx.d.ts +10 -0
  30. package/dist/ai-fx.d.ts.map +1 -0
  31. package/dist/ai-fx.js +634 -0
  32. package/dist/ai-fx.js.map +1 -0
  33. package/dist/ai-harness-BuLh_EYi.js +273 -0
  34. package/dist/ai-harness-BuLh_EYi.js.map +1 -0
  35. package/dist/ai-harness-DvsPeJeB.d.ts +26 -0
  36. package/dist/ai-harness-DvsPeJeB.d.ts.map +1 -0
  37. package/dist/ai-pi.browser.d.ts +1 -0
  38. package/dist/ai-pi.browser.js +6 -0
  39. package/dist/ai-pi.browser.js.map +1 -0
  40. package/dist/ai-pi.d.ts +17 -0
  41. package/dist/ai-pi.d.ts.map +1 -0
  42. package/dist/ai-pi.js +904 -0
  43. package/dist/ai-pi.js.map +1 -0
  44. package/dist/ai-react.d.ts +96 -0
  45. package/dist/ai-react.d.ts.map +1 -0
  46. package/dist/ai-react.js +142 -0
  47. package/dist/ai-react.js.map +1 -0
  48. package/dist/ai-server.browser.js +2 -2
  49. package/dist/ai-server.browser.js.map +1 -1
  50. package/dist/ai-server.d.ts +109 -43
  51. package/dist/ai-server.d.ts.map +1 -1
  52. package/dist/ai-server.js +526 -283
  53. package/dist/ai-server.js.map +1 -1
  54. package/dist/ai.d.ts +2 -2
  55. package/dist/ai.js +2 -2
  56. package/dist/bounded-cache-CPHWF-8h.js +41 -0
  57. package/dist/bounded-cache-CPHWF-8h.js.map +1 -0
  58. package/dist/{client-BYzHjkwU.js → client-BReKqwZC.js} +447 -80
  59. package/dist/client-BReKqwZC.js.map +1 -0
  60. package/dist/{client-CzyacQpJ.d.ts → client-h_DKFAjj.d.ts} +14 -13
  61. package/dist/client-h_DKFAjj.d.ts.map +1 -0
  62. package/dist/client.d.ts +4 -2
  63. package/dist/client.js +1 -1
  64. package/dist/{contract-CKRg_E4q.js → contract-BYKASlCV.js} +12 -21
  65. package/dist/contract-BYKASlCV.js.map +1 -0
  66. package/dist/{idempotent-replay-DVOlyYbx.js → idempotent-replay-JdgMQRRm.js} +2 -2
  67. package/dist/{idempotent-replay-DVOlyYbx.js.map → idempotent-replay-JdgMQRRm.js.map} +1 -1
  68. package/dist/index.d.ts +6 -5
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/{errors-DvhSXnxk.d.ts → initial-snapshot-Bx-c0pDP.d.ts} +7 -3
  72. package/dist/initial-snapshot-Bx-c0pDP.d.ts.map +1 -0
  73. package/dist/{internal-Dq2qYxou.js → internal-B2T3U4Rq.js} +29 -2
  74. package/dist/internal-B2T3U4Rq.js.map +1 -0
  75. package/dist/otel.d.ts +1 -1
  76. package/dist/react-BBXgHWFc.d.ts +86 -0
  77. package/dist/react-BBXgHWFc.d.ts.map +1 -0
  78. package/dist/react.d.ts +2 -122
  79. package/dist/react.js +107 -100
  80. package/dist/react.js.map +1 -1
  81. package/dist/reducer-BAXc8JyF.js +87 -0
  82. package/dist/reducer-BAXc8JyF.js.map +1 -0
  83. package/dist/{reducer-otzHjuJj.d.ts → reducer-CqA59Q6U.d.ts} +46 -30
  84. package/dist/reducer-CqA59Q6U.d.ts.map +1 -0
  85. package/dist/reducer-runtime-DQwz_J59.js +28 -0
  86. package/dist/reducer-runtime-DQwz_J59.js.map +1 -0
  87. package/dist/scheduler-qstash.d.ts +3 -3
  88. package/dist/scheduler-qstash.d.ts.map +1 -1
  89. package/dist/scheduler-qstash.js +33 -10
  90. package/dist/scheduler-qstash.js.map +1 -1
  91. package/dist/{scheduler-task-BpzhPnRS.js → scheduler-task-X1SToDjP.js} +18 -3
  92. package/dist/scheduler-task-X1SToDjP.js.map +1 -0
  93. package/dist/scheduler-vercel.d.ts +4 -9
  94. package/dist/scheduler-vercel.d.ts.map +1 -1
  95. package/dist/scheduler-vercel.js +25 -44
  96. package/dist/scheduler-vercel.js.map +1 -1
  97. package/dist/{server-BD5ckxZb.js → server-Dw_k_7kO.js} +904 -219
  98. package/dist/server-Dw_k_7kO.js.map +1 -0
  99. package/dist/{server-DbD7IJVK.d.ts → server-En7cbi3v.d.ts} +116 -26
  100. package/dist/server-En7cbi3v.d.ts.map +1 -0
  101. package/dist/server.d.ts +4 -4
  102. package/dist/server.js +2 -2
  103. package/dist/session-clear-Bo-XTFEy.d.ts +12 -0
  104. package/dist/session-clear-Bo-XTFEy.d.ts.map +1 -0
  105. package/dist/{errors-DCk6ch5n.js → session-clear-DGI4jTQ5.js} +17 -2
  106. package/dist/session-clear-DGI4jTQ5.js.map +1 -0
  107. package/dist/{store-DyZM6fS5.d.ts → store-Djdv0bnZ.d.ts} +73 -3
  108. package/dist/store-Djdv0bnZ.d.ts.map +1 -0
  109. package/dist/store-N8PXxDAS.js.map +1 -1
  110. package/dist/store-memory.d.ts +1 -1
  111. package/dist/store-memory.d.ts.map +1 -1
  112. package/dist/store-memory.js +205 -66
  113. package/dist/store-memory.js.map +1 -1
  114. package/dist/{store-polling-CmxUbV93.js → store-polling-B5FcwJfo.js} +21 -4
  115. package/dist/store-polling-B5FcwJfo.js.map +1 -0
  116. package/dist/store-postgres.d.ts +1 -1
  117. package/dist/store-postgres.d.ts.map +1 -1
  118. package/dist/store-postgres.js +450 -108
  119. package/dist/store-postgres.js.map +1 -1
  120. package/dist/{store-presence-polling-C7-XZyW9.js → store-presence-polling-BAwS8RWn.js} +3 -3
  121. package/dist/{store-presence-polling-C7-XZyW9.js.map → store-presence-polling-BAwS8RWn.js.map} +1 -1
  122. package/dist/store-redis-http.d.ts +1 -1
  123. package/dist/store-redis-http.d.ts.map +1 -1
  124. package/dist/store-redis-http.js +293 -11
  125. package/dist/store-redis-http.js.map +1 -1
  126. package/dist/store-redis-notify-BRITti_o.js +2493 -0
  127. package/dist/store-redis-notify-BRITti_o.js.map +1 -0
  128. package/dist/store-redis.d.ts +1 -1
  129. package/dist/store-redis.d.ts.map +1 -1
  130. package/dist/store-redis.js +11 -11
  131. package/dist/store-redis.js.map +1 -1
  132. package/dist/store-sqlite.d.ts +1 -1
  133. package/dist/store-sqlite.d.ts.map +1 -1
  134. package/dist/store-sqlite.js +197 -33
  135. package/dist/store-sqlite.js.map +1 -1
  136. package/dist/stream-buffer-Cx7AAPGq.js +23 -0
  137. package/dist/stream-buffer-Cx7AAPGq.js.map +1 -0
  138. package/dist/{telemetry-B5jzpy6y.d.ts → telemetry-Cyfqo6Qt.d.ts} +2 -2
  139. package/dist/telemetry-Cyfqo6Qt.d.ts.map +1 -0
  140. package/dist/{wire-BO5wWCb1.js → wire-rSUglYo8.js} +132 -10
  141. package/dist/wire-rSUglYo8.js.map +1 -0
  142. package/docs/01-quickstart.mdx +50 -84
  143. package/docs/{guides/07-examples.mdx → 02-examples.mdx} +10 -20
  144. package/docs/actors/01-introduction.mdx +48 -82
  145. package/docs/actors/02-concurrency.mdx +43 -70
  146. package/docs/actors/03-timers.mdx +28 -42
  147. package/docs/actors/04-routes.mdx +110 -137
  148. package/docs/actors/meta.ts +1 -1
  149. package/docs/agents/01-introduction.mdx +257 -0
  150. package/docs/agents/02-tools.mdx +247 -0
  151. package/docs/agents/03-conversations.mdx +538 -0
  152. package/docs/agents/04-harnesses.mdx +136 -0
  153. package/docs/agents/meta.ts +1 -0
  154. package/docs/client/01-react.mdx +294 -0
  155. package/docs/client/02-transports.mdx +86 -0
  156. package/docs/client/03-presence.mdx +224 -0
  157. package/docs/client/meta.ts +1 -0
  158. package/docs/contract/01-events.mdx +57 -0
  159. package/docs/contract/02-state.mdx +142 -0
  160. package/docs/contract/03-sessions.mdx +175 -0
  161. package/docs/index.mdx +112 -156
  162. package/docs/reference/01-core.mdx +139 -0
  163. package/docs/reference/02-server.mdx +855 -0
  164. package/docs/reference/03-client.mdx +301 -0
  165. package/docs/reference/04-ai.mdx +722 -0
  166. package/docs/reference/05-adapters.mdx +351 -0
  167. package/docs/reference/06-errors.mdx +138 -0
  168. package/docs/reference/meta.ts +1 -1
  169. package/docs/server/01-handlers.mdx +200 -0
  170. package/docs/server/02-timers.mdx +130 -0
  171. package/docs/server/03-cancellation.mdx +141 -0
  172. package/docs/server/04-routes.mdx +243 -0
  173. package/docs/server/05-production.mdx +322 -0
  174. package/docs/server/06-guarantees.mdx +123 -0
  175. package/examples/playground/DEPLOY.md +53 -7
  176. package/examples/playground/app/a0/[appId]/loading.tsx +16 -0
  177. package/examples/playground/app/a0/[appId]/page.tsx +53 -0
  178. package/examples/playground/app/a0/a0.css +818 -0
  179. package/examples/playground/app/a0/app-list.tsx +218 -0
  180. package/examples/playground/app/a0/app-view.tsx +63 -0
  181. package/examples/playground/app/a0/apps/[appId]/preview/route.ts +27 -0
  182. package/examples/playground/app/a0/apps/[appId]/route.ts +28 -0
  183. package/examples/playground/app/a0/apps.test.ts +128 -0
  184. package/examples/playground/app/a0/apps.ts +102 -0
  185. package/examples/playground/app/a0/chat.tsx +410 -0
  186. package/examples/playground/app/a0/columns.tsx +109 -0
  187. package/examples/playground/app/a0/context-menu.tsx +116 -0
  188. package/examples/playground/app/a0/cookies.ts +46 -0
  189. package/examples/playground/app/a0/crash-on-request.ts +72 -0
  190. package/examples/playground/app/a0/crashes/route.ts +3 -0
  191. package/examples/playground/app/a0/crashes.test.ts +33 -0
  192. package/examples/playground/app/a0/crashes.ts +28 -0
  193. package/examples/playground/app/a0/devtools.tsx +164 -0
  194. package/examples/playground/app/a0/events/route.ts +6 -0
  195. package/examples/playground/app/a0/flags.ts +30 -0
  196. package/examples/playground/app/a0/fx/events/route.ts +9 -0
  197. package/examples/playground/app/a0/fx/scheduler/route.ts +7 -0
  198. package/examples/playground/app/a0/fx/short/events/route.ts +11 -0
  199. package/examples/playground/app/a0/layout.tsx +38 -0
  200. package/examples/playground/app/a0/list/route.ts +10 -0
  201. package/examples/playground/app/a0/model.test.ts +73 -0
  202. package/examples/playground/app/a0/model.ts +95 -0
  203. package/examples/playground/app/a0/no-photon.cjs +4 -0
  204. package/examples/playground/app/a0/no-quickjs.cjs +3 -0
  205. package/examples/playground/app/a0/page.tsx +14 -0
  206. package/examples/playground/app/a0/pi/events/route.ts +9 -0
  207. package/examples/playground/app/a0/pi/scheduler/route.ts +7 -0
  208. package/examples/playground/app/a0/pi/short/events/route.ts +11 -0
  209. package/examples/playground/app/a0/preview-pane.tsx +145 -0
  210. package/examples/playground/app/a0/preview.test.ts +53 -0
  211. package/examples/playground/app/a0/preview.ts +26 -0
  212. package/examples/playground/app/a0/sandbox-log.ts +75 -0
  213. package/examples/playground/app/a0/sandbox-session.ts +152 -0
  214. package/examples/playground/app/a0/sandbox.ts +191 -0
  215. package/examples/playground/app/a0/sandboxes/scheduler/route.ts +6 -0
  216. package/examples/playground/app/a0/scheduler/route.ts +7 -0
  217. package/examples/playground/app/a0/server.ts +213 -0
  218. package/examples/playground/app/a0/session.ts +59 -0
  219. package/examples/playground/app/a0/shell.tsx +158 -0
  220. package/examples/playground/app/a0/short/events/route.ts +9 -0
  221. package/examples/playground/app/a0/splitter.tsx +120 -0
  222. package/examples/playground/app/agent/[agentId]/agent-client.tsx +144 -174
  223. package/examples/playground/app/agent/[agentId]/command-receipt.test.tsx +65 -0
  224. package/examples/playground/app/agent/[agentId]/command-receipt.tsx +27 -0
  225. package/examples/playground/app/agent/[agentId]/compaction-event.tsx +25 -24
  226. package/examples/playground/app/agent/[agentId]/compaction-panel.test.tsx +119 -0
  227. package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +208 -165
  228. package/examples/playground/app/agent/[agentId]/page.tsx +2 -16
  229. package/examples/playground/app/agent/events/route.ts +5 -2
  230. package/examples/playground/app/agent/ingress.test.ts +101 -0
  231. package/examples/playground/app/agent/model-input.test.tsx +58 -0
  232. package/examples/playground/app/agent/model-input.tsx +115 -0
  233. package/examples/playground/app/agent/model.test.ts +73 -0
  234. package/examples/playground/app/agent/model.ts +58 -26
  235. package/examples/playground/app/agent/new-agent-session.tsx +37 -19
  236. package/examples/playground/app/agent/page.tsx +4 -4
  237. package/examples/playground/app/agent/server.ts +19 -15
  238. package/examples/playground/app/agent/session.ts +5 -6
  239. package/examples/playground/app/agent/telemetry.test.ts +189 -0
  240. package/examples/playground/app/agent/telemetry.ts +30 -0
  241. package/examples/playground/app/agent/trace.test.ts +23 -0
  242. package/examples/playground/app/agent/trace.ts +18 -0
  243. package/examples/playground/app/canvas/[canvasId]/canvas-client.tsx +12 -12
  244. package/examples/playground/app/canvas/[canvasId]/page.tsx +1 -2
  245. package/examples/playground/app/canvas/[canvasId]/session.ts +2 -2
  246. package/examples/playground/app/canvas/model.ts +53 -53
  247. package/examples/playground/app/chat/[chatId]/agent-state.test.ts +176 -0
  248. package/examples/playground/app/chat/[chatId]/agent-stream-drawer.tsx +16 -1
  249. package/examples/playground/app/chat/[chatId]/chat-client.tsx +55 -77
  250. package/examples/playground/app/chat/[chatId]/chat-view.ts +37 -3
  251. package/examples/playground/app/chat/[chatId]/page.tsx +7 -15
  252. package/examples/playground/app/chat/[chatId]/session.ts +2 -19
  253. package/examples/playground/app/chat/agent/route.ts +9 -0
  254. package/examples/playground/app/chat/model.test.ts +46 -119
  255. package/examples/playground/app/chat/model.ts +82 -128
  256. package/examples/playground/app/chat/scheduler/route.ts +2 -2
  257. package/examples/playground/app/chat/server.test.ts +112 -0
  258. package/examples/playground/app/chat/server.ts +113 -117
  259. package/examples/playground/app/components/activity-feed.tsx +30 -1
  260. package/examples/playground/app/components/activity-history.test.tsx +35 -0
  261. package/examples/playground/app/components/demo-icons.tsx +89 -0
  262. package/examples/playground/app/components/demos.tsx +33 -0
  263. package/examples/playground/app/components/fast-link.test.tsx +58 -0
  264. package/examples/playground/app/components/fast-link.tsx +55 -0
  265. package/examples/playground/app/components/site-header.tsx +96 -0
  266. package/examples/playground/app/counter/counter-client.tsx +3 -3
  267. package/examples/playground/app/counter/model.test.ts +3 -3
  268. package/examples/playground/app/counter/model.ts +11 -21
  269. package/examples/playground/app/counter/page.tsx +2 -15
  270. package/examples/playground/app/counter/session.ts +2 -2
  271. package/examples/playground/app/documents/[documentId]/code-editor.tsx +12 -1
  272. package/examples/playground/app/documents/[documentId]/document-client.tsx +10 -11
  273. package/examples/playground/app/documents/[documentId]/page.tsx +1 -2
  274. package/examples/playground/app/documents/[documentId]/session.ts +2 -2
  275. package/examples/playground/app/documents/model.ts +27 -27
  276. package/examples/playground/app/globals.css +420 -207
  277. package/examples/playground/app/initial-state.test.tsx +54 -0
  278. package/examples/playground/app/layout.tsx +7 -23
  279. package/examples/playground/app/orders/[orderId]/page.tsx +1 -2
  280. package/examples/playground/app/orders/[orderId]/session.ts +2 -2
  281. package/examples/playground/app/orders/model.ts +49 -49
  282. package/examples/playground/app/page.tsx +13 -102
  283. package/examples/playground/app/recovery/[recoveryId]/page.tsx +1 -2
  284. package/examples/playground/app/recovery/[recoveryId]/session.ts +2 -2
  285. package/examples/playground/app/recovery/model.ts +30 -30
  286. package/examples/playground/instrumentation.ts +5 -0
  287. package/examples/playground/next.config.ts +17 -5
  288. package/examples/playground/package.json +13 -2
  289. package/examples/playground/scripts/a0-template.ts +77 -0
  290. package/examples/playground/vercel.json +36 -0
  291. package/package.json +29 -3
  292. package/src/actor-client.ts +16 -4
  293. package/src/actor-react.ts +25 -11
  294. package/src/actor-shared.ts +21 -6
  295. package/src/actor.ts +76 -31
  296. package/src/ai-agent-actions.ts +219 -0
  297. package/src/ai-agent.ts +2517 -0
  298. package/src/ai-app-state.ts +91 -0
  299. package/src/ai-client-state.ts +6 -3
  300. package/src/ai-client.ts +62 -0
  301. package/src/ai-context-schema.ts +25 -5
  302. package/src/ai-context.ts +90 -40
  303. package/src/ai-control-server.ts +92 -72
  304. package/src/ai-coordinator.ts +23 -22
  305. package/src/ai-fx.browser.ts +5 -0
  306. package/src/ai-fx.ts +833 -0
  307. package/src/ai-harness.ts +518 -0
  308. package/src/ai-message-base.ts +209 -0
  309. package/src/ai-message-projection.ts +262 -64
  310. package/src/ai-model-metadata.ts +11 -6
  311. package/src/ai-pi.browser.ts +5 -0
  312. package/src/ai-pi.ts +1263 -0
  313. package/src/ai-progress-batches.ts +26 -0
  314. package/src/ai-projector.ts +95 -14
  315. package/src/ai-react.ts +146 -0
  316. package/src/ai-sdk-step.ts +2 -2
  317. package/src/ai-server.browser.ts +2 -2
  318. package/src/ai-server.ts +23 -1965
  319. package/src/ai-stored-state.ts +48 -163
  320. package/src/ai.ts +339 -155
  321. package/src/bootstrap-stream.ts +72 -0
  322. package/src/bounded-cache.ts +55 -0
  323. package/src/client-render-session.ts +49 -0
  324. package/src/client.ts +688 -151
  325. package/src/contract.ts +92 -42
  326. package/src/deterministic-id.ts +37 -0
  327. package/src/equal-json.ts +32 -0
  328. package/src/errors.ts +13 -1
  329. package/src/index.ts +5 -1
  330. package/src/initial-snapshot.ts +39 -0
  331. package/src/internal.ts +52 -0
  332. package/src/libfx.d.ts +34 -0
  333. package/src/postgres-notification-scope.ts +14 -3
  334. package/src/react.ts +171 -206
  335. package/src/redis-http-reply.ts +256 -0
  336. package/src/redis-http-stream.ts +73 -0
  337. package/src/redis-http-subscriptions.ts +13 -5
  338. package/src/reducer-runtime.ts +52 -0
  339. package/src/reducer.ts +93 -11
  340. package/src/scheduler-qstash.ts +49 -20
  341. package/src/scheduler-task.ts +34 -2
  342. package/src/scheduler-vercel.ts +32 -84
  343. package/src/server-fetch.ts +139 -35
  344. package/src/server.ts +1133 -255
  345. package/src/session-clear.ts +23 -0
  346. package/src/session-socket.ts +154 -49
  347. package/src/sse.ts +71 -4
  348. package/src/store-memory.ts +252 -75
  349. package/src/store-polling.ts +18 -4
  350. package/src/store-postgres.ts +541 -114
  351. package/src/store-redis-core.ts +1450 -493
  352. package/src/store-redis-http.ts +18 -2
  353. package/src/store-redis-notify.ts +262 -85
  354. package/src/store-redis.ts +19 -9
  355. package/src/store-sqlite.ts +319 -45
  356. package/src/store.ts +79 -1
  357. package/src/stream-buffer.ts +43 -0
  358. package/src/telemetry.ts +14 -1
  359. package/src/wire.ts +241 -13
  360. package/dist/actor-ohPC-81x.d.ts.map +0 -1
  361. package/dist/actor-shared-B5tJfzt-.js.map +0 -1
  362. package/dist/ai-CrEf6p_W.d.ts.map +0 -1
  363. package/dist/ai-DgOBltJ_.js.map +0 -1
  364. package/dist/client-BYzHjkwU.js.map +0 -1
  365. package/dist/client-CzyacQpJ.d.ts.map +0 -1
  366. package/dist/contract-CKRg_E4q.js.map +0 -1
  367. package/dist/errors-DCk6ch5n.js.map +0 -1
  368. package/dist/errors-DvhSXnxk.d.ts.map +0 -1
  369. package/dist/internal-Dq2qYxou.js.map +0 -1
  370. package/dist/react.d.ts.map +0 -1
  371. package/dist/reducer-DEMjEY_O.js +0 -29
  372. package/dist/reducer-DEMjEY_O.js.map +0 -1
  373. package/dist/reducer-otzHjuJj.d.ts.map +0 -1
  374. package/dist/scheduler-task-BpzhPnRS.js.map +0 -1
  375. package/dist/server-BD5ckxZb.js.map +0 -1
  376. package/dist/server-DbD7IJVK.d.ts.map +0 -1
  377. package/dist/store-DyZM6fS5.d.ts.map +0 -1
  378. package/dist/store-polling-CmxUbV93.js.map +0 -1
  379. package/dist/store-redis-notify-BVLUfI3j.js +0 -1583
  380. package/dist/store-redis-notify-BVLUfI3j.js.map +0 -1
  381. package/dist/telemetry-B5jzpy6y.d.ts.map +0 -1
  382. package/dist/validate-XKT4FSNn.js +0 -30
  383. package/dist/validate-XKT4FSNn.js.map +0 -1
  384. package/dist/wire-BO5wWCb1.js.map +0 -1
  385. package/docs/concepts/01-contracts.mdx +0 -142
  386. package/docs/concepts/02-handlers.mdx +0 -284
  387. package/docs/concepts/03-durability.mdx +0 -314
  388. package/docs/concepts/04-state.mdx +0 -209
  389. package/docs/guides/01-timers.mdx +0 -185
  390. package/docs/guides/02-cancellation.mdx +0 -161
  391. package/docs/guides/03-react.mdx +0 -384
  392. package/docs/guides/04-local-first.mdx +0 -88
  393. package/docs/guides/05-production.mdx +0 -521
  394. package/docs/guides/06-ai-agents.mdx +0 -1318
  395. package/docs/guides/08-application-data.mdx +0 -113
  396. package/docs/guides/09-presence.mdx +0 -266
  397. package/docs/guides/10-transports.mdx +0 -221
  398. package/docs/reference/01-api.mdx +0 -1896
  399. package/docs/reference/02-errors.mdx +0 -149
  400. package/examples/playground/app/agent/[agentId]/compaction/route.ts +0 -14
  401. package/examples/playground/app/agent/compaction-settings.test.ts +0 -144
  402. package/examples/playground/app/agent/compaction-settings.ts +0 -49
  403. package/examples/playground/app/agent/compaction-timeline.test.ts +0 -377
  404. package/examples/playground/app/agent/compaction-timeline.ts +0 -208
  405. /package/docs/{concepts → contract}/meta.ts +0 -0
  406. /package/docs/{guides → server}/meta.ts +0 -0
@@ -13,19 +13,21 @@ import {
13
13
  useRef,
14
14
  useState,
15
15
  } from 'react'
16
- import { inputs } from 'experimental-a2/ai'
16
+ import type { InitialSnapshot } from 'experimental-a2'
17
+ import { inputs, type AIState } from 'experimental-a2/ai'
18
+ import { useAgent } from 'experimental-a2/ai/react'
17
19
  import { ActivityFeed } from '@/app/components/activity-feed'
18
20
  import { ConnectionPill } from '@/app/components/connection-pill'
19
- import {
20
- AgentStreamDrawer,
21
- type AgentStreamStatus,
22
- } from './agent-stream-drawer'
21
+ import { AgentStreamDrawer } from './agent-stream-drawer'
23
22
  import {
24
23
  buildChatTranscript,
24
+ agentRetry,
25
+ agentStreamStatus,
25
26
  selectAgentStream,
26
27
  type AgentMessageMetadata,
27
28
  } from './chat-view'
28
29
  import { insertComposerText, isComposerTypingKey } from './composer'
30
+ import { chatAgent, type ChatAgentMessage } from '../model'
29
31
  import { useSession } from './session'
30
32
 
31
33
  /** Max one durable presence ping per author in this window. */
@@ -180,8 +182,22 @@ const hasImportantFocus = (element: EventTarget | null): boolean =>
180
182
  (element.matches(IMPORTANT_FOCUS_SELECTOR) ||
181
183
  element.closest('dialog[open]') !== null)
182
184
 
183
- export function ChatClient({ chatId }: { chatId: string }): ReactNode {
184
- const { state, push, events, index, connection } = useSession()
185
+ export function ChatClient({
186
+ chatId,
187
+ agentInitial,
188
+ }: {
189
+ chatId: string
190
+ agentInitial: InitialSnapshot<AIState<ChatAgentMessage>>
191
+ }): ReactNode {
192
+ const { state, push, events, index, connection, history, loadHistory } =
193
+ useSession()
194
+ const agent = useAgent({
195
+ api: '/chat/agent',
196
+ id: chatId,
197
+ initial: agentInitial,
198
+ contract: chatAgent,
199
+ })
200
+ const ai = agent.state
185
201
 
186
202
  const [name, setName] = useState('')
187
203
  const [draft, setDraft] = useState('')
@@ -277,14 +293,14 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
277
293
  .filter(([, at]) => now - Date.parse(at) < TYPING_TTL_MS)
278
294
  .map(([author]) => author)
279
295
 
280
- const runningTool = state.ai.tools.find((tool) => tool.status === 'running')
296
+ const runningTool = ai.tools.find((tool) => tool.status === 'running')
281
297
  const activeResponseId =
282
- state.ai.activeGeneration?.responseMessageId ??
283
- state.ai.activeResponseMessageId ??
298
+ ai.activeGeneration?.responseMessageId ??
299
+ ai.activeResponseMessageId ??
284
300
  runningTool?.messageId ??
285
301
  null
286
302
  const activeMessage = activeResponseId
287
- ? state.ai.messages.find((message) => message.id === activeResponseId)
303
+ ? ai.messages.find((message) => message.id === activeResponseId)
288
304
  : undefined
289
305
  const writing =
290
306
  activeMessage?.parts.some(
@@ -294,11 +310,11 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
294
310
  ) === true ||
295
311
  (runningTool?.toolName === 'post' && runningTool.status === 'running')
296
312
  const agentActivity =
297
- state.ai.status === 'generating' ? (writing ? 'writing' : 'thinking') : null
313
+ ai.status === 'generating' ? (writing ? 'writing' : 'thinking') : null
298
314
  const presence = presenceLine(typingAuthors, agentActivity)
299
315
 
300
316
  const aiMessages = new Map(
301
- state.ai.messages.map((message) => [message.id, message]),
317
+ ai.messages.map((message) => [message.id, message]),
302
318
  )
303
319
  const lastPostByResponse = new Map<string, (typeof state.posts)[number]>()
304
320
  for (const post of state.posts) {
@@ -309,7 +325,7 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
309
325
  }
310
326
  const transcript = buildChatTranscript(state.messages, state.posts)
311
327
  const selectedAgentStream = openResponseId
312
- ? selectAgentStream(state.ai.messages, openResponseId)
328
+ ? selectAgentStream(ai.messages, openResponseId)
313
329
  : null
314
330
  const selectedStreamSummary = usageLabel(
315
331
  selectedAgentStream?.metadata ?? null,
@@ -317,32 +333,12 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
317
333
  const selectedStreamLive =
318
334
  openResponseId !== null &&
319
335
  openResponseId === activeResponseId &&
320
- state.ai.status === 'generating'
321
- const selectedStreamStatus = (() => {
322
- if (selectedStreamLive) return 'streaming'
323
- if (openResponseId === null) return 'completed'
324
- for (const event of events.toReversed()) {
325
- if (
326
- event.type === 'ai.message.interrupted' &&
327
- event.payload.messageId === openResponseId
328
- ) {
329
- return 'interrupted'
330
- }
331
- if (
332
- event.type === 'ai.generation.failed' &&
333
- event.payload.responseMessageId === openResponseId
334
- ) {
335
- return 'failed'
336
- }
337
- if (
338
- event.type === 'ai.message.completed' &&
339
- event.payload.messageId === openResponseId
340
- ) {
341
- return 'completed'
342
- }
343
- }
344
- return 'completed'
345
- })() satisfies AgentStreamStatus
336
+ ai.status === 'generating'
337
+ const selectedStreamStatus = agentStreamStatus({
338
+ state: ai,
339
+ responseMessageId: openResponseId,
340
+ streaming: selectedStreamLive,
341
+ })
346
342
 
347
343
  const participants = useMemo(() => {
348
344
  const seen = new Set<string>(['agent'])
@@ -358,26 +354,11 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
358
354
  )
359
355
  : []
360
356
 
361
- const totalTokens = state.ai.usage.reduce(
357
+ const totalTokens = ai.usage.reduce(
362
358
  (total, entry) => total + (entry.usage.totalTokens ?? 0),
363
359
  0,
364
360
  )
365
- const failedGeneration =
366
- state.ai.status === 'failed'
367
- ? events.findLast(
368
- (
369
- event,
370
- ): event is Extract<
371
- (typeof events)[number],
372
- { type: 'ai.generation.failed' }
373
- > =>
374
- event.type === 'ai.generation.failed' &&
375
- event.payload.superseded !== true,
376
- )
377
- : undefined
378
- const failedRootMessageId = failedGeneration
379
- ? state.ai.active?.input.id
380
- : undefined
361
+ const retry = agentRetry(ai)
381
362
 
382
363
  const sendTypingPing = useCallback((): void => {
383
364
  if (!name) return
@@ -535,29 +516,23 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
535
516
  }
536
517
 
537
518
  const stopAgent = async (): Promise<void> => {
538
- if (!state.ai.active) return
519
+ if (!ai.active) return
539
520
  setError(null)
540
521
  try {
541
- await push(
542
- ...inputs.stop({
543
- turnId: state.ai.active.turnId,
544
- lastSeenIndex: index,
545
- }),
546
- )
522
+ await agent.stop()
547
523
  } catch (err) {
548
524
  setError(err instanceof Error ? err.message : String(err))
549
525
  }
550
526
  }
551
527
 
552
528
  const retryAgent = async (): Promise<void> => {
553
- if (!failedGeneration || !failedRootMessageId) return
529
+ if (!retry) return
554
530
  setError(null)
555
531
  setRetrying(true)
556
532
  try {
557
- await push(
533
+ await agent.push(
558
534
  ...inputs.retry({
559
- messageId: failedRootMessageId,
560
- responseMessageId: failedGeneration.payload.responseMessageId,
535
+ ...retry,
561
536
  retryId: crypto.randomUUID(),
562
537
  }),
563
538
  )
@@ -606,12 +581,12 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
606
581
  {state.messages.length}{' '}
607
582
  {state.messages.length === 1 ? 'message' : 'messages'}
608
583
  </span>
609
- {state.ai.usage.length > 0 ? (
584
+ {ai.usage.length > 0 ? (
610
585
  <span className="badge">
611
586
  {totalTokens.toLocaleString()} AI tokens
612
587
  </span>
613
588
  ) : null}
614
- {state.ai.status === 'failed' ? (
589
+ {ai.status === 'failed' ? (
615
590
  <span className="badge cut">agent paused</span>
616
591
  ) : null}
617
592
  </div>
@@ -730,18 +705,16 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
730
705
  </div>
731
706
 
732
707
  <div className="chat-composer-dock" ref={composerDockRef}>
733
- {state.ai.status === 'failed' ? (
708
+ {ai.status === 'failed' ? (
734
709
  <div className="chat-agent-error" role="alert">
735
710
  <p>
736
711
  <strong>Agent paused.</strong>{' '}
737
- {state.ai.error ?? 'The model step failed.'}
712
+ {ai.error ?? 'The model step failed.'}
738
713
  </p>
739
714
  <button
740
715
  type="button"
741
716
  className="secondary compact"
742
- disabled={
743
- !failedGeneration || !failedRootMessageId || retrying
744
- }
717
+ disabled={!retry || retrying}
745
718
  onClick={() => void retryAgent()}
746
719
  >
747
720
  {retrying ? 'Retrying…' : 'Retry agent'}
@@ -888,10 +861,15 @@ export function ChatClient({ chatId }: { chatId: string }): ReactNode {
888
861
  onClick={() => setSidebarOpen((open) => !open)}
889
862
  >
890
863
  <span>Room details</span>
891
- <span className="badge">{events.length} events</span>
864
+ <span className="badge">{events.length} loaded events</span>
892
865
  </button>
893
866
  <div className="chat-sidebar-body" id="chat-sidebar-body">
894
- <ActivityFeed events={events} index={index} />
867
+ <ActivityFeed
868
+ events={events}
869
+ index={index}
870
+ history={history}
871
+ loadHistory={loadHistory}
872
+ />
895
873
  </div>
896
874
  </aside>
897
875
  </div>
@@ -1,9 +1,43 @@
1
+ import type { AIState, TranscriptMessage } from 'experimental-a2/ai'
1
2
  import type {
2
3
  AgentPost,
3
4
  ChatAgentMessage,
4
5
  ChatMessage,
5
6
  ChatMessageMetadata,
6
7
  } from '../model'
8
+ import type { AgentStreamStatus } from './agent-stream-drawer'
9
+
10
+ export function agentStreamStatus({
11
+ state,
12
+ responseMessageId,
13
+ streaming,
14
+ }: {
15
+ state: AIState<ChatAgentMessage>
16
+ responseMessageId: string | null
17
+ streaming: boolean
18
+ }): AgentStreamStatus {
19
+ if (streaming) return 'streaming'
20
+ const owner =
21
+ responseMessageId === null
22
+ ? undefined
23
+ : state.responseOwners[responseMessageId]
24
+ const terminal = owner && state.terminalGenerations?.[owner.generationId]
25
+ return terminal === 'failed' || terminal === 'interrupted'
26
+ ? terminal
27
+ : 'completed'
28
+ }
29
+
30
+ export function agentRetry(state: AIState<ChatAgentMessage>): {
31
+ messageId: string
32
+ responseMessageId: string
33
+ } | null {
34
+ if (state.status !== 'failed' || state.active === null) return null
35
+ const responseMessageId =
36
+ state.activeResponseMessageId ?? state.activeProjection?.responseMessageId
37
+ return responseMessageId
38
+ ? { messageId: state.active.input.id, responseMessageId }
39
+ : null
40
+ }
7
41
 
8
42
  const MESSAGE_BURST_MS = 60_000
9
43
 
@@ -80,13 +114,13 @@ export type AgentMessageMetadata = Extract<
80
114
  >
81
115
 
82
116
  export type AgentStreamSelection = {
83
- message: ChatAgentMessage
84
- parts: ChatAgentMessage['parts']
117
+ message: TranscriptMessage<ChatAgentMessage>
118
+ parts: TranscriptMessage<ChatAgentMessage>['parts']
85
119
  metadata: AgentMessageMetadata | null
86
120
  }
87
121
 
88
122
  export const selectAgentStream = (
89
- messages: ChatAgentMessage[],
123
+ messages: TranscriptMessage<ChatAgentMessage>[],
90
124
  responseMessageId: string,
91
125
  ): AgentStreamSelection | null => {
92
126
  const message = messages.find(
@@ -1,6 +1,5 @@
1
1
  import type { ReactNode } from 'react'
2
- import { chatReducer } from '../model'
3
- import { chatServer } from '../server'
2
+ import { chatAgentServer, chatServer } from '../server'
4
3
  import { SessionProvider } from './session'
5
4
  import { ChatClient } from './chat-client'
6
5
 
@@ -10,21 +9,14 @@ export default async function ChatSessionPage({
10
9
  params: Promise<{ chatId: string }>
11
10
  }): Promise<ReactNode> {
12
11
  const { chatId } = await params
13
- const session = chatServer.session(chatId)
14
- const { state, index } = await session.state(chatReducer)
15
- const events = (await session.history()).filter(
16
- (event) => event.index <= index,
17
- )
12
+ const [initial, agentInitial] = await Promise.all([
13
+ chatServer.session(chatId).state(),
14
+ chatAgentServer.session(chatId).state(),
15
+ ])
18
16
 
19
17
  return (
20
- <SessionProvider
21
- sessionId={chatId}
22
- api="/chat/events"
23
- initialState={state}
24
- initialIndex={index}
25
- initialEvents={events}
26
- >
27
- <ChatClient chatId={chatId} />
18
+ <SessionProvider sessionId={chatId} api="/chat/events" initial={initial}>
19
+ <ChatClient chatId={chatId} agentInitial={agentInitial} />
28
20
  </SessionProvider>
29
21
  )
30
22
  }
@@ -1,22 +1,5 @@
1
1
  'use client'
2
2
  import { createReact } from 'experimental-a2/react'
3
- import { chatReducer, viewChatState, type ChatViewState } from '../model'
4
- import { useMemo } from 'react'
3
+ import { chat } from '../model'
5
4
 
6
- const { SessionProvider, useSession: useStoredSession } = createReact({
7
- reducer: chatReducer,
8
- })
9
-
10
- export { SessionProvider }
11
-
12
- type ChatSession = Omit<ReturnType<typeof useStoredSession>, 'state'> & {
13
- state: ChatViewState
14
- }
15
-
16
- export function useSession(): ChatSession {
17
- const session = useStoredSession()
18
- return useMemo(
19
- () => ({ ...session, state: viewChatState(session.state) }),
20
- [session],
21
- )
22
- }
5
+ export const { SessionProvider, useSession } = createReact({ contract: chat })
@@ -0,0 +1,9 @@
1
+ import { authorizeChatAgentOperation, chatAgentServer } from '../server'
2
+
3
+ export const GET = chatAgentServer.fetch
4
+
5
+ export function POST(request: Request): Promise<Response> {
6
+ return chatAgentServer.fetch(request, {
7
+ authorize: authorizeChatAgentOperation,
8
+ })
9
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ContractEvent } from 'experimental-a2'
2
- import { describe, expect, it } from 'vitest'
3
- import { chat, chatReducer, viewChatState, type ChatState } from './model'
2
+ import { expect, it } from 'vitest'
3
+ import { chat, chatReducer, type ChatState } from './model'
4
4
 
5
5
  type ChatEvents = typeof chat.events
6
6
  type ChatEvent<
@@ -21,31 +21,10 @@ const event = <K extends keyof ChatEvents & string>(
21
21
  createdAt: new Date(index * 1_000),
22
22
  }) as ChatEvent<K>
23
23
 
24
- const generation = {
25
- requestId: 'request-1',
26
- messageId: 'participant-1',
27
- generationId: 'generation-1',
28
- responseMessageId: 'assistant-1',
29
- attempt: 1,
30
- model: 'test/model',
31
- } satisfies ChatEvent<'ai.generation.started'>['payload']
32
-
33
- const setup = (): ChatEvent[] => [event(1, 'ai.generation.started', generation)]
34
-
35
- const toolCalled = (index: number, toolCallId: string, message: string) =>
36
- event(index, 'ai.tool.called', {
37
- requestId: generation.requestId,
38
- messageId: generation.responseMessageId,
39
- generationId: generation.generationId,
40
- toolCallId,
41
- toolName: 'post',
42
- input: { message },
43
- })
44
-
45
24
  const messagePosted = (index: number, toolCallId: string, message: string) =>
46
25
  event(index, 'agent.message.posted', {
47
- generationId: generation.generationId,
48
- responseMessageId: generation.responseMessageId,
26
+ generationId: 'generation-1',
27
+ responseMessageId: 'assistant-1',
49
28
  toolCallId,
50
29
  message,
51
30
  })
@@ -56,100 +35,48 @@ const fold = (...events: ChatEvent[]): ChatState =>
56
35
  chatReducer.initialState,
57
36
  )
58
37
 
59
- describe('chat agent post projection', () => {
60
- it('keeps tool calls invisible until the tool appends its post event', () => {
61
- const called = toolCalled(2, 'call-1', 'Hello from the tool')
62
- let state = fold(...setup(), called)
63
-
64
- expect(state.posts).toEqual([])
65
- expect(viewChatState(state).ai.tools).toEqual([
66
- expect.objectContaining({
67
- generationId: generation.generationId,
68
- toolCallId: 'call-1',
69
- status: 'running',
70
- }),
71
- ])
72
-
73
- state = chatReducer.fold(
74
- state,
75
- messagePosted(3, 'call-1', 'Hello from the tool'),
76
- )
77
-
78
- expect(state.posts).toEqual([
79
- {
80
- generationId: generation.generationId,
81
- responseMessageId: generation.responseMessageId,
82
- toolCallId: 'call-1',
83
- index: 3,
84
- text: 'Hello from the tool',
85
- at: new Date(3_000).toISOString(),
86
- },
87
- ])
88
- })
89
-
90
- it('projects every explicit post in durable event order', () => {
91
- const firstCall = toolCalled(2, 'call-1', 'First post')
92
- const secondCall = toolCalled(3, 'call-2', 'Second post')
93
- const state = fold(
94
- ...setup(),
95
- firstCall,
96
- secondCall,
97
- messagePosted(4, 'call-1', 'First post'),
98
- messagePosted(5, 'call-2', 'Second post'),
99
- )
100
-
101
- expect(
102
- state.posts.map(({ toolCallId, index, text }) => ({
103
- toolCallId,
104
- index,
105
- text,
106
- })),
107
- ).toEqual([
108
- { toolCallId: 'call-1', index: 4, text: 'First post' },
109
- { toolCallId: 'call-2', index: 5, text: 'Second post' },
110
- ])
111
- })
112
-
113
- it('rewinds posts past an interruption cutpoint and ignores late appends', () => {
114
- const firstCall = toolCalled(2, 'call-1', 'Visible before stop')
115
- const secondCall = toolCalled(4, 'call-2', 'Not visible at stop')
116
- let state = fold(
117
- ...setup(),
118
- firstCall,
119
- messagePosted(3, 'call-1', 'Visible before stop'),
120
- secondCall,
121
- messagePosted(5, 'call-2', 'Not visible at stop'),
122
- )
123
-
124
- expect(state.posts.map((post) => post.toolCallId)).toEqual([
125
- 'call-1',
126
- 'call-2',
127
- ])
128
-
129
- state = chatReducer.fold(
130
- state,
131
- event(6, 'ai.message.interrupted', {
132
- messageId: generation.responseMessageId,
133
- generationId: generation.generationId,
134
- lastSeenIndex: 3,
135
- }),
136
- )
137
-
138
- expect(state.posts.map((post) => post.toolCallId)).toEqual(['call-1'])
139
- expect(viewChatState(state).ai.activeGeneration).toBeNull()
140
-
141
- state = chatReducer.fold(
142
- state,
143
- toolCalled(7, 'call-3', 'Called after stop'),
144
- )
145
-
146
- expect(viewChatState(state).ai.activeGeneration).toBeNull()
147
-
148
- state = chatReducer.fold(
149
- state,
150
- messagePosted(8, 'call-3', 'Late after stop'),
151
- )
38
+ it("orders the agent's posts among everyone's messages by log index", () => {
39
+ const state = fold(
40
+ event(1, 'typed', { author: 'alice' }),
41
+ event(2, 'message.sent', { author: 'alice', text: '@agent hi' }),
42
+ messagePosted(3, 'call-1', 'Hello'),
43
+ event(4, 'message.sent', { author: 'bob', text: 'mid-reply' }),
44
+ messagePosted(5, 'call-2', 'And more'),
45
+ )
46
+ expect(state.lastTypingAt).toEqual({})
47
+ expect(
48
+ state.messages.map(({ index, author }) => ({ index, author })),
49
+ ).toEqual([
50
+ { index: 2, author: 'alice' },
51
+ { index: 4, author: 'bob' },
52
+ ])
53
+ expect(state.posts).toEqual([
54
+ {
55
+ generationId: 'generation-1',
56
+ responseMessageId: 'assistant-1',
57
+ toolCallId: 'call-1',
58
+ index: 3,
59
+ text: 'Hello',
60
+ at: new Date(3_000).toISOString(),
61
+ },
62
+ {
63
+ generationId: 'generation-1',
64
+ responseMessageId: 'assistant-1',
65
+ toolCallId: 'call-2',
66
+ index: 5,
67
+ text: 'And more',
68
+ at: new Date(5_000).toISOString(),
69
+ },
70
+ ])
71
+ })
152
72
 
153
- expect(state.posts.map((post) => post.toolCallId)).toEqual(['call-1'])
73
+ it('records who changed ambient mode', () => {
74
+ const state = fold(
75
+ event(1, 'agent.ambient.changed', { enabled: true, author: 'alice' }),
76
+ )
77
+ expect(state.ambient).toEqual({
78
+ enabled: true,
79
+ changedBy: 'alice',
80
+ changedAt: new Date(1_000).toISOString(),
154
81
  })
155
82
  })