@athenaintel/react 0.22.4 → 0.23.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 (350) hide show
  1. package/README.md +390 -4
  2. package/dist/chat/AthenaChat.cjs +346 -244
  3. package/dist/chat/AthenaChat.cjs.map +1 -1
  4. package/dist/chat/AthenaChat.d.ts +19 -8
  5. package/dist/chat/AthenaChat.js +350 -248
  6. package/dist/chat/AthenaChat.js.map +1 -1
  7. package/dist/chat/StatewireApprovalCard.cjs +2 -1
  8. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  9. package/dist/chat/StatewireApprovalCard.js +2 -1
  10. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  11. package/dist/chat/StatewireClientToolBridge.cjs +1 -0
  12. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  13. package/dist/chat/StatewireClientToolBridge.js +1 -0
  14. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  15. package/dist/chat/StatewireQueuedMessages.cjs +1 -0
  16. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  17. package/dist/chat/StatewireQueuedMessages.js +1 -0
  18. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  19. package/dist/chat/WaitingOnYouBanner.cjs +100 -0
  20. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -0
  21. package/dist/chat/WaitingOnYouBanner.d.ts +29 -0
  22. package/dist/chat/WaitingOnYouBanner.js +100 -0
  23. package/dist/chat/WaitingOnYouBanner.js.map +1 -0
  24. package/dist/chat/athena-chat-welcome.cjs +148 -0
  25. package/dist/chat/athena-chat-welcome.cjs.map +1 -0
  26. package/dist/chat/athena-chat-welcome.d.ts +54 -0
  27. package/dist/chat/athena-chat-welcome.js +148 -0
  28. package/dist/chat/athena-chat-welcome.js.map +1 -0
  29. package/dist/chat/athena-composer-controls.cjs +76 -0
  30. package/dist/chat/athena-composer-controls.cjs.map +1 -0
  31. package/dist/chat/athena-composer-controls.d.ts +21 -0
  32. package/dist/chat/athena-composer-controls.js +76 -0
  33. package/dist/chat/athena-composer-controls.js.map +1 -0
  34. package/dist/chat/composer-keyboard-hint.cjs +61 -0
  35. package/dist/chat/composer-keyboard-hint.cjs.map +1 -0
  36. package/dist/chat/composer-keyboard-hint.d.ts +33 -0
  37. package/dist/chat/composer-keyboard-hint.js +61 -0
  38. package/dist/chat/composer-keyboard-hint.js.map +1 -0
  39. package/dist/chat/message-time.cjs +64 -0
  40. package/dist/chat/message-time.cjs.map +1 -0
  41. package/dist/chat/message-time.d.ts +45 -0
  42. package/dist/chat/message-time.js +64 -0
  43. package/dist/chat/message-time.js.map +1 -0
  44. package/dist/chat/run-activity.cjs +38 -7
  45. package/dist/chat/run-activity.cjs.map +1 -1
  46. package/dist/chat/run-activity.d.ts +14 -3
  47. package/dist/chat/run-activity.js +39 -8
  48. package/dist/chat/run-activity.js.map +1 -1
  49. package/dist/chat/statewire-client-tools.cjs +2 -1
  50. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  51. package/dist/chat/statewire-client-tools.js +2 -1
  52. package/dist/chat/statewire-client-tools.js.map +1 -1
  53. package/dist/chat/super-grouping/activity-timeline.cjs +2 -1
  54. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  55. package/dist/chat/super-grouping/activity-timeline.js +2 -1
  56. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  57. package/dist/chat/thread-scroll-to-bottom.cjs +24 -0
  58. package/dist/chat/thread-scroll-to-bottom.cjs.map +1 -0
  59. package/dist/chat/thread-scroll-to-bottom.d.ts +11 -0
  60. package/dist/chat/thread-scroll-to-bottom.js +24 -0
  61. package/dist/chat/thread-scroll-to-bottom.js.map +1 -0
  62. package/dist/chat/tiptap-text.cjs +26 -95
  63. package/dist/chat/tiptap-text.cjs.map +1 -1
  64. package/dist/chat/tiptap-text.d.ts +13 -0
  65. package/dist/chat/tiptap-text.js +26 -95
  66. package/dist/chat/tiptap-text.js.map +1 -1
  67. package/dist/chat/use-athena-run-live.cjs +2 -1
  68. package/dist/chat/use-athena-run-live.cjs.map +1 -1
  69. package/dist/chat/use-athena-run-live.js +2 -1
  70. package/dist/chat/use-athena-run-live.js.map +1 -1
  71. package/dist/chat/use-statewire-approval-request.cjs +1 -0
  72. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  73. package/dist/chat/use-statewire-approval-request.js +1 -0
  74. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  75. package/dist/chat-ui/composer/tiptap-composer.cjs +39 -4
  76. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  77. package/dist/chat-ui/composer/tiptap-composer.d.ts +21 -0
  78. package/dist/chat-ui/composer/tiptap-composer.js +39 -4
  79. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  80. package/dist/chat-ui/index.cjs +15 -1
  81. package/dist/chat-ui/index.cjs.map +1 -1
  82. package/dist/chat-ui/index.d.ts +6 -3
  83. package/dist/chat-ui/index.js +16 -2
  84. package/dist/chat-ui/index.js.map +1 -1
  85. package/dist/chat-ui/mentions/mention-chip.cjs +31 -4
  86. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  87. package/dist/chat-ui/mentions/mention-chip.js +31 -4
  88. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  89. package/dist/chat-ui/messages/message-components.cjs +19 -1
  90. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  91. package/dist/chat-ui/messages/message-components.d.ts +13 -0
  92. package/dist/chat-ui/messages/message-components.js +19 -1
  93. package/dist/chat-ui/messages/message-components.js.map +1 -1
  94. package/dist/chat-ui/messages/runtime-message.cjs +2 -1
  95. package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
  96. package/dist/chat-ui/messages/runtime-message.js +2 -1
  97. package/dist/chat-ui/messages/runtime-message.js.map +1 -1
  98. package/dist/chat-ui/messages/statewire-continue-run.cjs +1 -0
  99. package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
  100. package/dist/chat-ui/messages/statewire-continue-run.js +1 -0
  101. package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
  102. package/dist/chat-ui/messages/user-edit-composer.cjs +3 -1
  103. package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
  104. package/dist/chat-ui/messages/user-edit-composer.js +3 -1
  105. package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
  106. package/dist/chat-ui/messages/wake-marker.cjs +84 -0
  107. package/dist/chat-ui/messages/wake-marker.cjs.map +1 -0
  108. package/dist/chat-ui/messages/wake-marker.d.ts +16 -0
  109. package/dist/chat-ui/messages/wake-marker.js +84 -0
  110. package/dist/chat-ui/messages/wake-marker.js.map +1 -0
  111. package/dist/chat-ui/rich-text/chat-blocks.cjs +177 -0
  112. package/dist/chat-ui/rich-text/chat-blocks.cjs.map +1 -0
  113. package/dist/chat-ui/rich-text/chat-blocks.d.ts +58 -0
  114. package/dist/chat-ui/rich-text/chat-blocks.js +177 -0
  115. package/dist/chat-ui/rich-text/chat-blocks.js.map +1 -0
  116. package/dist/chat-ui/rich-text/chat-link.cjs +69 -0
  117. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -0
  118. package/dist/chat-ui/rich-text/chat-link.d.ts +24 -0
  119. package/dist/chat-ui/rich-text/chat-link.js +69 -0
  120. package/dist/chat-ui/rich-text/chat-link.js.map +1 -0
  121. package/dist/chat-ui/rich-text/chat-message-body.cjs +144 -18
  122. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  123. package/dist/chat-ui/rich-text/chat-message-body.d.ts +10 -1
  124. package/dist/chat-ui/rich-text/chat-message-body.js +149 -23
  125. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  126. package/dist/chat-ui/rich-text/chat-node-views.cjs +38 -5
  127. package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -1
  128. package/dist/chat-ui/rich-text/chat-node-views.d.ts +11 -0
  129. package/dist/chat-ui/rich-text/chat-node-views.js +39 -6
  130. package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -1
  131. package/dist/chat-ui/rich-text/chat-text-schema.cjs +20 -3
  132. package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
  133. package/dist/chat-ui/rich-text/chat-text-schema.d.ts +15 -2
  134. package/dist/chat-ui/rich-text/chat-text-schema.js +20 -3
  135. package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
  136. package/dist/chat-ui/rich-text/citation-anchors.cjs +139 -0
  137. package/dist/chat-ui/rich-text/citation-anchors.cjs.map +1 -0
  138. package/dist/chat-ui/rich-text/citation-anchors.d.ts +74 -0
  139. package/dist/chat-ui/rich-text/citation-anchors.js +139 -0
  140. package/dist/chat-ui/rich-text/citation-anchors.js.map +1 -0
  141. package/dist/chat-ui/statewire.cjs +8 -1
  142. package/dist/chat-ui/statewire.cjs.map +1 -1
  143. package/dist/chat-ui/statewire.d.ts +2 -0
  144. package/dist/chat-ui/statewire.js +8 -1
  145. package/dist/chat-ui/statewire.js.map +1 -1
  146. package/dist/chat-ui/thread/chat-composer-dock.cjs +27 -6
  147. package/dist/chat-ui/thread/chat-composer-dock.cjs.map +1 -1
  148. package/dist/chat-ui/thread/chat-composer-dock.d.ts +21 -1
  149. package/dist/chat-ui/thread/chat-composer-dock.js +28 -7
  150. package/dist/chat-ui/thread/chat-composer-dock.js.map +1 -1
  151. package/dist/chat-ui/thread/statewire-history-edge.cjs +12 -13
  152. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  153. package/dist/chat-ui/thread/statewire-history-edge.d.ts +11 -10
  154. package/dist/chat-ui/thread/statewire-history-edge.js +13 -14
  155. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  156. package/dist/chat-ui/thread/thread-loading-overlay.cjs +31 -0
  157. package/dist/chat-ui/thread/thread-loading-overlay.cjs.map +1 -0
  158. package/dist/chat-ui/thread/thread-loading-overlay.d.ts +14 -0
  159. package/dist/chat-ui/thread/thread-loading-overlay.js +31 -0
  160. package/dist/chat-ui/thread/thread-loading-overlay.js.map +1 -0
  161. package/dist/composer/ComposerDropZone.cjs +8 -1
  162. package/dist/composer/ComposerDropZone.cjs.map +1 -1
  163. package/dist/composer/ComposerDropZone.d.ts +6 -0
  164. package/dist/composer/ComposerDropZone.js +9 -2
  165. package/dist/composer/ComposerDropZone.js.map +1 -1
  166. package/dist/diagnostics/errors.cjs +20 -0
  167. package/dist/diagnostics/errors.cjs.map +1 -1
  168. package/dist/diagnostics/errors.d.ts +20 -0
  169. package/dist/diagnostics/errors.js +20 -0
  170. package/dist/diagnostics/errors.js.map +1 -1
  171. package/dist/index.cjs +47 -1
  172. package/dist/index.cjs.map +1 -1
  173. package/dist/index.d.ts +26 -7
  174. package/dist/index.js +51 -5
  175. package/dist/index.js.map +1 -1
  176. package/dist/lib/use-prefers-reduced-motion.cjs +29 -0
  177. package/dist/lib/use-prefers-reduced-motion.cjs.map +1 -0
  178. package/dist/lib/use-prefers-reduced-motion.d.ts +6 -0
  179. package/dist/lib/use-prefers-reduced-motion.js +29 -0
  180. package/dist/lib/use-prefers-reduced-motion.js.map +1 -0
  181. package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
  182. package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
  183. package/dist/packages/agent-runtime/src/conversation-list.cjs +59 -0
  184. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
  185. package/dist/packages/agent-runtime/src/conversation-list.js +60 -1
  186. package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
  187. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs +5 -3
  188. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  189. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js +5 -3
  190. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  191. package/dist/packages/agent-runtime/src/edit-command.cjs +21 -17
  192. package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
  193. package/dist/packages/agent-runtime/src/edit-command.js +22 -18
  194. package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
  195. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +9 -11
  196. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  197. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +9 -11
  198. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  199. package/dist/packages/agent-runtime/src/github-wake-event.cjs +86 -0
  200. package/dist/packages/agent-runtime/src/github-wake-event.cjs.map +1 -0
  201. package/dist/packages/agent-runtime/src/github-wake-event.js +86 -0
  202. package/dist/packages/agent-runtime/src/github-wake-event.js.map +1 -0
  203. package/dist/packages/agent-runtime/src/message-window.cjs +150 -42
  204. package/dist/packages/agent-runtime/src/message-window.cjs.map +1 -1
  205. package/dist/packages/agent-runtime/src/message-window.js +151 -43
  206. package/dist/packages/agent-runtime/src/message-window.js.map +1 -1
  207. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +13 -9
  208. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  209. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +13 -9
  210. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  211. package/dist/packages/agent-runtime/src/statewire-suspended.cjs +1 -3
  212. package/dist/packages/agent-runtime/src/statewire-suspended.cjs.map +1 -1
  213. package/dist/packages/agent-runtime/src/statewire-suspended.js +1 -3
  214. package/dist/packages/agent-runtime/src/statewire-suspended.js.map +1 -1
  215. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -1
  216. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -1
  217. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +61 -30
  218. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  219. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +59 -28
  220. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  221. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs +56 -36
  222. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  223. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js +59 -39
  224. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  225. package/dist/packages/react/package.json.cjs +1 -1
  226. package/dist/packages/react/package.json.js +1 -1
  227. package/dist/provider/AthenaContext.cjs.map +1 -1
  228. package/dist/provider/AthenaContext.d.ts +3 -0
  229. package/dist/provider/AthenaContext.js.map +1 -1
  230. package/dist/provider/AthenaProvider.cjs +93 -45
  231. package/dist/provider/AthenaProvider.cjs.map +1 -1
  232. package/dist/provider/AthenaProvider.d.ts +6 -1
  233. package/dist/provider/AthenaProvider.js +97 -49
  234. package/dist/provider/AthenaProvider.js.map +1 -1
  235. package/dist/runtime/statewire-lifecycle.cjs +2 -1
  236. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  237. package/dist/runtime/statewire-lifecycle.js +2 -1
  238. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  239. package/dist/runtime/useAthenaStatewireRuntime.cjs +3 -2
  240. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  241. package/dist/runtime/useAthenaStatewireRuntime.d.ts +8 -12
  242. package/dist/runtime/useAthenaStatewireRuntime.js +3 -2
  243. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  244. package/dist/search/useAthenaRepoSearch.cjs +94 -0
  245. package/dist/search/useAthenaRepoSearch.cjs.map +1 -0
  246. package/dist/search/useAthenaRepoSearch.d.ts +95 -0
  247. package/dist/search/useAthenaRepoSearch.js +94 -0
  248. package/dist/search/useAthenaRepoSearch.js.map +1 -0
  249. package/dist/styles.css +1 -1
  250. package/dist/threads/SessionList.cjs +486 -0
  251. package/dist/threads/SessionList.cjs.map +1 -0
  252. package/dist/threads/SessionList.d.ts +60 -0
  253. package/dist/threads/SessionList.js +486 -0
  254. package/dist/threads/SessionList.js.map +1 -0
  255. package/dist/threads/api.cjs +112 -4
  256. package/dist/threads/api.cjs.map +1 -1
  257. package/dist/threads/api.d.ts +128 -1
  258. package/dist/threads/api.js +113 -5
  259. package/dist/threads/api.js.map +1 -1
  260. package/dist/threads/conversation-list-fetcher.cjs +27 -3
  261. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  262. package/dist/threads/conversation-list-fetcher.d.ts +16 -3
  263. package/dist/threads/conversation-list-fetcher.js +27 -3
  264. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  265. package/dist/threads/index.d.ts +5 -1
  266. package/dist/threads/session-list-model.cjs +130 -0
  267. package/dist/threads/session-list-model.cjs.map +1 -0
  268. package/dist/threads/session-list-model.d.ts +62 -0
  269. package/dist/threads/session-list-model.js +130 -0
  270. package/dist/threads/session-list-model.js.map +1 -0
  271. package/dist/threads/statewire-thread-list.cjs +220 -38
  272. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  273. package/dist/threads/statewire-thread-list.d.ts +152 -2
  274. package/dist/threads/statewire-thread-list.js +222 -40
  275. package/dist/threads/statewire-thread-list.js.map +1 -1
  276. package/dist/threads/thread-prefetcher.cjs +63 -0
  277. package/dist/threads/thread-prefetcher.cjs.map +1 -0
  278. package/dist/threads/thread-prefetcher.d.ts +54 -0
  279. package/dist/threads/thread-prefetcher.js +63 -0
  280. package/dist/threads/thread-prefetcher.js.map +1 -0
  281. package/dist/threads/use-athena-session-attention.cjs +22 -0
  282. package/dist/threads/use-athena-session-attention.cjs.map +1 -0
  283. package/dist/threads/use-athena-session-attention.d.ts +24 -0
  284. package/dist/threads/use-athena-session-attention.js +22 -0
  285. package/dist/threads/use-athena-session-attention.js.map +1 -0
  286. package/dist/threads/use-rest-conversation-history.cjs +25 -2
  287. package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
  288. package/dist/threads/use-rest-conversation-history.d.ts +8 -1
  289. package/dist/threads/use-rest-conversation-history.js +25 -2
  290. package/dist/threads/use-rest-conversation-history.js.map +1 -1
  291. package/dist/threads/use-statewire-conversation-history.cjs +40 -16
  292. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  293. package/dist/threads/use-statewire-conversation-history.d.ts +13 -0
  294. package/dist/threads/use-statewire-conversation-history.js +40 -16
  295. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  296. package/dist/threads/use-thread-archive.cjs +128 -0
  297. package/dist/threads/use-thread-archive.cjs.map +1 -0
  298. package/dist/threads/use-thread-archive.d.ts +26 -0
  299. package/dist/threads/use-thread-archive.js +128 -0
  300. package/dist/threads/use-thread-archive.js.map +1 -0
  301. package/dist/threads/use-thread-favorite.cjs +53 -0
  302. package/dist/threads/use-thread-favorite.cjs.map +1 -0
  303. package/dist/threads/use-thread-favorite.d.ts +49 -0
  304. package/dist/threads/use-thread-favorite.js +53 -0
  305. package/dist/threads/use-thread-favorite.js.map +1 -0
  306. package/dist/threads/use-thread-field-override.cjs +184 -0
  307. package/dist/threads/use-thread-field-override.cjs.map +1 -0
  308. package/dist/threads/use-thread-field-override.d.ts +55 -0
  309. package/dist/threads/use-thread-field-override.js +184 -0
  310. package/dist/threads/use-thread-field-override.js.map +1 -0
  311. package/dist/threads/use-thread-rename.cjs +53 -0
  312. package/dist/threads/use-thread-rename.cjs.map +1 -0
  313. package/dist/threads/use-thread-rename.d.ts +48 -0
  314. package/dist/threads/use-thread-rename.js +53 -0
  315. package/dist/threads/use-thread-rename.js.map +1 -0
  316. package/dist/tools/index.cjs +5 -1
  317. package/dist/tools/index.cjs.map +1 -1
  318. package/dist/tools/index.d.ts +1 -1
  319. package/dist/tools/index.js +7 -3
  320. package/dist/tools/index.js.map +1 -1
  321. package/dist/tools/misc-tool-uis.cjs +2 -1
  322. package/dist/tools/misc-tool-uis.cjs.map +1 -1
  323. package/dist/tools/misc-tool-uis.js +2 -1
  324. package/dist/tools/misc-tool-uis.js.map +1 -1
  325. package/dist/tools/primitives.cjs +25 -2
  326. package/dist/tools/primitives.cjs.map +1 -1
  327. package/dist/tools/primitives.d.ts +9 -2
  328. package/dist/tools/primitives.js +26 -3
  329. package/dist/tools/primitives.js.map +1 -1
  330. package/dist/tools/tool-appearance.cjs +11 -0
  331. package/dist/tools/tool-appearance.cjs.map +1 -0
  332. package/dist/tools/tool-appearance.d.ts +29 -0
  333. package/dist/tools/tool-appearance.js +11 -0
  334. package/dist/tools/tool-appearance.js.map +1 -0
  335. package/dist/tools/tool-fallback.cjs +211 -13
  336. package/dist/tools/tool-fallback.cjs.map +1 -1
  337. package/dist/tools/tool-fallback.d.ts +31 -4
  338. package/dist/tools/tool-fallback.js +214 -16
  339. package/dist/tools/tool-fallback.js.map +1 -1
  340. package/dist/tools/use-elapsed-seconds.cjs +24 -0
  341. package/dist/tools/use-elapsed-seconds.cjs.map +1 -0
  342. package/dist/tools/use-elapsed-seconds.d.ts +10 -0
  343. package/dist/tools/use-elapsed-seconds.js +24 -0
  344. package/dist/tools/use-elapsed-seconds.js.map +1 -0
  345. package/dist/tools/voice-update-tool-ui.cjs +2 -1
  346. package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
  347. package/dist/tools/voice-update-tool-ui.js +2 -1
  348. package/dist/tools/voice-update-tool-ui.js.map +1 -1
  349. package/package.json +1 -1
  350. package/src/chat-ui/tailwind-preset.js +14 -1
@@ -1 +1 @@
1
- {"version":3,"file":"message-window.js","sources":["../../../../../agent-runtime/src/message-window.ts"],"sourcesContent":["import {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n DEEP_AGENT_HISTORY_LOAD_COMMAND,\n DEEP_AGENT_HISTORY_LOAD_MAX_LIMIT,\n DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_MESSAGE_WINDOW_HEADER,\n DEEP_AGENT_MESSAGES_WINDOW_STATE_KEY,\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n DEEP_AGENT_PROVENANCE_FULL,\n DEEP_AGENT_PROVENANCE_HEADER,\n DEEP_AGENT_TRUNCATED_CONTENT_KEY,\n} from './generated/runconfig-contract';\n\n/**\n * The windowed hello — the client half of agora's\n * `agora/graphs/athena_deep_agent/message_window.py`.\n *\n * A surface that declares a message window on its attach headers\n * ({@link withDeclaredMessageWindow}) receives a hello carrying only the last\n * N messages plus the `messagesWindow` lane, and its op stream re-addressed\n * to that window by the host — so `state.messages` is always exactly\n * `server.messages[start:]` and the ordinary converter renders it unchanged.\n * Older ranges arrive through `history/load` ({@link buildHistoryLoadCommand})\n * as ops for this client alone; a truncated tool output comes back whole\n * through `history/tool_output` ({@link buildToolOutputCommand}).\n *\n * Undeclared surfaces (no header) are untouched: full snapshot, no lane.\n * Skew-tolerant both ways — an old server ignores the header and advertises\n * no lane (read as \"everything is loaded\"), a malformed lane reads as absent.\n */\nexport interface DeepAgentMessagesWindow {\n /** Index in the server transcript of the client's first loaded message. */\n start: number;\n /** Server transcript length. */\n total: number;\n /** Whether older messages exist that the client has not loaded (`start > 0`). */\n truncated: boolean;\n /** The window size the host clamped the declaration to. */\n size: number;\n /**\n * Id of the message just before the window (`null` at the beginning): the\n * host parent of the first loaded message, so an edit or reload at the\n * window edge can assert its `runAnchorMessageId` without loading first.\n */\n anchorBefore: string | null;\n}\n\nconst isIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\n/**\n * The window lane off a raw statewire state, or `null` when the wire is not\n * windowed (an undeclared attach, a pre-window server, pre-attach) or the lane\n * is malformed (tolerated as absent — never thrown on).\n */\nexport function readDeepAgentMessagesWindow(state: unknown): DeepAgentMessagesWindow | null {\n if (typeof state !== 'object' || state === null) return null;\n const lane = (state as Record<string, unknown>)[DEEP_AGENT_MESSAGES_WINDOW_STATE_KEY];\n if (typeof lane !== 'object' || lane === null) return null;\n const { start, total, truncated, size, anchorBefore } = lane as Record<string, unknown>;\n if (!isIndex(start) || !isIndex(total) || !isIndex(size) || typeof truncated !== 'boolean') {\n return null;\n }\n if (anchorBefore !== null && typeof anchorBefore !== 'string') return null;\n return { start, total, truncated, size, anchorBefore };\n}\n\n/** How many messages older than the loaded window the server still holds. */\nexport function olderMessageCount(window: DeepAgentMessagesWindow | null): number {\n return window === null ? 0 : window.start;\n}\n\n/** What a surface declares on its attach: a window size and the provenance mode. */\nexport interface DeepAgentMessageWindowDeclaration {\n /** Messages the hello should carry (the host clamps to its bounds). */\n size?: number;\n /** `'full'` keeps the model-provenance blobs on the wire; default strips them. */\n provenance?: 'full' | 'compact';\n}\n\nconst clampWindowSize = (size: number): number =>\n Math.max(\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.trunc(size))\n );\n\n/**\n * Attach headers with a message window declared. Applied by\n * `useDeepAgentThread` when a surface passes `messageWindow`; an explicit\n * entry (tests, forced overrides) wins.\n */\nexport function withDeclaredMessageWindow(\n headers: Record<string, string>,\n declaration: DeepAgentMessageWindowDeclaration = {}\n): Record<string, string> {\n const declared: Record<string, string> = { ...headers };\n if (!(DEEP_AGENT_MESSAGE_WINDOW_HEADER in declared)) {\n const size = declaration.size ?? DEEP_AGENT_DEFAULT_MESSAGE_WINDOW;\n declared[DEEP_AGENT_MESSAGE_WINDOW_HEADER] = String(\n Number.isFinite(size) ? clampWindowSize(size) : DEEP_AGENT_DEFAULT_MESSAGE_WINDOW\n );\n }\n if (declaration.provenance === 'full' && !(DEEP_AGENT_PROVENANCE_HEADER in declared)) {\n declared[DEEP_AGENT_PROVENANCE_HEADER] = DEEP_AGENT_PROVENANCE_FULL;\n }\n return declared;\n}\n\n/**\n * The windowed wire's read commands on the statewire command union, so\n * `sendCommand({ type: 'history/load', … })` typechecks wherever the deep-agent\n * host is driven (`DeepAgentThread.history_load` / `history_tool_output`, both\n * attach-ACL reads). The augmentation is part of this module so every surface\n * that imports the builders gets the typed commands with them.\n */\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** Prepend older messages to this client's windowed view. */\n historyLoad: HistoryLoadCommand;\n /** A tool output the windowed wire carried cut, whole. */\n historyToolOutput: ToolOutputCommand;\n }\n }\n}\n\n/** The `history/load` command: older messages prepended to this client's view. */\nexport interface HistoryLoadCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_LOAD_COMMAND;\n /** The `start` this load extends — the host answers `stale` for any other. */\n readonly beforeIndex: number;\n readonly limit: number;\n}\n\n/**\n * The next page of older history for `window`, or `null` when the beginning of\n * the transcript is already loaded. `limit` defaults to the window size and is\n * capped at the host's bound.\n */\nexport function buildHistoryLoadCommand({\n window,\n limit,\n}: {\n window: DeepAgentMessagesWindow;\n limit?: number;\n}): HistoryLoadCommand | null {\n if (window.start <= 0) return null;\n const requested = limit ?? window.size;\n return {\n type: DEEP_AGENT_HISTORY_LOAD_COMMAND,\n beforeIndex: window.start,\n limit: Math.max(1, Math.min(DEEP_AGENT_HISTORY_LOAD_MAX_LIMIT, Math.trunc(requested))),\n };\n}\n\n/** The host's answer to `history/load`. */\nexport interface HistoryLoadResult {\n start: number;\n total: number;\n loaded: number;\n /** The load named a `start` the client no longer held; nothing was loaded. */\n stale: boolean;\n}\n\nexport function readHistoryLoadResult(payload: unknown): HistoryLoadResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { start, total, loaded, stale } = payload as Record<string, unknown>;\n if (!isIndex(start) || !isIndex(total) || !isIndex(loaded)) return null;\n return { start, total, loaded, stale: stale === true };\n}\n\n/** The `history/tool_output` command: a truncated tool output, whole. */\nexport interface ToolOutputCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildToolOutputCommand({ messageId }: { messageId: string }): ToolOutputCommand {\n return { type: DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, messageId };\n}\n\nexport interface ToolOutputResult {\n messageId: string;\n content: unknown;\n}\n\nexport function readToolOutputResult(payload: unknown): ToolOutputResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, content } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n return { messageId, content };\n}\n\n/** The wire marker on a tool message whose content the host cut. */\nexport interface TruncatedToolContent {\n /** Length of the full output, in characters. */\n chars: number;\n}\n\n/**\n * The truncation marker off a raw wire message, or `null` when the message's\n * content is whole. Pure and selector-safe on the marker object.\n */\nexport function readTruncatedToolContent(message: unknown): TruncatedToolContent | null {\n if (typeof message !== 'object' || message === null) return null;\n const marker = (message as Record<string, unknown>)[DEEP_AGENT_TRUNCATED_CONTENT_KEY];\n if (typeof marker !== 'object' || marker === null) return null;\n const chars = (marker as Record<string, unknown>).chars;\n return isIndex(chars) ? { chars } : null;\n}\n\n/**\n * `artifact` key the shared converter lifts a truncated tool output's marker\n * into (with the tool message id), so a tool card can ask for the full output\n * — the converter joins the ToolMessage into its tool-call part and would\n * otherwise drop the wire marker.\n */\nexport const ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY = '_athena_content_truncated';\n\nexport interface TruncatedToolOutputArtifact extends TruncatedToolContent {\n /** The tool message to name in `history/tool_output`. */\n messageId: string;\n}\n\n/**\n * The lifted truncation marker off a tool-call part's `artifact`, or `null`.\n */\nexport function readTruncatedToolOutputArtifact(\n artifact: unknown\n): TruncatedToolOutputArtifact | null {\n if (typeof artifact !== 'object' || artifact === null) return null;\n const lifted = (artifact as Record<string, unknown>)[ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY];\n if (typeof lifted !== 'object' || lifted === null) return null;\n const { chars, messageId } = lifted as Record<string, unknown>;\n if (!isIndex(chars) || typeof messageId !== 'string' || !messageId) return null;\n return { chars, messageId };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"message-window.js","sources":["../../../../../agent-runtime/src/message-window.ts"],"sourcesContent":["import type { LangChainMessage } from '@assistant-ui/react-langgraph';\nimport type { StatewireDocuments } from 'statewire';\nimport {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n DEEP_AGENT_HISTORY_MESSAGE_COMMAND,\n DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND,\n DEEP_AGENT_INTEREST_PROTOCOL,\n DEEP_AGENT_MAIN_NS,\n DEEP_AGENT_MAX_INTERESTS,\n DEEP_AGENT_MAX_MESSAGE_WINDOW,\n DEEP_AGENT_MESSAGE_WINDOW_HEADER,\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n DEEP_AGENT_TRUNCATED_CONTENT_KEY,\n} from './generated/runconfig-contract';\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from './statewire-transport';\n\n/**\n * The interest window — the client half of agora's\n * `agora/graphs/athena_deep_agent/message_window.py`.\n *\n * The wire main document carries no `messages`. A client states its interests\n * on the `athena/interest` lane as one array (`set(requests)`): the first is\n * the head (the newest `before` messages of the `main` namespace), the rest\n * are pages anchored `at` the first message of the window they extend. The\n * host holds one ephemeral main document per attach, `{ windows }`, one\n * resolution per request in order (the request echoed plus the covered\n * `chain` and `more` flags; an anchor that vanished leaves an empty window in\n * place), and one shared `athena` document per covered message, keyed\n * `[threadId, \"main\", messageId]`. {@link readDeepAgentMessageWindow} joins\n * them into the transcript the converter renders.\n *\n * Two host rules shape the client half. The head window never drops below\n * the oldest message it has carried on an attach (a run's appends grow the\n * view, nothing loaded slides out), so a page stays anchored at the head's\n * first message while the transcript grows; and the host remembers a client\n * id's interests across its attaches, so a reconnect's hello carries the\n * view the client held. A fresh client's hello carries the head it declared\n * on the attach headers ({@link withDeclaredMessageWindow}) — a statement\n * cannot ride the first attach, the hello negotiates the lanes.\n *\n * Pages are appended one per load until the host's interest cap, then the\n * loaded range is restated in as few maximal requests as it takes\n * ({@link pageInterestRequests}); the whole transcript stays reachable up to\n * `MAX_INTERESTS × MAX_MESSAGE_WINDOW` messages. A truncated tool output\n * comes back whole through `history/tool_output` ({@link buildToolOutputCommand});\n * a message's stored form — provenance blobs and all — through\n * `history/message` ({@link buildHistoryMessageCommand}).\n */\n\nconst isIndex = (value: unknown): value is number =>\n typeof value === 'number' && Number.isInteger(value) && value >= 0;\n\n/** What a surface declares for its window: the interest size (the host clamps to its bounds). */\nexport interface DeepAgentMessageWindowDeclaration {\n size?: number;\n}\n\nexport function clampMessageWindowSize(size?: number): number {\n if (size === undefined || !Number.isFinite(size)) return DEEP_AGENT_DEFAULT_MESSAGE_WINDOW;\n return Math.max(\n DEEP_AGENT_MIN_MESSAGE_WINDOW,\n Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, Math.trunc(size))\n );\n}\n\n/** An `athena/interest` request over the linear transcript. */\nexport interface DeepAgentInterestRequest {\n readonly ns: typeof DEEP_AGENT_MAIN_NS;\n /** Messages ending at the anchor (the tail without `at`). */\n readonly before: number;\n /** Anchor message id; a page's is the first message of the window it extends. */\n readonly at?: string;\n /** Messages past the anchor (requires `at`). */\n readonly after?: number;\n}\n\nexport function headInterestRequest(size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size) };\n}\n\nexport function pageInterestRequest(at: string, size?: number): DeepAgentInterestRequest {\n return { ns: DEEP_AGENT_MAIN_NS, before: clampMessageWindowSize(size), at };\n}\n\n/**\n * Attach headers with the head window declared (`x-athena-deep-agent-window`):\n * the hello of a client the host does not remember carries the newest `size`\n * messages. Part of the one attach recipe, so a pooled dial asks for the shape\n * the thread's own attach would. An explicit entry (tests, overrides) wins.\n */\nexport function withDeclaredMessageWindow(\n headers: Record<string, string>,\n declaration: DeepAgentMessageWindowDeclaration = {}\n): Record<string, string> {\n if (DEEP_AGENT_MESSAGE_WINDOW_HEADER in headers) return headers;\n return {\n ...headers,\n [DEEP_AGENT_MESSAGE_WINDOW_HEADER]: String(clampMessageWindowSize(declaration.size)),\n };\n}\n\nexport interface DeepAgentInterestChainEntry {\n id: string;\n siblings: string[];\n}\n\n/** The host's resolution document of one interest: the request echoed plus its coverage. */\nexport interface DeepAgentInterestResolution extends DeepAgentInterestRequest {\n /** Covered message ids, oldest first (a page ends at its anchor). */\n chain: DeepAgentInterestChainEntry[];\n more: { before: boolean; after: boolean };\n}\n\nexport function readDeepAgentInterestResolution(value: unknown): DeepAgentInterestResolution | null {\n if (typeof value !== 'object' || value === null) return null;\n const { ns, before, at, after, chain, more } = value as Record<string, unknown>;\n if (ns !== DEEP_AGENT_MAIN_NS || !isIndex(before) || !Array.isArray(chain)) return null;\n if (at !== undefined && typeof at !== 'string') return null;\n if (after !== undefined && !isIndex(after)) return null;\n if (typeof more !== 'object' || more === null) return null;\n const { before: moreBefore, after: moreAfter } = more as Record<string, unknown>;\n if (typeof moreBefore !== 'boolean' || typeof moreAfter !== 'boolean') return null;\n const entries: DeepAgentInterestChainEntry[] = [];\n for (const entry of chain) {\n const { id, siblings } = (entry ?? {}) as Record<string, unknown>;\n if (typeof id !== 'string') return null;\n entries.push({\n id,\n siblings: Array.isArray(siblings) ? siblings.filter((s) => typeof s === 'string') : [id],\n });\n }\n return {\n ns: DEEP_AGENT_MAIN_NS,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n chain: entries,\n more: { before: moreBefore, after: moreAfter },\n };\n}\n\n/** The attach's interest document: one resolution per stated request, in order. */\nexport interface DeepAgentInterestDocument {\n windows: DeepAgentInterestResolution[];\n}\n\nexport function readDeepAgentInterestDocument(value: unknown): DeepAgentInterestDocument | null {\n if (typeof value !== 'object' || value === null) return null;\n const { windows } = value as Record<string, unknown>;\n if (!Array.isArray(windows)) return null;\n const resolutions: DeepAgentInterestResolution[] = [];\n for (const window of windows) {\n const resolution = readDeepAgentInterestResolution(window);\n if (resolution === null) return null;\n resolutions.push(resolution);\n }\n return { windows: resolutions };\n}\n\nconst interestRequest = ({ ns, before, at, after }: DeepAgentInterestRequest): DeepAgentInterestRequest => ({\n ns,\n before,\n ...(at !== undefined && { at }),\n ...(after !== undefined && { after }),\n});\n\n/** The loaded transcript window, joined from the head interest and its anchored pages. */\nexport interface DeepAgentMessageWindow {\n /** The requests the joined view stands on, in order (the head first): what a `set` restates. A stale page (its anchor gone) or one the head already covers is left out. */\n requests: DeepAgentInterestRequest[];\n /** Covered chain, oldest first. */\n chain: DeepAgentInterestChainEntry[];\n /** The chain's message documents, in chain order (an unmounted one is skipped). */\n messages: LangChainMessage[];\n /** Whether the host holds messages older than the window. */\n hasOlder: boolean;\n /** Pages joined onto the head. */\n pages: number;\n}\n\n/**\n * The loaded chain plus `grow` older messages, restated in as few requests as\n * it takes: a maximal head, then maximal pages each anchored at the first\n * message of the window above it. `null` once even that exceeds the host's\n * interest cap — the reachable depth is `MAX_INTERESTS × MAX_MESSAGE_WINDOW`.\n */\nfunction coalescedInterestRequests(\n chain: readonly DeepAgentInterestChainEntry[],\n grow: number\n): DeepAgentInterestRequest[] | null {\n const loaded = chain.length;\n const requests: DeepAgentInterestRequest[] = [\n { ns: DEEP_AGENT_MAIN_NS, before: Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, loaded) },\n ];\n // `low` is the chain index of the lowest message the plan covers so far.\n let low = loaded - (requests[0] as DeepAgentInterestRequest).before;\n while (low > 0) {\n const before = Math.min(DEEP_AGENT_MAX_MESSAGE_WINDOW, low + 1);\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before,\n at: (chain[low] as DeepAgentInterestChainEntry).id,\n });\n low = low + 1 - before;\n }\n // The plan covers the chain; the new messages extend its lowest request\n // when that stays within one window, else they are a page of their own.\n const last = requests[requests.length - 1] as DeepAgentInterestRequest;\n if (last.before + grow <= DEEP_AGENT_MAX_MESSAGE_WINDOW) {\n requests[requests.length - 1] = { ...last, before: last.before + grow };\n } else {\n requests.push({\n ns: DEEP_AGENT_MAIN_NS,\n before: grow + 1,\n at: (chain[0] as DeepAgentInterestChainEntry).id,\n });\n }\n return requests.length > DEEP_AGENT_MAX_INTERESTS ? null : requests;\n}\n\n/**\n * The requests to state for one more page of older history, or `null` when\n * there is nothing to load (no anchor, the beginning reached) or the host's\n * interest cap is spent. One page is appended, anchored at the window's first\n * message, while the cap allows; at the cap the loaded range is coalesced\n * into maximal requests first (see {@link coalescedInterestRequests}).\n */\nexport function pageInterestRequests(\n window: DeepAgentMessageWindow,\n size?: number\n): DeepAgentInterestRequest[] | null {\n const anchor = window.chain[0]?.id;\n if (anchor === undefined || !window.hasOlder) return null;\n const page = pageInterestRequest(anchor, size);\n const appended = [...window.requests, page];\n if (appended.length <= DEEP_AGENT_MAX_INTERESTS) return appended;\n // A page's `before` counts its anchor: it adds `before - 1` older messages.\n return coalescedInterestRequests(window.chain, page.before - 1);\n}\n\n/**\n * Whether every `stated` request is carried by `carried` (the same anchor, at\n * least as wide): a reconnect's hello that carries what the client last\n * stated — the host remembers a client id's interests — needs no restatement.\n */\nexport function interestsCovered(\n carried: readonly DeepAgentInterestRequest[],\n stated: readonly DeepAgentInterestRequest[]\n): boolean {\n return stated.every((request) =>\n carried.some((have) => have.at === request.at && have.before >= request.before)\n );\n}\n\n/**\n * The window off the mounted documents, or `null` while the head interest has\n * not resolved (pre-attach, before the first `set` answer, a wire without\n * documents). A page joins wherever its anchor sits in the chain so far — at\n * the first message it extends the view, inside the head (the head widened\n * past it) it adds nothing and is left out of `requests`; a page whose anchor\n * left the transcript (a rewind emptied it in place) is left out too. A\n * malformed document reads as absent.\n */\nexport function readDeepAgentMessageWindow(\n mounted: ReadonlyMap<number, StatewireDocuments.Document> | undefined\n): DeepAgentMessageWindow | null {\n if (mounted === undefined) return null;\n let windows: DeepAgentInterestResolution[] | undefined;\n const documents = new Map<string, LangChainMessage>();\n for (const document of mounted.values()) {\n const key = document.key;\n if (document.protocol === DEEP_AGENT_INTEREST_PROTOCOL && document.main === true) {\n windows = readDeepAgentInterestDocument(document.value)?.windows;\n } else if (\n document.protocol === DEEP_AGENT_STATEWIRE_PROTOCOL &&\n key !== null &&\n key.length === 3 &&\n key[1] === DEEP_AGENT_MAIN_NS\n ) {\n documents.set(key[2] as string, document.value as LangChainMessage);\n }\n }\n const head = windows?.[0];\n if (windows === undefined || head === undefined) return null;\n let chain = head.chain;\n let hasOlder = head.more.before;\n const requests = [interestRequest(head)];\n const loaded = new Set(chain.map((entry) => entry.id));\n let pages = 0;\n for (const page of windows.slice(1)) {\n if (page.at === undefined || !loaded.has(page.at)) continue;\n const anchorAt = page.chain.findIndex((entry) => entry.id === page.at);\n if (anchorAt < 0) continue;\n // Both windows are ranges of one linear transcript sharing the anchor:\n // what the page holds below it and the chain does not is a prefix.\n const fresh = page.chain.slice(0, anchorAt).filter((entry) => !loaded.has(entry.id));\n if (fresh.length === 0) {\n // Nothing new, but a page that starts where the loaded chain starts\n // has looked below it: its `more.before` is the answer for the edge.\n if (page.chain[0]?.id === chain[0]?.id) hasOlder = page.more.before;\n continue;\n }\n chain = [...fresh, ...chain];\n for (const entry of fresh) loaded.add(entry.id);\n hasOlder = page.more.before;\n requests.push(interestRequest(page));\n pages += 1;\n }\n const messages: LangChainMessage[] = [];\n for (const entry of chain) {\n const message = documents.get(entry.id);\n if (message !== undefined) messages.push(message);\n }\n return { requests, chain, messages, hasOlder, pages };\n}\n\n/**\n * The windowed wire's read command on the statewire command union, so\n * `sendCommand({ type: 'history/tool_output', … })` typechecks wherever the\n * deep-agent host is driven (`DeepAgentThread.history_tool_output`, an\n * attach-ACL read).\n */\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** A tool output the windowed wire carried cut, whole. */\n historyToolOutput: ToolOutputCommand;\n /** One stored message whole — the per-client shape, by command. */\n historyMessage: HistoryMessageCommand;\n }\n }\n}\n\n/** The `history/tool_output` command: a truncated tool output, whole. */\nexport interface ToolOutputCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildToolOutputCommand({ messageId }: { messageId: string }): ToolOutputCommand {\n return { type: DEEP_AGENT_HISTORY_TOOL_OUTPUT_COMMAND, messageId };\n}\n\nexport interface ToolOutputResult {\n messageId: string;\n content: unknown;\n}\n\nexport function readToolOutputResult(payload: unknown): ToolOutputResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, content } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n return { messageId, content };\n}\n\n/**\n * The `history/message` command: a message as the host stores it — the\n * model-provenance blobs the wire strips and an untruncated tool output\n * included. Every client receives one projected wire form; this is the one\n * per-client shape, fetched on demand like `history/tool_output`.\n */\nexport interface HistoryMessageCommand {\n readonly type: typeof DEEP_AGENT_HISTORY_MESSAGE_COMMAND;\n readonly messageId: string;\n}\n\nexport function buildHistoryMessageCommand({\n messageId,\n}: {\n messageId: string;\n}): HistoryMessageCommand {\n return { type: DEEP_AGENT_HISTORY_MESSAGE_COMMAND, messageId };\n}\n\nexport interface HistoryMessageResult {\n messageId: string;\n message: LangChainMessage;\n}\n\nexport function readHistoryMessageResult(payload: unknown): HistoryMessageResult | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const { messageId, message } = payload as Record<string, unknown>;\n if (typeof messageId !== 'string' || !messageId) return null;\n if (typeof message !== 'object' || message === null) return null;\n return { messageId, message: message as LangChainMessage };\n}\n\n/** The wire marker on a tool message whose content the host cut. */\nexport interface TruncatedToolContent {\n /** Length of the full output, in characters. */\n chars: number;\n}\n\n/**\n * The truncation marker off a raw wire message, or `null` when the message's\n * content is whole. Pure and selector-safe on the marker object.\n */\nexport function readTruncatedToolContent(message: unknown): TruncatedToolContent | null {\n if (typeof message !== 'object' || message === null) return null;\n const marker = (message as Record<string, unknown>)[DEEP_AGENT_TRUNCATED_CONTENT_KEY];\n if (typeof marker !== 'object' || marker === null) return null;\n const chars = (marker as Record<string, unknown>).chars;\n return isIndex(chars) ? { chars } : null;\n}\n\n/**\n * `artifact` key the shared converter lifts a truncated tool output's marker\n * into (with the tool message id), so a tool card can ask for the full output\n * — the converter joins the ToolMessage into its tool-call part and would\n * otherwise drop the wire marker.\n */\nexport const ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY = '_athena_content_truncated';\n\nexport interface TruncatedToolOutputArtifact extends TruncatedToolContent {\n /** The tool message to name in `history/tool_output`. */\n messageId: string;\n}\n\n/**\n * The lifted truncation marker off a tool-call part's `artifact`, or `null`.\n */\nexport function readTruncatedToolOutputArtifact(\n artifact: unknown\n): TruncatedToolOutputArtifact | null {\n if (typeof artifact !== 'object' || artifact === null) return null;\n const lifted = (artifact as Record<string, unknown>)[ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY];\n if (typeof lifted !== 'object' || lifted === null) return null;\n const { chars, messageId } = lifted as Record<string, unknown>;\n if (!isIndex(chars) || typeof messageId !== 'string' || !messageId) return null;\n return { chars, messageId };\n}\n"],"names":[],"mappings":"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"}
@@ -3,12 +3,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const tap = require("@assistant-ui/tap");
4
4
  const storage = require("statewire/storage");
5
5
  const react = require("react");
6
+ const statewireTransport = require("./statewire-transport.cjs");
6
7
  const SESSION_PREFIX = "aui:statewire-session:";
7
8
  const SESSION_LOCK_PREFIX = "aui:statewire-session-lock:";
8
9
  const SESSION_ACTIVE_PREFIX = "aui:statewire-session-active:";
9
- const LEGACY_PROTOCOL = { name: "athena", version: "2026-09-07" };
10
10
  const DAY_MS = 24 * 60 * 60 * 1e3;
11
- const READABLE_STORAGE_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
11
+ const READABLE_STORAGE_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3]);
12
12
  const browserGlobals = globalThis;
13
13
  function readPersistedFailedSendStamps(sessionKey) {
14
14
  const storage2 = browserGlobals.localStorage;
@@ -26,12 +26,16 @@ function readFailedSendStampsFromRecord(record) {
26
26
  var _a, _b, _c;
27
27
  if (!record || typeof record !== "object") return null;
28
28
  const stamps = /* @__PURE__ */ new Map();
29
- if (!Array.isArray(record.failed)) return stamps;
30
- for (const entry of record.failed) {
31
- const failed = entry;
32
- const messageId = (_c = (_b = (_a = failed == null ? void 0 : failed.params) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) == null ? void 0 : _c.id;
33
- if (typeof messageId === "string" && typeof (failed == null ? void 0 : failed.ts) === "number") {
34
- stamps.set(messageId, failed.ts);
29
+ const lanes = record.lanes && typeof record.lanes === "object" ? record.lanes : {};
30
+ const failed = [
31
+ ...Array.isArray(record.failed) ? record.failed : [],
32
+ ...Object.values(lanes).flatMap((lane) => Array.isArray(lane == null ? void 0 : lane.failed) ? lane.failed : [])
33
+ ];
34
+ for (const entry of failed) {
35
+ const failed2 = entry;
36
+ const messageId = (_c = (_b = (_a = failed2 == null ? void 0 : failed2.params) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) == null ? void 0 : _c.id;
37
+ if (typeof messageId === "string" && typeof (failed2 == null ? void 0 : failed2.ts) === "number") {
38
+ stamps.set(messageId, failed2.ts);
35
39
  }
36
40
  }
37
41
  return stamps;
@@ -280,7 +284,7 @@ const useQuotaSafeLocalStorageSession = ({
280
284
  backend: shared.backend,
281
285
  key: sessionKey,
282
286
  namespace: SESSION_PREFIX,
283
- legacyProtocol: LEGACY_PROTOCOL,
287
+ protocol: statewireTransport.DEEP_AGENT_STATEWIRE_PROTOCOL,
284
288
  retention: { maxAgeMs: ttl, maxEntries: max },
285
289
  // A failure statewire raises itself (a structurally invalid stored
286
290
  // record) still stops the session; it is at least reported the same way.
@@ -1 +1 @@
1
- {"version":3,"file":"quota-safe-local-storage-session.cjs","sources":["../../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst LEGACY_PROTOCOL = { name: \"athena\", version: \"2026-09-07\" };\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 2). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string,\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: { failed?: unknown } | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record =\n raw === null ? null : (JSON.parse(raw) as { failed?: unknown } | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: { failed?: unknown } | null | undefined,\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n if (!Array.isArray(record.failed)) return stamps;\n for (const entry of record.failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\";\n key: string;\n error: unknown;\n};\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message,\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\",\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`,\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index),\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n },\n );\n },\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's legacy protocol and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\",\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\",\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error,\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n },\n ),\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error }),\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n legacyProtocol: LEGACY_PROTOCOL,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n }),\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation],\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession,\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["storage","useRef","useMemo","useEffect","useResource","KeyValueSessionStorage","_a","resource"],"mappings":";;;;;AAKA,MAAM,iBAAiB;AACvB,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,kBAAkB,EAAE,MAAM,UAAU,SAAS,aAAA;AACnD,MAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,MAAM,4BAAkD,oBAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AA0CtE,MAAM,iBAAiB;AAEhB,SAAS,8BACd,YACoC;AACpC,QAAMA,WAAU,eAAe;AAC/B,MAAI,CAACA,SAAS,QAAO;AACrB,MAAI;AACJ,MAAI;AACF,UAAM,MAAMA,SAAQ,QAAQ,iBAAiB,UAAU;AACvD,aACE,QAAQ,OAAO,OAAQ,KAAK,MAAM,GAAG;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,SAAO,+BAA+B,MAAM;AAC9C;AAYO,SAAS,+BACd,QACoC;;AACpC,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,6BAAa,IAAA;AACnB,MAAI,CAAC,MAAM,QAAQ,OAAO,MAAM,EAAG,QAAO;AAC1C,aAAW,SAAS,OAAO,QAAQ;AACjC,UAAM,SAAS;AAIf,UAAM,aAAY,kDAAQ,WAAR,mBAAiB,OAAjB,mBAAqB,YAArB,mBAA8B;AAChD,QAAI,OAAO,cAAc,YAAY,QAAO,iCAAQ,QAAO,UAAU;AACnE,aAAO,IAAI,WAAW,OAAO,EAAE;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAiCO,SAAS,oBAAoB,OAAyB;AAC3D,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,EAAE,MAAM,MAAM,QAAA,IAAY;AAKhC,SACE,SAAS,wBACT,SAAS,gCACT,SAAS,MACT,SAAS,QACR,OAAO,YAAY,YAClB,oEAAoE;AAAA,IAClE;AAAA,EAAA;AAGR;AAGA,SAAS,sBAAsB,OAAuB;AACpD,MAAI,iBAAiB,SAAS,MAAM,SAAS;AAC3C,WAAO;AACT,QAAM,aAAa,IAAI;AAAA,IACrB,iBAAiB,QAAQ,MAAM,UAAU;AAAA,EAAA;AAE3C,aAAW,OAAO;AAClB,SAAO;AACT;AAYA,SAAS,iBAAiB,KAAgC;AACxD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,WAAO,iBAAiB,QACpB,QACA,IAAI,MAAM,mCAAmC;AAAA,EACnD;AACA,MAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,WAAO,IAAI,MAAM,wCAAwC;AAAA,EAC3D;AACA,QAAM,UAAW,OACd;AACH,MAAI,YAAY,UAAa,CAAC,0BAA0B,IAAI,OAAO,GAAG;AACpE,WAAO,IAAI;AAAA,MACT,8CAA8C,OAAO,OAAO,CAAC;AAAA,IAAA;AAAA,EAEjE;AACA,SAAO;AACT;AAgBA,SAAS,eAAe;AAAA,EACtB,SAAAA;AAAA,EACA;AACF,GAGgB;AACd,SAAO;AAAA,IACL,SAAS,CAAC,QAAQA,SAAQ,QAAQ,GAAG;AAAA,IACrC,SAAS,CAAC,KAAK,UAAUA,SAAQ,QAAQ,KAAK,KAAK;AAAA,IACnD,YAAY,CAAC,QAAQA,SAAQ,WAAW,GAAG;AAAA,IAC3C,YAAY,MACV,MAAM;AAAA,MAAK,EAAE,QAAQA,SAAQ,OAAA;AAAA,MAAU,CAAC,GAAG,UACzCA,SAAQ,IAAI,KAAK;AAAA,IAAA,EACjB,OAAO,CAAC,QAAuB,QAAQ,IAAI;AAAA,IAC/C,YAAY,OAAO,KAAK,WAAW;AACjC,YAAM,MAAM;AAAA,QACV,wBAAwB;AAAA,QACxB,EAAE,aAAa,KAAA;AAAA,QACf,OAAO,WAAW;AAChB,cAAI,WAAW,KAAM;AACrB,gBAAM,MAAM;AAAA,YACV,sBAAsB,IAAI,MAAM,eAAe,MAAM;AAAA,YACrD,EAAE,aAAa,KAAA;AAAA,YACf,OAAO,SAAS;AACd,kBAAI,SAAS,KAAM;AACnB,oBAAM,OAAA;AAAA,YACR;AAAA,UAAA;AAAA,QAEJ;AAAA,MAAA;AAAA,IAEJ;AAAA,EAAA;AAEJ;AA4BO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGmC;AAKjC,QAAM,oCAAoB,IAAA;AAE1B,QAAM,8BAAc,IAAA;AAEpB,QAAM,+BAAe,IAAA;AAIrB,QAAM,8BAAc,IAAA;AAIpB,QAAM,WAAW,CAAC,KAAa,QAAmC;AAChE,UAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,QAAI,aAAa,UAAa,SAAS,QAAQ,YAAY,SAAS;AACpE,UAAM,UAAU,iBAAiB,GAAG;AACpC,YAAQ,IAAI,KAAK,EAAE,KAAK,SAAS;AACjC,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,CAAC,QAAQ;AAChB,YAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,UAAI,aAAa,OAAW,QAAO;AACnC,UAAI,SAAS,IAAI,GAAG,EAAG,QAAO;AAC9B,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,QAAQ,GAAG;AAAA,MACzB,SAAS,OAAO;AACd,iBAAS,IAAI,GAAG;AAChB,mBAAW,EAAE,WAAW,WAAW,KAAK,OAAO;AAC/C,eAAO;AAAA,MACT;AACA,UAAI,QAAQ,KAAM,QAAO;AACzB,YAAM,aAAa,SAAS,KAAK,GAAG;AACpC,UAAI,eAAe,OAAW,QAAO;AACrC,eAAS,IAAI,GAAG;AAChB,iBAAW,EAAE,WAAW,UAAU,KAAK,OAAO,YAAY;AAC1D,aAAO;AAAA,IACT;AAAA,IACA,SAAS,CAAC,KAAK,UAAU;AACvB,UAAI,SAAS,IAAI,GAAG,GAAG;AACrB,gBAAQ,IAAI,KAAK,KAAK;AACtB;AAAA,MACF;AACA,UAAI;AACF,cAAM,QAAQ,KAAK,KAAK;AACxB,gBAAQ,OAAO,GAAG;AAClB,sBAAc,OAAO,GAAG;AAAA,MAC1B,SAAS,OAAO;AACd,YAAI,oBAAoB,KAAK,KAAK,CAAC,cAAc,IAAI,GAAG,GAAG;AACzD,wBAAc,IAAI,GAAG;AACrB,gBAAM,sBAAsB,KAAK;AAAA,QACnC;AACA,gBAAQ,IAAI,KAAK,KAAK;AACtB,mBAAW,EAAE,WAAW,WAAW,KAAK,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,IACA,YAAY,CAAC,QAAQ;AACnB,cAAQ,OAAO,GAAG;AAClB,UAAI,SAAS,IAAI,GAAG,EAAG;AACvB,UAAI;AACF,cAAM,WAAW,GAAG;AAAA,MACtB,SAAS,OAAO;AACd,mBAAW,EAAE,WAAW,cAAc,KAAK,OAAO;AAAA,MACpD;AAAA,IACF;AAAA,IACA,YAAY,MAAM;AAChB,YAAM,OAAO,IAAI,IAAY,QAAQ,MAAM;AAC3C,UAAI;AACF,mBAAW,OAAO,MAAM,WAAA,EAAc,MAAK,IAAI,GAAG;AAAA,MACpD,SAAS,OAAO;AACd,mBAAW,EAAE,WAAW,cAAc,KAAK,IAAI,OAAO;AAAA,MACxD;AACA,aAAO,CAAC,GAAG,IAAI;AAAA,IACjB;AAAA,IACA,YAAY,OAAO,KAAK,WAAW;AACjC,UAAI;AACF,cAAM,MAAM,WAAW,KAAK,MAAM;AAAA,MACpC,SAAS,OAAO;AACd,mBAAW,EAAE,WAAW,cAAc,KAAK,OAAO;AAAA,MACpD;AAAA,IACF;AAAA,EAAA;AAEJ;AAmBA,MAAM,qCAAqB,QAAA;AAE3B,SAAS,cAAc;AAAA,EACrB,SAAAA;AAAA,EACA;AACF,GAGkB;AAChB,MAAI,SAAS,eAAe,IAAIA,QAAO;AACvC,MAAI,WAAW,QAAW;AACxB,UAAM,gCAAgB,IAAA;AACtB,aAAS;AAAA,MACP;AAAA,MACA,SAAS,qBAAqB;AAAA,QAC5B,OAAO,eAAe,EAAE,SAAAA,UAAS,OAAO;AAAA,QACxC,YAAY,CAAC,gBAAgB;AAC3B,qBAAW,YAAY,UAAW,UAAS,WAAW;AAAA,QACxD;AAAA,MAAA,CACD;AAAA,IAAA;AAEH,mBAAe,IAAIA,UAAS,MAAM;AAAA,EACpC;AACA,SAAO;AACT;AASA,MAAM,kCAAkC,CAAC;AAAA,EACvC;AAAA,EACA,MAAM,CAAC,OAAO;AAAA,EACd,MAAM,IAAI;AAAA,EACV,MAAM;AAAA,EACN;AACF,MAA4E;;AAC1E,QAAMA,YAAU,eAAe;AAC/B,QAAM,SAAQ,oBAAe,cAAf,mBAA0B;AACxC,MAAI,CAACA;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAEJ,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAEJ,QAAM,aAAa,IAAI,QAAQ;AAC/B,QAAM,aAAa,iBAAiB;AACpC,QAAM,WAAW,sBAAsB;AAEvC,QAAM,mBAAmBC,MAAAA,OAAO,iBAAiB;AACjD,mBAAiB,UAAU;AAC3B,QAAM,oBAAoBC,MAAAA,QAAQ,MAAM;AACtC,UAAM,+BAAe,IAAA;AACrB,WAAO,CAAC,gBAA2C;AACjD,YAAM,UAAU,iBAAiB;AACjC,UAAI,SAAS;AACX,YAAI;AACF,kBAAQ,WAAW;AAAA,QACrB,QAAQ;AAAA,QAGR;AACA;AAAA,MACF;AACA,YAAM,YAAY,GAAG,YAAY,SAAS,IAAI,YAAY,GAAG;AAC7D,UAAI,SAAS,IAAI,SAAS,EAAG;AAC7B,eAAS,IAAI,SAAS;AACtB,cAAQ;AAAA,QACN,wDAAwD,YAAY,SAAS,IAAI,YAAY,GAAG;AAAA,QAChG,YAAY;AAAA,MAAA;AAAA,IAEhB;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,QAAM,SAAS,cAAc,WAAEF,WAAS,OAAO;AAI/C,SAAO,UAAU,IAAI,iBAAiB;AACtCG,QAAAA,UAAU,MAAM;AACd,WAAO,UAAU,IAAI,iBAAiB;AACtC,WAAO,MAAM;AACX,aAAO,UAAU,OAAO,iBAAiB;AAAA,IAC3C;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAI9BA,QAAAA,UAAU,MAAM;AACd,QAAI;AACJ,QAAI,WAAW;AACf,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ,wBAAwB;AAAA,QACxB,EAAE,MAAM,SAAA;AAAA,QACR,MAAM;AACJ,cAAI,SAAU;AACd,iBAAO,IAAI,QAAc,CAAC,YAAY;AACpC,sBAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MAAA;AAAA,IACF,EACA;AAAA,MAAM,CAAC,UACP,kBAAkB,EAAE,WAAW,QAAQ,KAAK,YAAY,MAAA,CAAO;AAAA,IAAA;AAEjE,WAAO,MAAM;AACX,iBAAW;AACX;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,YAAY,iBAAiB,CAAC;AAEzC,QAAM,iBAAiBC,IAAAA;AAAAA,IACrBC,+BAAuB;AAAA,MACrB,SAAS,OAAO;AAAA,MAChB,KAAK;AAAA,MACL,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,WAAW,EAAE,UAAU,KAAK,YAAY,IAAA;AAAA;AAAA;AAAA,MAGxC,SAAS,CAAC,UACR,kBAAkB,EAAE,WAAW,WAAW,KAAK,YAAY,MAAA,CAAO;AAAA,IAAA,CACrE;AAAA,EAAA;AAGH,SAAOH,MAAAA;AAAAA,IACL,OAAO;AAAA,MACL,GAAG;AAAA,MACH,MAAM,OAAU,OAAyC;AACvD,YAAI,UAAU;AACd,YAAI;AACF,iBAAO,MAAM,MAAM,QAAQ,UAAU,YAAY;AAC/C,sBAAU;AACV,mBAAO,GAAA;AAAA,UACT,CAAC;AAAA,QACH,SAAS,OAAO;AACd,cAAI,QAAS,OAAM;AAInB,4BAAkB,EAAE,WAAW,QAAQ,KAAK,YAAY,OAAO;AAC/D,iBAAO,GAAA;AAAA,QACT;AAAA,MACF;AAAA,MACA,WAAW,CAAC,aAAyB;;AACnC,cAAM,YAAY,CAAC,UAA4B;AAC7C,cAAI,MAAM,QAAQ,WAAY,UAAA;AAAA,QAChC;AACA,SAAAI,MAAA,eAAe,qBAAf,gBAAAA,IAAA,qBAAkC,WAAW;AAC7C,eAAO;;AAAM,kBAAAA,MAAA,eAAe,wBAAf,gBAAAA,IAAA,qBAAqC,WAAW;AAAA;AAAA,MAC/D;AAAA,IAAA;AAAA,IAEF,CAAC,gBAAgB,OAAO,YAAY,UAAU,iBAAiB;AAAA,EAAA;AAEnE;AAGO,MAAM,+BAA+BC,IAAAA;AAAAA,EAC1C;AACF;;;;;;"}
1
+ {"version":3,"file":"quota-safe-local-storage-session.cjs","sources":["../../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from \"./statewire-transport\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 3). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2, 3]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\n/** A persisted session record: per-lane failed entries (a pre-lane record carried them at the root). */\ntype SessionRecordLike = {\n failed?: unknown;\n lanes?: Record<string, { failed?: unknown } | null | undefined> | null;\n};\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string,\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: SessionRecordLike | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record = raw === null ? null : (JSON.parse(raw) as SessionRecordLike | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: SessionRecordLike | null | undefined,\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n const lanes = record.lanes && typeof record.lanes === \"object\" ? record.lanes : {};\n const failed = [\n ...(Array.isArray(record.failed) ? record.failed : []),\n ...Object.values(lanes).flatMap((lane) => (Array.isArray(lane?.failed) ? lane.failed : [])),\n ];\n for (const entry of failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\";\n key: string;\n error: unknown;\n};\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message,\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\",\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`,\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index),\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n },\n );\n },\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's protocol lane and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\",\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\",\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error,\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n },\n ),\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error }),\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n }),\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation],\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession,\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["storage","failed","useRef","useMemo","useEffect","useResource","KeyValueSessionStorage","DEEP_AGENT_STATEWIRE_PROTOCOL","_a","resource"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { resource, useResource } from "@assistant-ui/tap";
2
2
  import { KeyValueSessionStorage } from "statewire/storage";
3
3
  import { useRef, useMemo, useEffect } from "react";
4
+ import { DEEP_AGENT_STATEWIRE_PROTOCOL } from "./statewire-transport.js";
4
5
  const SESSION_PREFIX = "aui:statewire-session:";
5
6
  const SESSION_LOCK_PREFIX = "aui:statewire-session-lock:";
6
7
  const SESSION_ACTIVE_PREFIX = "aui:statewire-session-active:";
7
- const LEGACY_PROTOCOL = { name: "athena", version: "2026-09-07" };
8
8
  const DAY_MS = 24 * 60 * 60 * 1e3;
9
- const READABLE_STORAGE_VERSIONS = /* @__PURE__ */ new Set([1, 2]);
9
+ const READABLE_STORAGE_VERSIONS = /* @__PURE__ */ new Set([1, 2, 3]);
10
10
  const browserGlobals = globalThis;
11
11
  function readPersistedFailedSendStamps(sessionKey) {
12
12
  const storage = browserGlobals.localStorage;
@@ -24,12 +24,16 @@ function readFailedSendStampsFromRecord(record) {
24
24
  var _a, _b, _c;
25
25
  if (!record || typeof record !== "object") return null;
26
26
  const stamps = /* @__PURE__ */ new Map();
27
- if (!Array.isArray(record.failed)) return stamps;
28
- for (const entry of record.failed) {
29
- const failed = entry;
30
- const messageId = (_c = (_b = (_a = failed == null ? void 0 : failed.params) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) == null ? void 0 : _c.id;
31
- if (typeof messageId === "string" && typeof (failed == null ? void 0 : failed.ts) === "number") {
32
- stamps.set(messageId, failed.ts);
27
+ const lanes = record.lanes && typeof record.lanes === "object" ? record.lanes : {};
28
+ const failed = [
29
+ ...Array.isArray(record.failed) ? record.failed : [],
30
+ ...Object.values(lanes).flatMap((lane) => Array.isArray(lane == null ? void 0 : lane.failed) ? lane.failed : [])
31
+ ];
32
+ for (const entry of failed) {
33
+ const failed2 = entry;
34
+ const messageId = (_c = (_b = (_a = failed2 == null ? void 0 : failed2.params) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) == null ? void 0 : _c.id;
35
+ if (typeof messageId === "string" && typeof (failed2 == null ? void 0 : failed2.ts) === "number") {
36
+ stamps.set(messageId, failed2.ts);
33
37
  }
34
38
  }
35
39
  return stamps;
@@ -278,7 +282,7 @@ const useQuotaSafeLocalStorageSession = ({
278
282
  backend: shared.backend,
279
283
  key: sessionKey,
280
284
  namespace: SESSION_PREFIX,
281
- legacyProtocol: LEGACY_PROTOCOL,
285
+ protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,
282
286
  retention: { maxAgeMs: ttl, maxEntries: max },
283
287
  // A failure statewire raises itself (a structurally invalid stored
284
288
  // record) still stops the session; it is at least reported the same way.
@@ -1 +1 @@
1
- {"version":3,"file":"quota-safe-local-storage-session.js","sources":["../../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useEffect, useMemo, useRef } from \"react\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst LEGACY_PROTOCOL = { name: \"athena\", version: \"2026-09-07\" };\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 2). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string,\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: { failed?: unknown } | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record =\n raw === null ? null : (JSON.parse(raw) as { failed?: unknown } | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: { failed?: unknown } | null | undefined,\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n if (!Array.isArray(record.failed)) return stamps;\n for (const entry of record.failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\";\n key: string;\n error: unknown;\n};\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message,\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\",\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`,\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index),\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n },\n );\n },\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's legacy protocol and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\",\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\",\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error,\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n },\n ),\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error }),\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n legacyProtocol: LEGACY_PROTOCOL,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n }),\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation],\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession,\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"quota-safe-local-storage-session.js","sources":["../../../../../agent-runtime/src/quota-safe-local-storage-session.ts"],"sourcesContent":["import { resource, useResource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"statewire\";\nimport { KeyValueSessionStorage } from \"statewire/storage\";\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { DEEP_AGENT_STATEWIRE_PROTOCOL } from \"./statewire-transport\";\n\nconst SESSION_PREFIX = \"aui:statewire-session:\";\nconst SESSION_LOCK_PREFIX = \"aui:statewire-session-lock:\";\nconst SESSION_ACTIVE_PREFIX = \"aui:statewire-session-active:\";\nconst DAY_MS = 24 * 60 * 60 * 1000;\n/** The stored-record formats the statewire this build ships reads (it writes 3). */\nconst READABLE_STORAGE_VERSIONS: ReadonlySet<unknown> = new Set([1, 2, 3]);\n\n/** Structural view of the browser storage global — this package compiles\n * without the DOM lib (React Native honesty; see `globals.d.ts`). */\ntype StorageLike = {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n key(index: number): string | null;\n readonly length: number;\n};\n\ntype LockGrant = object | null;\n\n/** The Web Locks API surface the session uses (`navigator.locks`). */\ntype LockManagerLike = {\n request<T>(\n name: string,\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n request<T>(\n name: string,\n options: { mode?: \"shared\" | \"exclusive\"; ifAvailable?: boolean },\n callback: (lock: LockGrant) => T | Promise<T>,\n ): Promise<T>;\n};\n\ntype StorageEventLike = { key: string | null };\n\ntype BrowserGlobals = {\n localStorage?: StorageLike;\n navigator?: { locks?: LockManagerLike };\n addEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n removeEventListener?: (\n type: \"storage\",\n listener: (event: StorageEventLike) => void,\n ) => void;\n};\n\nconst browserGlobals = globalThis as BrowserGlobals;\n\n/** A persisted session record: per-lane failed entries (a pre-lane record carried them at the root). */\ntype SessionRecordLike = {\n failed?: unknown;\n lanes?: Record<string, { failed?: unknown } | null | undefined> | null;\n};\n\nexport function readPersistedFailedSendStamps(\n sessionKey: string,\n): ReadonlyMap<string, number> | null {\n const storage = browserGlobals.localStorage;\n if (!storage) return null;\n let record: SessionRecordLike | null;\n try {\n const raw = storage.getItem(SESSION_PREFIX + sessionKey);\n record = raw === null ? null : (JSON.parse(raw) as SessionRecordLike | null);\n } catch {\n return null;\n }\n return readFailedSendStampsFromRecord(record);\n}\n\n/**\n * Send stamps (message id → epoch ms of the original send) off an already\n * loaded session record's failed lane — the storage-agnostic half of\n * {@link readPersistedFailedSendStamps}, for hosts whose session store is not\n * localStorage (the React Native AsyncStorage-backed store reads its record\n * synchronously through `SessionStorage.load()`).\n *\n * Returns `null` for no record (every failed entry then counts as fresh) and\n * an empty map for a record without failures.\n */\nexport function readFailedSendStampsFromRecord(\n record: SessionRecordLike | null | undefined,\n): ReadonlyMap<string, number> | null {\n if (!record || typeof record !== \"object\") return null;\n const stamps = new Map<string, number>();\n const lanes = record.lanes && typeof record.lanes === \"object\" ? record.lanes : {};\n const failed = [\n ...(Array.isArray(record.failed) ? record.failed : []),\n ...Object.values(lanes).flatMap((lane) => (Array.isArray(lane?.failed) ? lane.failed : [])),\n ];\n for (const entry of failed) {\n const failed = entry as {\n ts?: unknown;\n params?: readonly { message?: { id?: unknown } }[];\n } | null;\n const messageId = failed?.params?.[0]?.message?.id;\n if (typeof messageId === \"string\" && typeof failed?.ts === \"number\") {\n stamps.set(messageId, failed.ts);\n }\n }\n return stamps;\n}\n\n/** A storage operation the session survived by degrading instead of failing. */\nexport type SessionStorageDegradation = {\n operation:\n | \"getItem\"\n | \"setItem\"\n | \"removeItem\"\n | \"getAllKeys\"\n | \"withUnused\"\n /** The lock manager refused the transaction fence; it ran unfenced. */\n | \"lock\"\n /**\n * The stored record is one this build cannot read (not JSON, or a newer\n * storage version): served as absent, and the key is shielded from being\n * overwritten so the record survives this tab.\n */\n | \"record\"\n /** statewire's own storage failure (a structurally invalid stored record). */\n | \"session\";\n key: string;\n error: unknown;\n};\n\n/**\n * Whether `error` is the browser refusing a write for lack of room, under\n * every name the engines use — `QuotaExceededError` (Chromium, WebKit, current\n * Firefox), `NS_ERROR_DOM_QUOTA_REACHED` (older Firefox), the legacy\n * `DOMException` codes 22 (WebKit) and 1014 (Firefox) — or a plain error whose\n * message carries one of those (storage proxies and WebViews rewrap the\n * exception). Olympus's `isStorageQuotaError` recognises the same set; the two\n * stay in step.\n */\nexport function isStorageQuotaError(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) return false;\n const { name, code, message } = error as {\n name?: unknown;\n code?: unknown;\n message?: unknown;\n };\n return (\n name === \"QuotaExceededError\" ||\n name === \"NS_ERROR_DOM_QUOTA_REACHED\" ||\n code === 22 ||\n code === 1014 ||\n (typeof message === \"string\" &&\n /exceeded the quota|quotaexceedederror|ns_error_dom_quota_reached/i.test(\n message,\n ))\n );\n}\n\n/** The one quota shape statewire's retention sweep recognises. */\nfunction asStatewireQuotaError(error: unknown): Error {\n if (error instanceof Error && error.name === \"QuotaExceededError\")\n return error;\n const normalized = new Error(\n error instanceof Error ? error.message : \"storage quota exceeded\",\n );\n normalized.name = \"QuotaExceededError\";\n return normalized;\n}\n\n/**\n * Why `raw` is a stored record this build cannot read, or `undefined` when it\n * may be one. Mirrors the two decode failures statewire raises for a record it\n * cannot read — invalid JSON, and a `statewireStorageVersion` newer than the\n * ones this build's statewire decodes (a tab left open across a\n * statewire upgrade, reading the record a newer tab keeps) — because statewire\n * throws those out of the resource at mount instead of treating the record as\n * absent. A structurally invalid record of the right version still fails\n * inside statewire.\n */\nfunction unreadableRecord(raw: string): Error | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n return error instanceof Error\n ? error\n : new Error(\"stored session record is not JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null) {\n return new Error(\"stored session record is not an object\");\n }\n const version = (parsed as { statewireStorageVersion?: unknown })\n .statewireStorageVersion;\n if (version !== undefined && !READABLE_STORAGE_VERSIONS.has(version)) {\n return new Error(\n `stored session record uses storage version ${String(version)}`,\n );\n }\n return undefined;\n}\n\ntype SyncBackend = {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n getAllKeys: () => readonly string[];\n withUnused: (key: string, action: () => Promise<void>) => Promise<void>;\n};\n\n/**\n * The browser backend `@assistant-ui/react-statewire`'s `LocalStorageSession`\n * uses, reproduced here because that resource does not take a backend: raw\n * `localStorage`, with retention cleanup fenced by the same Web Locks so a\n * sweep never removes a record another tab is actively sharing.\n */\nfunction browserBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SyncBackend {\n return {\n getItem: (key) => storage.getItem(key),\n setItem: (key, value) => storage.setItem(key, value),\n removeItem: (key) => storage.removeItem(key),\n getAllKeys: () =>\n Array.from({ length: storage.length }, (_, index) =>\n storage.key(index),\n ).filter((key): key is string => key !== null),\n withUnused: async (key, action) => {\n await locks.request(\n SESSION_ACTIVE_PREFIX + key,\n { ifAvailable: true },\n async (active) => {\n if (active === null) return;\n await locks.request(\n SESSION_LOCK_PREFIX + key.slice(SESSION_PREFIX.length),\n { ifAvailable: true },\n async (lock) => {\n if (lock === null) return;\n await action();\n },\n );\n },\n );\n },\n };\n}\n\n/**\n * Wraps a synchronous key-value backend so a failing origin degrades the\n * session to memory-only persistence instead of stopping the connection.\n *\n * statewire's session storage latches the first unrecognised write failure\n * and from then on rejects every send (`stopped/storage-error`, \"session\n * storage failed\" on the connection chip). It only recovers a\n * `QuotaExceededError` by that exact name — sweeping old session records and\n * retrying the write once — and latches again when the retry fails too. Here\n * the first quota failure per key is surfaced under that name so the sweep\n * runs; the retry that follows it and every other storage failure are absorbed\n * and reported through `onDegraded`, so chat keeps working.\n *\n * An absorbed write is kept in an in-memory overlay that `getItem` serves\n * first: statewire re-reads the record through `load()` on every shared-mode\n * transaction, so a stale persisted copy would otherwise win back its old\n * `nextSeq`, `clientId` and command journal. The overlay is per tab — another\n * tab sharing the thread still reads the last persisted copy, which is the\n * memory-only degradation this backend exists to allow. A later successful\n * write clears the overlay entry.\n *\n * A key whose read threw, or whose stored value this build cannot read (see\n * {@link unreadableRecord}), is served as absent and shielded: writes to it\n * stay in the overlay for good, so a fresh lane minted over an unreadable\n * record never overwrites what another tab or a newer build persisted.\n */\nexport function quotaTolerantBackend({\n inner,\n onDegraded,\n}: {\n inner: SyncBackend;\n onDegraded: (degradation: SessionStorageDegradation) => void;\n}): KeyValueSessionStorage.Backend {\n // Keys whose write already surfaced a quota error to statewire: until a\n // write succeeds again, later failures on the key are absorbed instead of\n // surfaced, or every save would re-run statewire's sweep (which evicts\n // other threads' idle records) and the failure would latch on the retry.\n const quotaSurfaced = new Set<string>();\n // Absorbed writes, served ahead of the backend (see the function docs).\n const overlay = new Map<string, string>();\n // Keys served as absent and never written through (see the function docs).\n const shielded = new Set<string>();\n // statewire re-reads and re-decodes the record on every transaction; the\n // readability check is memoised on the raw string so it costs one string\n // comparison per read, not a second parse.\n const checked = new Map<\n string,\n { raw: string; verdict: Error | undefined }\n >();\n const readable = (key: string, raw: string): Error | undefined => {\n const previous = checked.get(key);\n if (previous !== undefined && previous.raw === raw) return previous.verdict;\n const verdict = unreadableRecord(raw);\n checked.set(key, { raw, verdict });\n return verdict;\n };\n return {\n getItem: (key) => {\n const shadowed = overlay.get(key);\n if (shadowed !== undefined) return shadowed;\n if (shielded.has(key)) return null;\n let raw: string | null;\n try {\n raw = inner.getItem(key);\n } catch (error) {\n shielded.add(key);\n onDegraded({ operation: \"getItem\", key, error });\n return null;\n }\n if (raw === null) return null;\n const unreadable = readable(key, raw);\n if (unreadable === undefined) return raw;\n shielded.add(key);\n onDegraded({ operation: \"record\", key, error: unreadable });\n return null;\n },\n setItem: (key, value) => {\n if (shielded.has(key)) {\n overlay.set(key, value);\n return;\n }\n try {\n inner.setItem(key, value);\n overlay.delete(key);\n quotaSurfaced.delete(key);\n } catch (error) {\n if (isStorageQuotaError(error) && !quotaSurfaced.has(key)) {\n quotaSurfaced.add(key);\n throw asStatewireQuotaError(error);\n }\n overlay.set(key, value);\n onDegraded({ operation: \"setItem\", key, error });\n }\n },\n removeItem: (key) => {\n overlay.delete(key);\n if (shielded.has(key)) return;\n try {\n inner.removeItem(key);\n } catch (error) {\n onDegraded({ operation: \"removeItem\", key, error });\n }\n },\n getAllKeys: () => {\n const keys = new Set<string>(overlay.keys());\n try {\n for (const key of inner.getAllKeys()) keys.add(key);\n } catch (error) {\n onDegraded({ operation: \"getAllKeys\", key: \"\", error });\n }\n return [...keys];\n },\n withUnused: async (key, action) => {\n try {\n await inner.withUnused(key, action);\n } catch (error) {\n onDegraded({ operation: \"withUnused\", key, error });\n }\n },\n };\n}\n\ntype DegradationListener = (degradation: SessionStorageDegradation) => void;\n\ntype SharedBackend = {\n backend: KeyValueSessionStorage.Backend;\n listeners: Set<DegradationListener>;\n};\n\n/**\n * One backend per storage object — per origin, in a browser — shared by every\n * session mounted in the tab, exactly as the upstream resource's module-level\n * backend is. statewire keys its in-tab \"active\" bookkeeping by backend\n * identity, so a mount's retention sweep only skips the records of other\n * mounts on the *same* backend; and the absorbed-write overlay has to outlive\n * a remount of the same thread (a thread switch and back) or the remount would\n * read the stale persisted copy. Degradations fan out to every mounted\n * session's reporter.\n */\nconst sharedBackends = new WeakMap<StorageLike, SharedBackend>();\n\nfunction sharedBackend({\n storage,\n locks,\n}: {\n storage: StorageLike;\n locks: LockManagerLike;\n}): SharedBackend {\n let shared = sharedBackends.get(storage);\n if (shared === undefined) {\n const listeners = new Set<DegradationListener>();\n shared = {\n listeners,\n backend: quotaTolerantBackend({\n inner: browserBackend({ storage, locks }),\n onDegraded: (degradation) => {\n for (const listener of listeners) listener(degradation);\n },\n }),\n };\n sharedBackends.set(storage, shared);\n }\n return shared;\n}\n\n/**\n * Athena's browser session store: `@assistant-ui/react-statewire`'s\n * `LocalStorageSession` contract (per-thread key under the shared prefix,\n * cross-tab Web Lock, storage-event subscription, retention) on top of\n * {@link quotaTolerantBackend}, with Athena's protocol lane and retention\n * policy.\n */\nconst useQuotaSafeLocalStorageSession = ({\n threadId,\n key = (id) => id,\n ttl = 7 * DAY_MS,\n max = 50,\n onStorageDegraded,\n}: QuotaSafeLocalStorageSession.Options): StatewireClient.SessionStorage => {\n const storage = browserGlobals.localStorage;\n const locks = browserGlobals.navigator?.locks;\n if (!storage)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires localStorage\",\n );\n if (!locks)\n throw new Error(\n \"statewire: QuotaSafeLocalStorageSession requires the Web Locks API (navigator.locks)\",\n );\n const sessionKey = key(threadId);\n const storageKey = SESSION_PREFIX + sessionKey;\n const lockName = SESSION_LOCK_PREFIX + sessionKey;\n\n const latestOnDegraded = useRef(onStorageDegraded);\n latestOnDegraded.current = onStorageDegraded;\n const reportDegradation = useMemo(() => {\n const reported = new Set<string>();\n return (degradation: SessionStorageDegradation) => {\n const handler = latestOnDegraded.current;\n if (handler) {\n try {\n handler(degradation);\n } catch {\n // Reporting runs inside the session's recovery path and must never\n // become the failure it is reporting.\n }\n return;\n }\n const dedupeKey = `${degradation.operation}:${degradation.key}`;\n if (reported.has(dedupeKey)) return;\n reported.add(dedupeKey);\n console.warn(\n `[statewire] session storage degraded to memory only (${degradation.operation} ${degradation.key})`,\n degradation.error,\n );\n };\n }, []);\n\n const shared = sharedBackend({ storage, locks });\n // Registered during render as well as in the effect: the read statewire\n // makes while mounting below happens before any effect runs, and tap may\n // clean an effect up and re-run it within one commit.\n shared.listeners.add(reportDegradation);\n useEffect(() => {\n shared.listeners.add(reportDegradation);\n return () => {\n shared.listeners.delete(reportDegradation);\n };\n }, [shared, reportDegradation]);\n\n // Hold the record's shared \"active\" lock while mounted so another tab's\n // retention sweep skips it (the same fence the upstream resource holds).\n useEffect(() => {\n let release: (() => void) | undefined;\n let disposed = false;\n Promise.resolve(\n locks.request(\n SESSION_ACTIVE_PREFIX + storageKey,\n { mode: \"shared\" },\n () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n },\n ),\n ).catch((error: unknown) =>\n reportDegradation({ operation: \"lock\", key: storageKey, error }),\n );\n return () => {\n disposed = true;\n release?.();\n };\n }, [locks, storageKey, reportDegradation]);\n\n const sessionStorage = useResource(\n KeyValueSessionStorage({\n backend: shared.backend,\n key: sessionKey,\n namespace: SESSION_PREFIX,\n protocol: DEEP_AGENT_STATEWIRE_PROTOCOL,\n retention: { maxAgeMs: ttl, maxEntries: max },\n // A failure statewire raises itself (a structurally invalid stored\n // record) still stops the session; it is at least reported the same way.\n onError: (error) =>\n reportDegradation({ operation: \"session\", key: storageKey, error }),\n }),\n );\n\n return useMemo(\n () => ({\n ...sessionStorage,\n lock: async <T>(fn: () => T | Promise<T>): Promise<T> => {\n let granted = false;\n try {\n return await locks.request(lockName, async () => {\n granted = true;\n return fn();\n });\n } catch (error) {\n if (granted) throw error;\n // The lock manager refused the request itself (a detached or\n // back/forward-cached document, a lockdown-mode origin): the\n // transaction runs unfenced rather than stopping the session.\n reportDegradation({ operation: \"lock\", key: storageKey, error });\n return fn();\n }\n },\n subscribe: (listener: () => void) => {\n const onStorage = (event: StorageEventLike) => {\n if (event.key === storageKey) listener();\n };\n browserGlobals.addEventListener?.(\"storage\", onStorage);\n return () => browserGlobals.removeEventListener?.(\"storage\", onStorage);\n },\n }),\n [sessionStorage, locks, storageKey, lockName, reportDegradation],\n );\n};\n\n/** Athena's browser session keys and retention policy. */\nexport const QuotaSafeLocalStorageSession = resource(\n useQuotaSafeLocalStorageSession,\n);\n\nexport namespace QuotaSafeLocalStorageSession {\n export type Options = {\n threadId: string;\n key?: (threadId: string) => string;\n ttl?: number;\n max?: number;\n /**\n * Called when a storage operation failed and the session carried on in\n * memory. Defaults to one `console.warn` per operation and key.\n */\n onStorageDegraded?: (degradation: SessionStorageDegradation) => void;\n };\n}\n"],"names":["failed","_a"],"mappings":"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"}
@@ -29,9 +29,7 @@ const useSuspendedStatewire = (options) => {
29
29
  state: void 0,
30
30
  commands: suspendedCommands(),
31
31
  connection,
32
- isBusy: false,
33
- setContext: () => {
34
- }
32
+ isBusy: false
35
33
  };
36
34
  });
37
35
  return value;
@@ -1 +1 @@
1
- {"version":3,"file":"statewire-suspended.cjs","sources":["../../../../../agent-runtime/src/statewire-suspended.ts"],"sourcesContent":["import { resource } from '@assistant-ui/tap';\nimport type { ResourceElement } from '@assistant-ui/tap';\nimport { useRef, useState } from 'react';\nimport type { Statewire, StatewireClient } from 'statewire';\nimport { StatewireSendError } from 'statewire';\n\nexport interface SuspendedStatewireOptions {\n /** Why the transport is parked; surfaced as the connection message. */\n message: string;\n /**\n * Invoked when a surface asks the parked connection to `reconnect()` (the\n * connection indicator's retry affordance). Hosts that park for a reason\n * only they can clear (e.g. the user has no workspace) omit it.\n */\n onReconnectRequested?: (() => void) | undefined;\n}\n\nconst suspendedCommands = <\n TCommands extends Statewire.Commands,\n>(): Statewire.CommandsProxy<TCommands> =>\n new Proxy({} as Statewire.CommandsProxy<TCommands>, {\n get: (_target, method) => {\n if (typeof method !== 'string') return undefined;\n // Settle like a locally-dead channel: hosts already classify\n // non-\"server\" causes as channel teardown (quiet log, no user error).\n return () =>\n Promise.reject(new StatewireSendError({ fate: 'aborted', cause: 'stream-gone' }));\n },\n });\n\nconst useSuspendedStatewire = <TCommands extends Statewire.Commands = Statewire.Commands>(\n options: SuspendedStatewireOptions\n): Statewire<undefined, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n // `state` stays undefined so the thread keeps serving its `initialState`\n // (the same contract a live transport has before its attach snapshot).\n const [value] = useState((): Statewire<undefined, TCommands> => {\n const connection: StatewireClient.Connection & { readonly message: string } = {\n status: 'standby',\n reason: 'deferred',\n get message() {\n return latest.current.message;\n },\n reconnect: () => latest.current.onReconnectRequested?.(),\n degraded: false,\n };\n return {\n state: undefined,\n commands: suspendedCommands<TCommands>(),\n connection,\n isBusy: false,\n setContext: () => {},\n };\n });\n return value;\n};\n\n/**\n * A statewire transport that never dials.\n *\n * Swapped in for the live WS/SSE transport when attaching is known to be\n * pointless — the host gates it (no workspace) or repeated `unauthorized`\n * denials parked it — so the thread element stays mounted for assistant-ui\n * consumers while the reconnect loop stops entirely.\n */\nexport const StatewireSuspended = resource(useSuspendedStatewire) as <\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: SuspendedStatewireOptions\n) => ResourceElement<Statewire<undefined, TCommands>>;\n"],"names":["StatewireSendError","useRef","useState","resource"],"mappings":";;;;;AAiBA,MAAM,oBAAoB,MAGxB,IAAI,MAAM,IAA0C;AAAA,EAClD,KAAK,CAAC,SAAS,WAAW;AACxB,QAAI,OAAO,WAAW,SAAU,QAAO;AAGvC,WAAO,MACL,QAAQ,OAAO,IAAIA,UAAAA,mBAAmB,EAAE,MAAM,WAAW,OAAO,cAAA,CAAe,CAAC;AAAA,EACpF;AACF,CAAC;AAEH,MAAM,wBAAwB,CAC5B,YACoC;AACpC,QAAM,SAASC,MAAAA,OAAO,OAAO;AAC7B,SAAO,UAAU;AAGjB,QAAM,CAAC,KAAK,IAAIC,MAAAA,SAAS,MAAuC;AAC9D,UAAM,aAAwE;AAAA,MAC5E,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,IAAI,UAAU;AACZ,eAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,MACA,WAAW,MAAA;;AAAM,kCAAO,SAAQ,yBAAf;AAAA;AAAA,MACjB,UAAU;AAAA,IAAA;AAEZ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU,kBAAA;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,MAAM;AAAA,MAAC;AAAA,IAAA;AAAA,EAEvB,CAAC;AACD,SAAO;AACT;AAUO,MAAM,qBAAqBC,IAAAA,SAAS,qBAAqB;;"}
1
+ {"version":3,"file":"statewire-suspended.cjs","sources":["../../../../../agent-runtime/src/statewire-suspended.ts"],"sourcesContent":["import { resource } from '@assistant-ui/tap';\nimport type { ResourceElement } from '@assistant-ui/tap';\nimport { useRef, useState } from 'react';\nimport type { Statewire, StatewireClient } from 'statewire';\nimport { StatewireSendError } from 'statewire';\n\nexport interface SuspendedStatewireOptions {\n /** Why the transport is parked; surfaced as the connection message. */\n message: string;\n /**\n * Invoked when a surface asks the parked connection to `reconnect()` (the\n * connection indicator's retry affordance). Hosts that park for a reason\n * only they can clear (e.g. the user has no workspace) omit it.\n */\n onReconnectRequested?: (() => void) | undefined;\n}\n\nconst suspendedCommands = <\n TCommands extends Statewire.Commands,\n>(): Statewire.CommandsProxy<TCommands> =>\n new Proxy({} as Statewire.CommandsProxy<TCommands>, {\n get: (_target, method) => {\n if (typeof method !== 'string') return undefined;\n // Settle like a locally-dead channel: hosts already classify\n // non-\"server\" causes as channel teardown (quiet log, no user error).\n return () =>\n Promise.reject(new StatewireSendError({ fate: 'aborted', cause: 'stream-gone' }));\n },\n });\n\nconst useSuspendedStatewire = <TCommands extends Statewire.Commands = Statewire.Commands>(\n options: SuspendedStatewireOptions\n): Statewire<undefined, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n // `state` stays undefined so the thread keeps serving its `initialState`\n // (the same contract a live transport has before its attach snapshot).\n const [value] = useState((): Statewire<undefined, TCommands> => {\n const connection: StatewireClient.Connection & { readonly message: string } = {\n status: 'standby',\n reason: 'deferred',\n get message() {\n return latest.current.message;\n },\n reconnect: () => latest.current.onReconnectRequested?.(),\n degraded: false,\n };\n return {\n state: undefined,\n commands: suspendedCommands<TCommands>(),\n connection,\n isBusy: false,\n };\n });\n return value;\n};\n\n/**\n * A statewire transport that never dials.\n *\n * Swapped in for the live WS/SSE transport when attaching is known to be\n * pointless — the host gates it (no workspace) or repeated `unauthorized`\n * denials parked it — so the thread element stays mounted for assistant-ui\n * consumers while the reconnect loop stops entirely.\n */\nexport const StatewireSuspended = resource(useSuspendedStatewire) as <\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: SuspendedStatewireOptions\n) => ResourceElement<Statewire<undefined, TCommands>>;\n"],"names":["StatewireSendError","useRef","useState","resource"],"mappings":";;;;;AAiBA,MAAM,oBAAoB,MAGxB,IAAI,MAAM,IAA0C;AAAA,EAClD,KAAK,CAAC,SAAS,WAAW;AACxB,QAAI,OAAO,WAAW,SAAU,QAAO;AAGvC,WAAO,MACL,QAAQ,OAAO,IAAIA,UAAAA,mBAAmB,EAAE,MAAM,WAAW,OAAO,cAAA,CAAe,CAAC;AAAA,EACpF;AACF,CAAC;AAEH,MAAM,wBAAwB,CAC5B,YACoC;AACpC,QAAM,SAASC,MAAAA,OAAO,OAAO;AAC7B,SAAO,UAAU;AAGjB,QAAM,CAAC,KAAK,IAAIC,MAAAA,SAAS,MAAuC;AAC9D,UAAM,aAAwE;AAAA,MAC5E,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,IAAI,UAAU;AACZ,eAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,MACA,WAAW,MAAA;;AAAM,kCAAO,SAAQ,yBAAf;AAAA;AAAA,MACjB,UAAU;AAAA,IAAA;AAEZ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU,kBAAA;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,IAAA;AAAA,EAEZ,CAAC;AACD,SAAO;AACT;AAUO,MAAM,qBAAqBC,IAAAA,SAAS,qBAAqB;;"}
@@ -27,9 +27,7 @@ const useSuspendedStatewire = (options) => {
27
27
  state: void 0,
28
28
  commands: suspendedCommands(),
29
29
  connection,
30
- isBusy: false,
31
- setContext: () => {
32
- }
30
+ isBusy: false
33
31
  };
34
32
  });
35
33
  return value;
@@ -1 +1 @@
1
- {"version":3,"file":"statewire-suspended.js","sources":["../../../../../agent-runtime/src/statewire-suspended.ts"],"sourcesContent":["import { resource } from '@assistant-ui/tap';\nimport type { ResourceElement } from '@assistant-ui/tap';\nimport { useRef, useState } from 'react';\nimport type { Statewire, StatewireClient } from 'statewire';\nimport { StatewireSendError } from 'statewire';\n\nexport interface SuspendedStatewireOptions {\n /** Why the transport is parked; surfaced as the connection message. */\n message: string;\n /**\n * Invoked when a surface asks the parked connection to `reconnect()` (the\n * connection indicator's retry affordance). Hosts that park for a reason\n * only they can clear (e.g. the user has no workspace) omit it.\n */\n onReconnectRequested?: (() => void) | undefined;\n}\n\nconst suspendedCommands = <\n TCommands extends Statewire.Commands,\n>(): Statewire.CommandsProxy<TCommands> =>\n new Proxy({} as Statewire.CommandsProxy<TCommands>, {\n get: (_target, method) => {\n if (typeof method !== 'string') return undefined;\n // Settle like a locally-dead channel: hosts already classify\n // non-\"server\" causes as channel teardown (quiet log, no user error).\n return () =>\n Promise.reject(new StatewireSendError({ fate: 'aborted', cause: 'stream-gone' }));\n },\n });\n\nconst useSuspendedStatewire = <TCommands extends Statewire.Commands = Statewire.Commands>(\n options: SuspendedStatewireOptions\n): Statewire<undefined, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n // `state` stays undefined so the thread keeps serving its `initialState`\n // (the same contract a live transport has before its attach snapshot).\n const [value] = useState((): Statewire<undefined, TCommands> => {\n const connection: StatewireClient.Connection & { readonly message: string } = {\n status: 'standby',\n reason: 'deferred',\n get message() {\n return latest.current.message;\n },\n reconnect: () => latest.current.onReconnectRequested?.(),\n degraded: false,\n };\n return {\n state: undefined,\n commands: suspendedCommands<TCommands>(),\n connection,\n isBusy: false,\n setContext: () => {},\n };\n });\n return value;\n};\n\n/**\n * A statewire transport that never dials.\n *\n * Swapped in for the live WS/SSE transport when attaching is known to be\n * pointless — the host gates it (no workspace) or repeated `unauthorized`\n * denials parked it — so the thread element stays mounted for assistant-ui\n * consumers while the reconnect loop stops entirely.\n */\nexport const StatewireSuspended = resource(useSuspendedStatewire) as <\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: SuspendedStatewireOptions\n) => ResourceElement<Statewire<undefined, TCommands>>;\n"],"names":[],"mappings":";;;AAiBA,MAAM,oBAAoB,MAGxB,IAAI,MAAM,IAA0C;AAAA,EAClD,KAAK,CAAC,SAAS,WAAW;AACxB,QAAI,OAAO,WAAW,SAAU,QAAO;AAGvC,WAAO,MACL,QAAQ,OAAO,IAAI,mBAAmB,EAAE,MAAM,WAAW,OAAO,cAAA,CAAe,CAAC;AAAA,EACpF;AACF,CAAC;AAEH,MAAM,wBAAwB,CAC5B,YACoC;AACpC,QAAM,SAAS,OAAO,OAAO;AAC7B,SAAO,UAAU;AAGjB,QAAM,CAAC,KAAK,IAAI,SAAS,MAAuC;AAC9D,UAAM,aAAwE;AAAA,MAC5E,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,IAAI,UAAU;AACZ,eAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,MACA,WAAW,MAAA;;AAAM,kCAAO,SAAQ,yBAAf;AAAA;AAAA,MACjB,UAAU;AAAA,IAAA;AAEZ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU,kBAAA;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,MACR,YAAY,MAAM;AAAA,MAAC;AAAA,IAAA;AAAA,EAEvB,CAAC;AACD,SAAO;AACT;AAUO,MAAM,qBAAqB,SAAS,qBAAqB;"}
1
+ {"version":3,"file":"statewire-suspended.js","sources":["../../../../../agent-runtime/src/statewire-suspended.ts"],"sourcesContent":["import { resource } from '@assistant-ui/tap';\nimport type { ResourceElement } from '@assistant-ui/tap';\nimport { useRef, useState } from 'react';\nimport type { Statewire, StatewireClient } from 'statewire';\nimport { StatewireSendError } from 'statewire';\n\nexport interface SuspendedStatewireOptions {\n /** Why the transport is parked; surfaced as the connection message. */\n message: string;\n /**\n * Invoked when a surface asks the parked connection to `reconnect()` (the\n * connection indicator's retry affordance). Hosts that park for a reason\n * only they can clear (e.g. the user has no workspace) omit it.\n */\n onReconnectRequested?: (() => void) | undefined;\n}\n\nconst suspendedCommands = <\n TCommands extends Statewire.Commands,\n>(): Statewire.CommandsProxy<TCommands> =>\n new Proxy({} as Statewire.CommandsProxy<TCommands>, {\n get: (_target, method) => {\n if (typeof method !== 'string') return undefined;\n // Settle like a locally-dead channel: hosts already classify\n // non-\"server\" causes as channel teardown (quiet log, no user error).\n return () =>\n Promise.reject(new StatewireSendError({ fate: 'aborted', cause: 'stream-gone' }));\n },\n });\n\nconst useSuspendedStatewire = <TCommands extends Statewire.Commands = Statewire.Commands>(\n options: SuspendedStatewireOptions\n): Statewire<undefined, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n // `state` stays undefined so the thread keeps serving its `initialState`\n // (the same contract a live transport has before its attach snapshot).\n const [value] = useState((): Statewire<undefined, TCommands> => {\n const connection: StatewireClient.Connection & { readonly message: string } = {\n status: 'standby',\n reason: 'deferred',\n get message() {\n return latest.current.message;\n },\n reconnect: () => latest.current.onReconnectRequested?.(),\n degraded: false,\n };\n return {\n state: undefined,\n commands: suspendedCommands<TCommands>(),\n connection,\n isBusy: false,\n };\n });\n return value;\n};\n\n/**\n * A statewire transport that never dials.\n *\n * Swapped in for the live WS/SSE transport when attaching is known to be\n * pointless — the host gates it (no workspace) or repeated `unauthorized`\n * denials parked it — so the thread element stays mounted for assistant-ui\n * consumers while the reconnect loop stops entirely.\n */\nexport const StatewireSuspended = resource(useSuspendedStatewire) as <\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: SuspendedStatewireOptions\n) => ResourceElement<Statewire<undefined, TCommands>>;\n"],"names":[],"mappings":";;;AAiBA,MAAM,oBAAoB,MAGxB,IAAI,MAAM,IAA0C;AAAA,EAClD,KAAK,CAAC,SAAS,WAAW;AACxB,QAAI,OAAO,WAAW,SAAU,QAAO;AAGvC,WAAO,MACL,QAAQ,OAAO,IAAI,mBAAmB,EAAE,MAAM,WAAW,OAAO,cAAA,CAAe,CAAC;AAAA,EACpF;AACF,CAAC;AAEH,MAAM,wBAAwB,CAC5B,YACoC;AACpC,QAAM,SAAS,OAAO,OAAO;AAC7B,SAAO,UAAU;AAGjB,QAAM,CAAC,KAAK,IAAI,SAAS,MAAuC;AAC9D,UAAM,aAAwE;AAAA,MAC5E,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,IAAI,UAAU;AACZ,eAAO,OAAO,QAAQ;AAAA,MACxB;AAAA,MACA,WAAW,MAAA;;AAAM,kCAAO,SAAQ,yBAAf;AAAA;AAAA,MACjB,UAAU;AAAA,IAAA;AAEZ,WAAO;AAAA,MACL,OAAO;AAAA,MACP,UAAU,kBAAA;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,IAAA;AAAA,EAEZ,CAAC;AACD,SAAO;AACT;AAUO,MAAM,qBAAqB,SAAS,qBAAqB;"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-agent-edit-submit.cjs","sources":["../../../../../agent-runtime/src/use-deep-agent-edit-submit.ts"],"sourcesContent":["import { useAui } from '@assistant-ui/store';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { readDeepAgentStatewireExtras, resolveDeepAgentEditCommand } from './edit-command';\n\n/**\n * The submit handler of a message's edit composer on a deep-agent thread: the\n * edited text leaves as a `run/edit` anchored on the host's own parent of the\n * message (see `edit-command.ts`) — on a windowed wire the lane's\n * `anchorBefore` at the window edge, and older pages loaded first for a\n * message below the loaded range — instead of the library's converted-message\n * anchor, which the host rejects `wrong-anchor` after a chunked assistant turn\n * and at the window edge.\n *\n * Read inside the message scope (`aui.message()`): the library's optimistic\n * fold shows the edited bubble and rewinds the visible history the moment the\n * command is dispatched, so the edit composer closes right away. Falls back\n * to the composer's own `send()` when the thread is not a statewire thread or\n * the host has not settled the message yet. Resolves `false` for an empty\n * draft, a duplicate submission, or an edit cancelled during preparation.\n */\nexport function useDeepAgentMessageEditSubmit(): () => Promise<boolean> {\n const aui = useAui();\n const alive = useRef(true);\n const submitting = useRef(false);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n return useCallback(async () => {\n if (submitting.current) return false;\n const composer = aui.message().composer();\n const draft = composer.getState();\n const text = draft.text.trim();\n if (!text && draft.attachments.length === 0) return false;\n const files: StatewireThread.FilePart[] = [];\n const texts = text ? [text] : [];\n // beginEdit lifts original inline images/files into completed composer\n // attachments. The anchored command must carry those too: a text-only\n // reconstruction silently deleted them when editing a cross-device turn.\n for (const attachment of draft.attachments) {\n if (attachment.status.type !== 'complete')\n throw new Error('Wait for attachments to finish before sending your edit.');\n for (const part of attachment.content ?? []) {\n if (part.type === 'text') texts.push(part.text);\n else if (part.type === 'image')\n files.push({ type: 'file', mediaType: 'image/*', url: part.image });\n else if (part.type === 'file')\n files.push({\n type: 'file',\n mediaType: part.mimeType,\n url: part.data,\n ...(part.filename && { filename: part.filename }),\n });\n else throw new Error('This attachment cannot be edited here.');\n }\n }\n submitting.current = true;\n try {\n const sourceId = aui.message().getState().id;\n const readExtras = () =>\n alive.current ? readDeepAgentStatewireExtras(aui.thread().getState().extras) : null;\n const extras = readExtras();\n if (extras) {\n const preparedMetadata = extras.prepareMessageMetadata?.();\n try {\n const command = await resolveDeepAgentEditCommand({\n readExtras,\n sourceId,\n text,\n });\n if (!alive.current || !composer.getState().isEditing) {\n if (preparedMetadata) extras.discardPreparedMessageMetadata?.(preparedMetadata);\n return false;\n }\n if (command) {\n void Promise.resolve(\n extras.sendCommand(\n {\n ...command,\n message: {\n ...command.message,\n parts: [\n ...files,\n ...(texts.length\n ? [{ type: 'text' as const, text: texts.join('\\n\\n') }]\n : []),\n ],\n },\n },\n preparedMetadata === undefined ? undefined : { preparedMetadata },\n ),\n ).catch(() => undefined);\n composer.cancel();\n return true;\n }\n if (preparedMetadata && extras.sendPreparedEdit) {\n extras.sendPreparedEdit(sourceId, preparedMetadata);\n return true;\n }\n } catch (error) {\n if (preparedMetadata) extras.discardPreparedMessageMetadata?.(preparedMetadata);\n throw error;\n }\n }\n composer.send();\n return true;\n } finally {\n submitting.current = false;\n }\n }, [aui]);\n}\n"],"names":["useAui","useRef","useEffect","useCallback","readDeepAgentStatewireExtras","resolveDeepAgentEditCommand"],"mappings":";;;;;AAqBO,SAAS,gCAAwD;AACtE,QAAM,MAAMA,MAAAA,OAAA;AACZ,QAAM,QAAQC,MAAAA,OAAO,IAAI;AACzB,QAAM,aAAaA,MAAAA,OAAO,KAAK;AAC/BC,QAAAA,UAAU,MAAM;AACd,UAAM,UAAU;AAChB,WAAO,MAAM;AACX,YAAM,UAAU;AAAA,IAClB;AAAA,EACF,GAAG,CAAA,CAAE;AACL,SAAOC,MAAAA,YAAY,YAAY;;AAC7B,QAAI,WAAW,QAAS,QAAO;AAC/B,UAAM,WAAW,IAAI,QAAA,EAAU,SAAA;AAC/B,UAAM,QAAQ,SAAS,SAAA;AACvB,UAAM,OAAO,MAAM,KAAK,KAAA;AACxB,QAAI,CAAC,QAAQ,MAAM,YAAY,WAAW,EAAG,QAAO;AACpD,UAAM,QAAoC,CAAA;AAC1C,UAAM,QAAQ,OAAO,CAAC,IAAI,IAAI,CAAA;AAI9B,eAAW,cAAc,MAAM,aAAa;AAC1C,UAAI,WAAW,OAAO,SAAS;AAC7B,cAAM,IAAI,MAAM,0DAA0D;AAC5E,iBAAW,QAAQ,WAAW,WAAW,CAAA,GAAI;AAC3C,YAAI,KAAK,SAAS,OAAQ,OAAM,KAAK,KAAK,IAAI;AAAA,iBACrC,KAAK,SAAS;AACrB,gBAAM,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,KAAK,KAAK,OAAO;AAAA,iBAC3D,KAAK,SAAS;AACrB,gBAAM,KAAK;AAAA,YACT,MAAM;AAAA,YACN,WAAW,KAAK;AAAA,YAChB,KAAK,KAAK;AAAA,YACV,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAA;AAAA,UAAS,CAChD;AAAA,YACE,OAAM,IAAI,MAAM,wCAAwC;AAAA,MAC/D;AAAA,IACF;AACA,eAAW,UAAU;AACrB,QAAI;AACF,YAAM,WAAW,IAAI,QAAA,EAAU,WAAW;AAC1C,YAAM,aAAa,MACjB,MAAM,UAAUC,YAAAA,6BAA6B,IAAI,SAAS,WAAW,MAAM,IAAI;AACjF,YAAM,SAAS,WAAA;AACf,UAAI,QAAQ;AACV,cAAM,oBAAmB,YAAO,2BAAP;AACzB,YAAI;AACF,gBAAM,UAAU,MAAMC,wCAA4B;AAAA,YAChD;AAAA,YACA;AAAA,YACA;AAAA,UAAA,CACD;AACD,cAAI,CAAC,MAAM,WAAW,CAAC,SAAS,SAAA,EAAW,WAAW;AACpD,gBAAI,iBAAkB,cAAO,mCAAP,gCAAwC;AAC9D,mBAAO;AAAA,UACT;AACA,cAAI,SAAS;AACX,iBAAK,QAAQ;AAAA,cACX,OAAO;AAAA,gBACL;AAAA,kBACE,GAAG;AAAA,kBACH,SAAS;AAAA,oBACP,GAAG,QAAQ;AAAA,oBACX,OAAO;AAAA,sBACL,GAAG;AAAA,sBACH,GAAI,MAAM,SACN,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,MAAM,EAAA,CAAG,IACpD,CAAA;AAAA,oBAAC;AAAA,kBACP;AAAA,gBACF;AAAA,gBAEF,qBAAqB,SAAY,SAAY,EAAE,iBAAA;AAAA,cAAiB;AAAA,YAClE,EACA,MAAM,MAAM,MAAS;AACvB,qBAAS,OAAA;AACT,mBAAO;AAAA,UACT;AACA,cAAI,oBAAoB,OAAO,kBAAkB;AAC/C,mBAAO,iBAAiB,UAAU,gBAAgB;AAClD,mBAAO;AAAA,UACT;AAAA,QACF,SAAS,OAAO;AACd,cAAI,iBAAkB,cAAO,mCAAP,gCAAwC;AAC9D,gBAAM;AAAA,QACR;AAAA,MACF;AACA,eAAS,KAAA;AACT,aAAO;AAAA,IACT,UAAA;AACE,iBAAW,UAAU;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AACV;;"}
1
+ {"version":3,"file":"use-deep-agent-edit-submit.cjs","sources":["../../../../../agent-runtime/src/use-deep-agent-edit-submit.ts"],"sourcesContent":["import { useAui } from '@assistant-ui/store';\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef } from 'react';\nimport { readDeepAgentStatewireExtras, resolveDeepAgentEditCommand } from './edit-command';\n\n/**\n * The submit handler of a message's edit composer on a deep-agent thread: the\n * edited text leaves as a `run/edit` anchored on the host's own parent of the\n * message (see `edit-command.ts`) — older pages stated first for a message at\n * or below the loaded window's edge — instead of the library's\n * converted-message anchor, which the host rejects `wrong-anchor` after a\n * chunked assistant turn and at the window edge.\n *\n * Read inside the message scope (`aui.message()`): the library's optimistic\n * fold shows the edited bubble and rewinds the visible history the moment the\n * command is dispatched, so the edit composer closes right away. Falls back\n * to the composer's own `send()` when the thread is not a statewire thread or\n * the host has not settled the message yet. Resolves `false` for an empty\n * draft, a duplicate submission, or an edit cancelled during preparation.\n */\nexport function useDeepAgentMessageEditSubmit(): () => Promise<boolean> {\n const aui = useAui();\n const alive = useRef(true);\n const submitting = useRef(false);\n useEffect(() => {\n alive.current = true;\n return () => {\n alive.current = false;\n };\n }, []);\n return useCallback(async () => {\n if (submitting.current) return false;\n const composer = aui.message().composer();\n const draft = composer.getState();\n const text = draft.text.trim();\n if (!text && draft.attachments.length === 0) return false;\n const files: StatewireThread.FilePart[] = [];\n const texts = text ? [text] : [];\n // beginEdit lifts original inline images/files into completed composer\n // attachments. The anchored command must carry those too: a text-only\n // reconstruction silently deleted them when editing a cross-device turn.\n for (const attachment of draft.attachments) {\n if (attachment.status.type !== 'complete')\n throw new Error('Wait for attachments to finish before sending your edit.');\n for (const part of attachment.content ?? []) {\n if (part.type === 'text') texts.push(part.text);\n else if (part.type === 'image')\n files.push({ type: 'file', mediaType: 'image/*', url: part.image });\n else if (part.type === 'file')\n files.push({\n type: 'file',\n mediaType: part.mimeType,\n url: part.data,\n ...(part.filename && { filename: part.filename }),\n });\n else throw new Error('This attachment cannot be edited here.');\n }\n }\n submitting.current = true;\n try {\n const sourceId = aui.message().getState().id;\n const readExtras = () =>\n alive.current ? readDeepAgentStatewireExtras(aui.thread().getState().extras) : null;\n const extras = readExtras();\n if (extras) {\n const preparedMetadata = extras.prepareMessageMetadata?.();\n try {\n const command = await resolveDeepAgentEditCommand({\n readExtras,\n sourceId,\n text,\n });\n if (!alive.current || !composer.getState().isEditing) {\n if (preparedMetadata) extras.discardPreparedMessageMetadata?.(preparedMetadata);\n return false;\n }\n if (command) {\n void Promise.resolve(\n extras.sendCommand(\n {\n ...command,\n message: {\n ...command.message,\n parts: [\n ...files,\n ...(texts.length\n ? [{ type: 'text' as const, text: texts.join('\\n\\n') }]\n : []),\n ],\n },\n },\n preparedMetadata === undefined ? undefined : { preparedMetadata },\n ),\n ).catch(() => undefined);\n composer.cancel();\n return true;\n }\n if (preparedMetadata && extras.sendPreparedEdit) {\n extras.sendPreparedEdit(sourceId, preparedMetadata);\n return true;\n }\n } catch (error) {\n if (preparedMetadata) extras.discardPreparedMessageMetadata?.(preparedMetadata);\n throw error;\n }\n }\n composer.send();\n return true;\n } finally {\n submitting.current = false;\n }\n }, [aui]);\n}\n"],"names":["useAui","useRef","useEffect","useCallback","readDeepAgentStatewireExtras","resolveDeepAgentEditCommand"],"mappings":";;;;;AAoBO,SAAS,gCAAwD;AACtE,QAAM,MAAMA,MAAAA,OAAA;AACZ,QAAM,QAAQC,MAAAA,OAAO,IAAI;AACzB,QAAM,aAAaA,MAAAA,OAAO,KAAK;AAC/BC,QAAAA,UAAU,MAAM;AACd,UAAM,UAAU;AAChB,WAAO,MAAM;AACX,YAAM,UAAU;AAAA,IAClB;AAAA,EACF,GAAG,CAAA,CAAE;AACL,SAAOC,MAAAA,YAAY,YAAY;;AAC7B,QAAI,WAAW,QAAS,QAAO;AAC/B,UAAM,WAAW,IAAI,QAAA,EAAU,SAAA;AAC/B,UAAM,QAAQ,SAAS,SAAA;AACvB,UAAM,OAAO,MAAM,KAAK,KAAA;AACxB,QAAI,CAAC,QAAQ,MAAM,YAAY,WAAW,EAAG,QAAO;AACpD,UAAM,QAAoC,CAAA;AAC1C,UAAM,QAAQ,OAAO,CAAC,IAAI,IAAI,CAAA;AAI9B,eAAW,cAAc,MAAM,aAAa;AAC1C,UAAI,WAAW,OAAO,SAAS;AAC7B,cAAM,IAAI,MAAM,0DAA0D;AAC5E,iBAAW,QAAQ,WAAW,WAAW,CAAA,GAAI;AAC3C,YAAI,KAAK,SAAS,OAAQ,OAAM,KAAK,KAAK,IAAI;AAAA,iBACrC,KAAK,SAAS;AACrB,gBAAM,KAAK,EAAE,MAAM,QAAQ,WAAW,WAAW,KAAK,KAAK,OAAO;AAAA,iBAC3D,KAAK,SAAS;AACrB,gBAAM,KAAK;AAAA,YACT,MAAM;AAAA,YACN,WAAW,KAAK;AAAA,YAChB,KAAK,KAAK;AAAA,YACV,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAA;AAAA,UAAS,CAChD;AAAA,YACE,OAAM,IAAI,MAAM,wCAAwC;AAAA,MAC/D;AAAA,IACF;AACA,eAAW,UAAU;AACrB,QAAI;AACF,YAAM,WAAW,IAAI,QAAA,EAAU,WAAW;AAC1C,YAAM,aAAa,MACjB,MAAM,UAAUC,YAAAA,6BAA6B,IAAI,SAAS,WAAW,MAAM,IAAI;AACjF,YAAM,SAAS,WAAA;AACf,UAAI,QAAQ;AACV,cAAM,oBAAmB,YAAO,2BAAP;AACzB,YAAI;AACF,gBAAM,UAAU,MAAMC,wCAA4B;AAAA,YAChD;AAAA,YACA;AAAA,YACA;AAAA,UAAA,CACD;AACD,cAAI,CAAC,MAAM,WAAW,CAAC,SAAS,SAAA,EAAW,WAAW;AACpD,gBAAI,iBAAkB,cAAO,mCAAP,gCAAwC;AAC9D,mBAAO;AAAA,UACT;AACA,cAAI,SAAS;AACX,iBAAK,QAAQ;AAAA,cACX,OAAO;AAAA,gBACL;AAAA,kBACE,GAAG;AAAA,kBACH,SAAS;AAAA,oBACP,GAAG,QAAQ;AAAA,oBACX,OAAO;AAAA,sBACL,GAAG;AAAA,sBACH,GAAI,MAAM,SACN,CAAC,EAAE,MAAM,QAAiB,MAAM,MAAM,KAAK,MAAM,EAAA,CAAG,IACpD,CAAA;AAAA,oBAAC;AAAA,kBACP;AAAA,gBACF;AAAA,gBAEF,qBAAqB,SAAY,SAAY,EAAE,iBAAA;AAAA,cAAiB;AAAA,YAClE,EACA,MAAM,MAAM,MAAS;AACvB,qBAAS,OAAA;AACT,mBAAO;AAAA,UACT;AACA,cAAI,oBAAoB,OAAO,kBAAkB;AAC/C,mBAAO,iBAAiB,UAAU,gBAAgB;AAClD,mBAAO;AAAA,UACT;AAAA,QACF,SAAS,OAAO;AACd,cAAI,iBAAkB,cAAO,mCAAP,gCAAwC;AAC9D,gBAAM;AAAA,QACR;AAAA,MACF;AACA,eAAS,KAAA;AACT,aAAO;AAAA,IACT,UAAA;AACE,iBAAW,UAAU;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AACV;;"}