@athenaintel/react 0.48.0 → 0.52.1

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 (255) hide show
  1. package/README.md +68 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
  26. package/dist/agent-runtime/src/edit-command.cjs +21 -0
  27. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  28. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  29. package/dist/agent-runtime/src/edit-command.js +21 -0
  30. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  31. package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
  32. package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
  33. package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
  34. package/dist/agent-runtime/src/handoff-arrival.js +20 -1
  35. package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
  36. package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
  37. package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
  38. package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
  39. package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
  40. package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
  41. package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
  42. package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
  43. package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
  44. package/dist/agent-runtime/src/handoff-cursor.js +88 -0
  45. package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
  46. package/dist/agent-runtime/src/index.d.ts +9 -7
  47. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  48. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  49. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  50. package/dist/agent-runtime/src/interrupts.js +28 -1
  51. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  52. package/dist/agent-runtime/src/model-context.cjs +3 -2
  53. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  54. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  55. package/dist/agent-runtime/src/model-context.js +3 -2
  56. package/dist/agent-runtime/src/model-context.js.map +1 -1
  57. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  58. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  59. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  60. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  61. package/dist/agent-runtime/src/send-recovery.js +37 -3
  62. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  63. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
  64. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
  65. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  66. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  67. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  68. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  69. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  70. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  71. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  72. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  73. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  74. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  75. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  76. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  77. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  78. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  79. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  80. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  81. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  82. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  83. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  84. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  85. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  86. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
  87. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  88. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
  89. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
  90. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  91. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
  92. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  93. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  94. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  95. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  96. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  97. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  98. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  99. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  100. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  101. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  102. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  103. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  104. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  105. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  106. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  107. package/dist/assets/use-asset-embed.cjs.map +1 -1
  108. package/dist/assets/use-asset-embed.d.ts +12 -5
  109. package/dist/assets/use-asset-embed.js.map +1 -1
  110. package/dist/chat/AthenaChat.cjs +27 -12
  111. package/dist/chat/AthenaChat.cjs.map +1 -1
  112. package/dist/chat/AthenaChat.d.ts +13 -0
  113. package/dist/chat/AthenaChat.js +27 -12
  114. package/dist/chat/AthenaChat.js.map +1 -1
  115. package/dist/chat/AthenaPlanStrip.cjs +97 -0
  116. package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
  117. package/dist/chat/AthenaPlanStrip.d.ts +15 -0
  118. package/dist/chat/AthenaPlanStrip.js +97 -0
  119. package/dist/chat/AthenaPlanStrip.js.map +1 -0
  120. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  121. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  122. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  123. package/dist/chat/StatewireApprovalCard.js +42 -7
  124. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  125. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  126. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  127. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  128. package/dist/chat/StatewireQueuedMessages.js +58 -8
  129. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  130. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  131. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  132. package/dist/chat/WaitingOnYouBanner.js +7 -3
  133. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  134. package/dist/chat/chat-voice-controls.cjs +3 -1
  135. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  136. package/dist/chat/chat-voice-controls.d.ts +6 -1
  137. package/dist/chat/chat-voice-controls.js +3 -1
  138. package/dist/chat/chat-voice-controls.js.map +1 -1
  139. package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
  140. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  141. package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
  142. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  143. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
  144. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  145. package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
  146. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  147. package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
  148. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  149. package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
  150. package/dist/chat/super-grouping/activity-timeline.js +6 -4
  151. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  152. package/dist/chat/super-grouping/index.d.ts +1 -1
  153. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
  154. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
  155. package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
  156. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
  157. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
  158. package/dist/chat-ui/index.cjs +11 -0
  159. package/dist/chat-ui/index.cjs.map +1 -1
  160. package/dist/chat-ui/index.d.ts +3 -1
  161. package/dist/chat-ui/index.js +12 -1
  162. package/dist/chat-ui/index.js.map +1 -1
  163. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  164. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  165. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  166. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  167. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  168. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  169. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  170. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  171. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  172. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  173. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  174. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  175. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  176. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  177. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  178. package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
  179. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  180. package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
  181. package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
  182. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  183. package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
  184. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
  185. package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
  186. package/dist/chat-ui/thread/handoff-cursor.js +216 -0
  187. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
  188. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
  189. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  190. package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
  191. package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
  192. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  193. package/dist/collab/client.cjs +15 -3
  194. package/dist/collab/client.cjs.map +1 -1
  195. package/dist/collab/client.d.ts +4 -2
  196. package/dist/collab/client.js +16 -4
  197. package/dist/collab/client.js.map +1 -1
  198. package/dist/composer/index.d.ts +1 -1
  199. package/dist/composer/useReadComposerDraft.cjs +26 -7
  200. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  201. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  202. package/dist/composer/useReadComposerDraft.js +27 -8
  203. package/dist/composer/useReadComposerDraft.js.map +1 -1
  204. package/dist/index.cjs +5 -0
  205. package/dist/index.cjs.map +1 -1
  206. package/dist/index.d.ts +5 -2
  207. package/dist/index.js +6 -1
  208. package/dist/index.js.map +1 -1
  209. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
  210. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  211. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
  212. package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
  213. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  214. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  215. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  216. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  217. package/dist/keryx-http/src/update-attributions.js +35 -5
  218. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  219. package/dist/provider/AthenaProvider.cjs +4 -0
  220. package/dist/provider/AthenaProvider.cjs.map +1 -1
  221. package/dist/provider/AthenaProvider.d.ts +11 -1
  222. package/dist/provider/AthenaProvider.js +4 -0
  223. package/dist/provider/AthenaProvider.js.map +1 -1
  224. package/dist/react/package.json.cjs +1 -1
  225. package/dist/react/package.json.js +1 -1
  226. package/dist/styles.css +1 -1
  227. package/dist/threads/api.cjs.map +1 -1
  228. package/dist/threads/api.d.ts +15 -0
  229. package/dist/threads/api.js.map +1 -1
  230. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  231. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  232. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  233. package/dist/threads/conversation-list-fetcher.js +13 -5
  234. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  235. package/dist/threads/statewire-thread-list.cjs +28 -6
  236. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  237. package/dist/threads/statewire-thread-list.d.ts +20 -8
  238. package/dist/threads/statewire-thread-list.js +28 -6
  239. package/dist/threads/statewire-thread-list.js.map +1 -1
  240. package/dist/threads/use-athena-sessions.cjs +27 -5
  241. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  242. package/dist/threads/use-athena-sessions.d.ts +10 -5
  243. package/dist/threads/use-athena-sessions.js +28 -6
  244. package/dist/threads/use-athena-sessions.js.map +1 -1
  245. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  246. package/dist/tools/write-todos-tool-ui.cjs +8 -0
  247. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  248. package/dist/tools/write-todos-tool-ui.d.ts +2 -0
  249. package/dist/tools/write-todos-tool-ui.js +8 -0
  250. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  251. package/dist/workbench/session-workbench-panes.cjs +1 -8
  252. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  253. package/dist/workbench/session-workbench-panes.js +2 -9
  254. package/dist/workbench/session-workbench-panes.js.map +1 -1
  255. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"chat-outbox-store.cjs","sources":["../../../../agent-runtime/src/chat-outbox-store.ts"],"sourcesContent":["import {\n type IdbDatabase,\n idbGlobals,\n lazySealedDatabase,\n openJson,\n requestToPromise,\n sealJson,\n sha256Hex,\n transactionDone,\n} from \"./sealed-idb\";\n\n/**\n * Where chat messages typed while Athena can't be reached wait until they are sent\n * (`chat-outbox.ts`): one store per person on this device, sealed with a key of theirs.\n *\n * This store can hold the only copy of what someone wrote, so it never deletes as a side effect:\n * a record goes when its message was handed to the conversation and the conversation's own journal\n * took it (`remove`), when the person cancels or discards it, or when the person chooses to sign\n * out (`clear`). A record that does not open (a key that is gone, a newer build's format) is left\n * where it is.\n */\n\n/** An already-uploaded file a waiting message refers to (never a file picked while offline). */\nexport interface ChatOutboxFile {\n /** A server-backed URL (an asset's image route); never a `blob:` or `data:` URL. */\n url: string;\n mediaType: string;\n filename?: string;\n}\n\n/**\n * A file already in Athena that a waiting message carries: an upload that finished before Athena\n * went out of reach (a file picked while it can't be reached is never attached: it can't upload).\n * Its reference rides the message's text, as a sent message's does; this copy names it so the\n * outbox can ask Athena, right before the message goes, that the file is still there and still the\n * person's to open (`ChatOutboxOptions.checkAttachments`).\n */\nexport interface ChatOutboxAttachment {\n assetId: string;\n /** Its name, as the composer showed it. */\n title: string;\n mediaType?: string;\n}\n\nexport interface ChatOutboxEntry {\n /**\n * The message id, minted when the message was typed. The message is sent under it, so a send\n * whose answer was lost can never post twice: statewire replays the same statement, and the\n * thread's host refuses an id it already holds (`duplicate-id`) or re-places one still queued.\n */\n id: string;\n threadId: string;\n /** The text exactly as it will be sent (what the model reads). */\n text: string;\n /** The composer draft the text was serialized from, for giving it back exactly as typed. */\n composerMarkdown?: string;\n /**\n * The draft as a composer whose draft is HTML holds it (the phone's: its mention chips\n * included), given back exactly on Cancel or Edit.\n */\n composerHtml?: string;\n /** The message metadata frozen when it was typed (its composer markdown, …). */\n metadata?: Record<string, unknown>;\n /** Already-uploaded files that ride along. */\n files?: ChatOutboxFile[];\n /**\n * The files already in Athena the message carries (uploads that finished before Athena went out\n * of reach), asked about right before it goes: a refused one keeps it as \"Couldn't send\".\n */\n attachments?: ChatOutboxAttachment[];\n /** When it was typed (epoch ms): a message that waited more than a day asks before it goes. */\n queuedAt: number;\n /** Typed into a chat that had never started: reopening that chat finds it again. */\n newChat: boolean;\n}\n\n/** Which window is handing a message to its conversation right now, and since when. */\nexport interface ChatOutboxClaim {\n by: string;\n at: number;\n}\n\n/** Why a hand-off failed. */\nexport type ChatOutboxFailureReason =\n /** The conversation refused it: stopped, or parked on an authorization denial. */\n | \"refused\"\n /** The send failed before the conversation's journal took it. */\n | \"error\"\n /** It could not be claimed for sending (the store failed). */\n | \"prepare\"\n /** Athena refused one of its attachments: gone, or no longer the person's to open. */\n | \"attachment\";\n\n/**\n * A failed hand-off, kept with the message: every window (and the next mount) shows it as\n * \"Couldn't send\", and nothing sends it again until the person says Retry.\n */\nexport interface ChatOutboxFailure {\n reason: ChatOutboxFailureReason;\n at: number;\n}\n\nexport interface ChatOutboxRecord {\n entry: ChatOutboxEntry;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n /**\n * Kept only in this store's memory, because this device could not keep it: it waits as long as\n * the page or the app stays open.\n */\n memoryOnly?: true;\n}\n\nexport type ChatOutboxClaimResult = \"claimed\" | \"taken\" | \"gone\";\n\n/**\n * A person's waiting messages. A store keeps every message put into it until it is removed: on\n * the device when it can (`put` resolves `true`), otherwise in its own memory for as long as the\n * store lives (`false`, listed with `memoryOnly`), so a host keeps one store per person and a\n * thread switch never loses a message (`withChatOutboxMemoryFallback`).\n */\nexport interface ChatOutboxStore {\n /** The thread's waiting messages, oldest first. Resolves `[]` when nothing can be read. */\n list(threadId: string): Promise<ChatOutboxRecord[]>;\n /** Every waiting message of this person, oldest first (sign-out, reopening a new chat). */\n listAll(): Promise<ChatOutboxRecord[]>;\n /**\n * How many messages wait, counted without opening them, so a record this build cannot read\n * still counts (a sign-out asks rather than deleting unread work); the ones kept only in memory\n * count too. Rejects when the device's records cannot be counted at all.\n */\n count(): Promise<number>;\n /** Keep a waiting message. Resolves `false` when it could not be kept on this device. */\n put(entry: ChatOutboxEntry): Promise<boolean>;\n remove(threadId: string, id: string): Promise<void>;\n /**\n * Claim a message for handing it to its conversation, atomically across windows: `taken` while\n * another claimant's claim is younger than `leaseMs`, `gone` when the record no longer exists\n * (cancelled, discarded, or handed by another window).\n */\n claim(input: {\n threadId: string;\n id: string;\n claimant: string;\n now: number;\n leaseMs: number;\n }): Promise<ChatOutboxClaimResult>;\n /** Drop `claimant`'s claim (its hand-off never reached the conversation). */\n release(input: { threadId: string; id: string; claimant: string }): Promise<void>;\n /** Keep the hand-off's failure with the message, dropping `claimant`'s claim. */\n markFailed(input: { threadId: string; id: string; claimant: string; failure: ChatOutboxFailure }): Promise<void>;\n /** The person said Retry: the message may be sent again. */\n clearFailure(input: { threadId: string; id: string }): Promise<void>;\n /** Every waiting message of this person, gone (a sign-out the person chose). */\n clear(): Promise<void>;\n /** Called after any change, this window's or another's. */\n subscribe(listener: () => void): () => void;\n}\n\nexport const CHAT_OUTBOX_DATABASE = \"athena-chat-outbox:v1\";\n/** The sealed record's format; an older build leaves a newer one alone. */\nexport const CHAT_OUTBOX_FORMAT = 1;\n\nconst DATABASE_VERSION = 1;\nconst ENTRIES_STORE = \"entries\";\nconst BY_OWNER = \"byOwner\";\nconst BY_OWNER_THREAD = \"byOwnerThread\";\n\ninterface StoredRecord {\n key: string;\n owner: string;\n thread: string;\n at: number;\n format: number;\n sealed: Uint8Array;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n}\n\nfunction upgradeOutboxDatabase(db: IdbDatabase): void {\n if (!db.objectStoreNames.contains(ENTRIES_STORE)) {\n const store = db.createObjectStore(ENTRIES_STORE, { keyPath: \"key\" });\n store.createIndex(BY_OWNER, \"owner\", { unique: false });\n store.createIndex(BY_OWNER_THREAD, [\"owner\", \"thread\"], { unique: false });\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** A record this build may replace or delete: none, or one in a format it writes itself. */\nfunction isOwnFormat(record: unknown): boolean {\n if (!isRecord(record)) return true;\n const format = record.format;\n return typeof format !== \"number\" || format <= CHAT_OUTBOX_FORMAT;\n}\n\nexport function readChatOutboxClaim(value: unknown): ChatOutboxClaim | undefined {\n if (!isRecord(value) || typeof value.by !== \"string\" || typeof value.at !== \"number\") return undefined;\n return { by: value.by, at: value.at };\n}\n\nconst FAILURE_REASONS: ReadonlySet<string> = new Set<ChatOutboxFailureReason>([\n \"refused\",\n \"error\",\n \"prepare\",\n \"attachment\",\n]);\n\n/** A failure read back from storage; an unknown reason reads as a send error (still failed). */\nexport function readChatOutboxFailure(value: unknown): ChatOutboxFailure | undefined {\n if (!isRecord(value) || typeof value.at !== \"number\") return undefined;\n const reason = typeof value.reason === \"string\" && FAILURE_REASONS.has(value.reason) ? value.reason : \"error\";\n return { reason: reason as ChatOutboxFailureReason, at: value.at };\n}\n\nconst readClaim = readChatOutboxClaim;\n\nfunction readFiles(value: unknown): ChatOutboxFile[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const files: ChatOutboxFile[] = [];\n for (const file of value) {\n if (!isRecord(file) || typeof file.url !== \"string\" || typeof file.mediaType !== \"string\") continue;\n files.push({\n url: file.url,\n mediaType: file.mediaType,\n ...(typeof file.filename === \"string\" && { filename: file.filename }),\n });\n }\n return files.length > 0 ? files : undefined;\n}\n\nfunction readAttachments(value: unknown): ChatOutboxAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const attachments: ChatOutboxAttachment[] = [];\n for (const attachment of value) {\n if (!isRecord(attachment) || typeof attachment.assetId !== \"string\" || attachment.assetId.length === 0) {\n continue;\n }\n attachments.push({\n assetId: attachment.assetId,\n title: typeof attachment.title === \"string\" ? attachment.title : \"\",\n ...(typeof attachment.mediaType === \"string\" && { mediaType: attachment.mediaType }),\n });\n }\n return attachments.length > 0 ? attachments : undefined;\n}\n\n/** A sealed entry read back, or `null` when it is not one. */\nexport function parseChatOutboxEntry(value: unknown): ChatOutboxEntry | null {\n if (!isRecord(value)) return null;\n const { id, threadId, text, queuedAt } = value;\n if (typeof id !== \"string\" || typeof threadId !== \"string\" || typeof text !== \"string\") return null;\n if (typeof queuedAt !== \"number\" || !Number.isFinite(queuedAt)) return null;\n const files = readFiles(value.files);\n const attachments = readAttachments(value.attachments);\n return {\n id,\n threadId,\n text,\n queuedAt,\n newChat: value.newChat === true,\n ...(typeof value.composerMarkdown === \"string\" && { composerMarkdown: value.composerMarkdown }),\n ...(typeof value.composerHtml === \"string\" && { composerHtml: value.composerHtml }),\n ...(isRecord(value.metadata) && { metadata: value.metadata }),\n ...(files && { files }),\n ...(attachments && { attachments }),\n };\n}\n\nfunction byQueuedAt(a: ChatOutboxRecord, b: ChatOutboxRecord): number {\n return a.entry.queuedAt - b.entry.queuedAt;\n}\n\n/** A tiny change bus: this window's listeners, plus other windows' through a BroadcastChannel. */\nfunction createChangeBus(channelName: string): {\n notify: () => void;\n subscribe: (listener: () => void) => () => void;\n} {\n const listeners = new Set<() => void>();\n type Channel = {\n postMessage(message: unknown): void;\n onmessage: ((event: unknown) => void) | null;\n close(): void;\n };\n const ChannelCtor = (globalThis as { BroadcastChannel?: new (name: string) => Channel }).BroadcastChannel;\n let channel: Channel | null = null;\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n };\n return {\n notify() {\n emit();\n try {\n channel?.postMessage(\"changed\");\n } catch {\n // Another window learns of the change on its next read.\n }\n },\n subscribe(listener) {\n listeners.add(listener);\n if (!channel && ChannelCtor) {\n try {\n channel = new ChannelCtor(channelName);\n channel.onmessage = emit;\n } catch {\n channel = null;\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && channel) {\n channel.close();\n channel = null;\n }\n };\n },\n };\n}\n\n/** What a store asks the host's key for: opening kept messages, or keeping a new one. */\nexport type ChatOutboxKeyPurpose = \"read\" | \"write\";\n\nexport interface IndexedDbChatOutboxStoreOptions {\n /** The person this store belongs to (with the environment, the key's scope). */\n ownerId: string;\n /**\n * The person's AES-GCM key, or `null` when there is none for `purpose`: `read` opens what is\n * already kept (a key that exists, whatever the deployment's policy now says, so kept messages\n * always show and drain); `write` keeps a new message (a key only while the deployment lets\n * devices keep copies). Without a key to write with, `put` resolves `false` and the message\n * waits in this store's memory.\n */\n getKey: (ownerId: string, purpose: ChatOutboxKeyPurpose) => Promise<CryptoKey | null>;\n /** An `IDBFactory` (the browser's `indexedDB` by default; a fake one in tests). */\n factory?: object;\n databaseName?: string;\n}\n\n/**\n * The browser's outbox store (Olympus, the desktop app): IndexedDB records sealed with AES-GCM-256\n * under the person's key, the owner and the thread hashed for the index and bound to each record\n * as additional data, so the database names no one and a record moved to another owner or thread\n * fails to open. A message it cannot keep on the device waits in its memory\n * ({@link withChatOutboxMemoryFallback}).\n */\nexport function createIndexedDbChatOutboxStore(options: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n return withChatOutboxMemoryFallback(createSealedIndexedDbChatOutboxStore(options));\n}\n\nfunction createSealedIndexedDbChatOutboxStore({\n ownerId,\n getKey,\n factory = idbGlobals.indexedDB,\n databaseName = CHAT_OUTBOX_DATABASE,\n}: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n const database = lazySealedDatabase({\n factory,\n name: databaseName,\n version: DATABASE_VERSION,\n upgrade: upgradeOutboxDatabase,\n });\n const bus = createChangeBus(`${databaseName}:changes`);\n const keyOf = async (purpose: ChatOutboxKeyPurpose): Promise<CryptoKey | null> => {\n try {\n return await getKey(ownerId, purpose);\n } catch {\n return null;\n }\n };\n const ownerHash = () => sha256Hex(ownerId);\n const threadHash = (threadId: string) => sha256Hex(`${ownerId}|${threadId}`);\n const recordKey = (threadId: string, id: string) => sha256Hex(`${ownerId}|${threadId}|${id}`);\n const aadOf = (owner: string, thread: string, key: string) =>\n `athena-chat-outbox|${CHAT_OUTBOX_FORMAT}|${owner}|${thread}|${key}`;\n\n // Writes are serialized so a claim never races a put or a clear in this window; IndexedDB's own\n // transactions order them against other windows.\n let queue: Promise<unknown> = Promise.resolve();\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const run = queue.then(task, task);\n queue = run.catch(() => undefined);\n return run;\n };\n\n const openRecords = async (records: StoredRecord[]): Promise<ChatOutboxRecord[]> => {\n if (records.length === 0) return [];\n const cryptoKey = await keyOf(\"read\");\n if (!cryptoKey) return [];\n const opened: ChatOutboxRecord[] = [];\n for (const record of records) {\n if (!record || record.format > CHAT_OUTBOX_FORMAT) continue;\n try {\n const value = await openJson({\n key: cryptoKey,\n sealed: record.sealed,\n aad: aadOf(record.owner, record.thread, record.key),\n });\n const entry = parseChatOutboxEntry(value);\n if (!entry) continue;\n const claim = readClaim(record.claim);\n const failure = readChatOutboxFailure(record.failure);\n opened.push({ entry, ...(claim && { claim }), ...(failure && { failure }) });\n } catch {\n // Not readable with this key: left as it is (it may be the only copy of someone's work).\n }\n }\n return opened.sort(byQueuedAt);\n };\n\n return {\n async list(threadId) {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const thread = await threadHash(threadId);\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER_THREAD).getAll([owner, thread]),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async listAll() {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAll(owner),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async count() {\n const db = await database();\n if (!db) return 0;\n const owner = await ownerHash();\n const keys = await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAllKeys(owner),\n );\n return keys.length;\n },\n put(entry) {\n return enqueue(async () => {\n try {\n const db = await database();\n if (!db) return false;\n const cryptoKey = await keyOf(\"write\");\n if (!cryptoKey) return false;\n const owner = await ownerHash();\n const thread = await threadHash(entry.threadId);\n const key = await recordKey(entry.threadId, entry.id);\n const sealed = await sealJson({ key: cryptoKey, value: entry, aad: aadOf(owner, thread, key) });\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = await requestToPromise(store.get(key));\n let kept = true;\n // A newer build's record of the same message stays as that build wrote it.\n if (isOwnFormat(existing)) {\n store.put({\n key,\n owner,\n thread,\n at: entry.queuedAt,\n format: CHAT_OUTBOX_FORMAT,\n sealed,\n } satisfies StoredRecord);\n } else {\n kept = false;\n }\n await transactionDone(tx);\n bus.notify();\n return kept;\n } catch {\n return false;\n }\n });\n },\n remove(threadId, id) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n if (isOwnFormat(await requestToPromise(store.get(key)))) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n claim({ threadId, id, claimant, now, leaseMs }) {\n return enqueue(async () => {\n const db = await database();\n // No database: nothing else can hand this message, so the one window holding it may.\n if (!db) return \"claimed\" as const;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n let result: ChatOutboxClaimResult;\n if (!existing) {\n result = \"gone\";\n } else {\n const claim = readClaim(existing.claim);\n if (claim && claim.by !== claimant && now - claim.at < leaseMs) {\n result = \"taken\";\n } else {\n store.put({ ...existing, claim: { by: claimant, at: now } } satisfies StoredRecord);\n result = \"claimed\";\n }\n }\n await transactionDone(tx);\n if (result === \"claimed\") bus.notify();\n return result;\n });\n },\n release({ threadId, id, claimant }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && readClaim(existing.claim)?.by === claimant) {\n const { claim: _claim, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n markFailed({ threadId, id, claimant, failure }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && isOwnFormat(existing)) {\n const { claim, ...rest } = existing;\n const keepClaim = claim !== undefined && readClaim(claim)?.by !== claimant;\n store.put({\n ...rest,\n ...(keepClaim && { claim }),\n failure: { reason: failure.reason, at: failure.at },\n } satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clearFailure({ threadId, id }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing?.failure !== undefined && isOwnFormat(existing)) {\n const { failure: _failure, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clear() {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const owner = await ownerHash();\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const keys = await requestToPromise(store.index(BY_OWNER).getAllKeys(owner));\n for (const key of keys) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n subscribe: (listener) => bus.subscribe(listener),\n };\n}\n\n/** Records kept in a process's memory, with the claim and failure rules every store shares. */\nfunction createRecordMap() {\n const records = new Map<string, ChatOutboxRecord>();\n const keyOf = (threadId: string, id: string) => `${threadId}\\u0000${id}`;\n return {\n records,\n keyOf,\n has: (threadId: string, id: string) => records.has(keyOf(threadId, id)),\n sorted: (filter: (record: ChatOutboxRecord) => boolean) =>\n [...records.values()].filter(filter).map((record) => ({ ...record })).sort(byQueuedAt),\n claim({ threadId, id, claimant, now, leaseMs }: Parameters<ChatOutboxStore[\"claim\"]>[0]): ChatOutboxClaimResult {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return \"gone\";\n if (record.claim && record.claim.by !== claimant && now - record.claim.at < leaseMs) return \"taken\";\n records.set(key, { ...record, claim: { by: claimant, at: now } });\n return \"claimed\";\n },\n release({ threadId, id, claimant }: Parameters<ChatOutboxStore[\"release\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (record?.claim?.by !== claimant) return;\n const { claim: _claim, ...rest } = record;\n records.set(key, rest);\n },\n markFailed({ threadId, id, claimant, failure }: Parameters<ChatOutboxStore[\"markFailed\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return;\n const { claim, ...rest } = record;\n records.set(key, { ...rest, ...(claim && claim.by !== claimant && { claim }), failure: { ...failure } });\n },\n clearFailure({ threadId, id }: Parameters<ChatOutboxStore[\"clearFailure\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record?.failure) return;\n const { failure: _failure, ...rest } = record;\n records.set(key, rest);\n },\n };\n}\n\nfunction createListenerSet() {\n const listeners = new Set<() => void>();\n return {\n notify() {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/**\n * The store contract's memory half, around a store that keeps messages on the device: a message\n * `inner` could not keep (no key, no storage, a failed write) waits in this wrapper's memory,\n * listed with `memoryOnly` and counted, for as long as the wrapper lives. A host keeps one store\n * per person, so switching conversations never loses such a message, and a sign-out still asks\n * about it.\n */\nexport function withChatOutboxMemoryFallback(inner: ChatOutboxStore): ChatOutboxStore {\n const memory = createRecordMap();\n const local = createListenerSet();\n const remembered = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => ({ ...record, memoryOnly: true as const }));\n const merged = (kept: ChatOutboxRecord[], extra: ChatOutboxRecord[]): ChatOutboxRecord[] => {\n const ids = new Set(kept.map((record) => record.entry.id));\n return [...kept, ...extra.filter((record) => !ids.has(record.entry.id))].sort(byQueuedAt);\n };\n return {\n async list(threadId) {\n const kept = await inner.list(threadId).catch(() => []);\n return merged(kept, remembered((record) => record.entry.threadId === threadId));\n },\n async listAll() {\n const kept = await inner.listAll().catch(() => []);\n return merged(kept, remembered(() => true));\n },\n async count() {\n return (await inner.count()) + memory.records.size;\n },\n async put(entry) {\n let kept = false;\n try {\n kept = await inner.put(entry);\n } catch {\n kept = false;\n }\n if (kept) return true;\n memory.records.set(memory.keyOf(entry.threadId, entry.id), { entry });\n local.notify();\n return false;\n },\n async remove(threadId, id) {\n if (memory.records.delete(memory.keyOf(threadId, id))) {\n local.notify();\n return;\n }\n await inner.remove(threadId, id);\n },\n async claim(input) {\n if (!memory.has(input.threadId, input.id)) return inner.claim(input);\n const result = memory.claim(input);\n local.notify();\n return result;\n },\n async release(input) {\n if (!memory.has(input.threadId, input.id)) return inner.release(input);\n memory.release(input);\n local.notify();\n },\n async markFailed(input) {\n if (!memory.has(input.threadId, input.id)) return inner.markFailed(input);\n memory.markFailed(input);\n local.notify();\n },\n async clearFailure(input) {\n if (!memory.has(input.threadId, input.id)) return inner.clearFailure(input);\n memory.clearFailure(input);\n local.notify();\n },\n async clear() {\n memory.records.clear();\n local.notify();\n await inner.clear();\n },\n subscribe(listener) {\n const stopLocal = local.subscribe(listener);\n const stopInner = inner.subscribe(listener);\n return () => {\n stopLocal();\n stopInner();\n };\n },\n };\n}\n\n/**\n * An outbox store in this process's memory: for tests, and for a host that keeps nothing on the\n * device (the messages then wait only as long as the page or app stays open). `durable: false`\n * makes every `put` report that nothing was kept on the device, and lists its messages as\n * `memoryOnly`.\n */\nexport function createMemoryChatOutboxStore({ durable = true }: { durable?: boolean } = {}): ChatOutboxStore & {\n /** The records as stored (tests). */\n records(): ChatOutboxRecord[];\n} {\n const memory = createRecordMap();\n const listeners = createListenerSet();\n const listed = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => (durable ? record : { ...record, memoryOnly: true as const }));\n return {\n records: () => listed(() => true),\n async list(threadId) {\n return listed((record) => record.entry.threadId === threadId);\n },\n async listAll() {\n return listed(() => true);\n },\n async count() {\n return memory.records.size;\n },\n async put(entry) {\n const key = memory.keyOf(entry.threadId, entry.id);\n const existing = memory.records.get(key);\n memory.records.set(key, { ...existing, entry });\n listeners.notify();\n return durable;\n },\n async remove(threadId, id) {\n memory.records.delete(memory.keyOf(threadId, id));\n listeners.notify();\n },\n async claim(input) {\n const result = memory.claim(input);\n if (result === \"claimed\") listeners.notify();\n return result;\n },\n async release(input) {\n memory.release(input);\n listeners.notify();\n },\n async markFailed(input) {\n memory.markFailed(input);\n listeners.notify();\n },\n async clearFailure(input) {\n memory.clearFailure(input);\n listeners.notify();\n },\n async clear() {\n memory.records.clear();\n listeners.notify();\n },\n subscribe: (listener) => listeners.subscribe(listener),\n };\n}\n"],"names":["idbGlobals","lazySealedDatabase","sha256Hex","openJson","requestToPromise","sealJson","transactionDone"],"mappings":";;;AA+JO,MAAM,uBAAuB;AAE7B,MAAM,qBAAqB;AAElC,MAAM,mBAAmB;AACzB,MAAM,gBAAgB;AACtB,MAAM,WAAW;AACjB,MAAM,kBAAkB;AAaxB,SAAS,sBAAsB,IAAuB;AACpD,MAAI,CAAC,GAAG,iBAAiB,SAAS,aAAa,GAAG;AAChD,UAAM,QAAQ,GAAG,kBAAkB,eAAe,EAAE,SAAS,OAAO;AACpE,UAAM,YAAY,UAAU,SAAS,EAAE,QAAQ,OAAO;AACtD,UAAM,YAAY,iBAAiB,CAAC,SAAS,QAAQ,GAAG,EAAE,QAAQ,OAAO;AAAA,EAC3E;AACF;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAGA,SAAS,YAAY,QAA0B;AAC7C,MAAI,CAAC,SAAS,MAAM,EAAG,QAAO;AAC9B,QAAM,SAAS,OAAO;AACtB,SAAO,OAAO,WAAW,YAAY,UAAU;AACjD;AAEO,SAAS,oBAAoB,OAA6C;AAC/E,MAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO,YAAY,OAAO,MAAM,OAAO,SAAU,QAAO;AAC7F,SAAO,EAAE,IAAI,MAAM,IAAI,IAAI,MAAM,GAAA;AACnC;AAEA,MAAM,sCAA2C,IAA6B;AAAA,EAC5E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,sBAAsB,OAA+C;AACnF,MAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO,SAAU,QAAO;AAC7D,QAAM,SAAS,OAAO,MAAM,WAAW,YAAY,gBAAgB,IAAI,MAAM,MAAM,IAAI,MAAM,SAAS;AACtG,SAAO,EAAE,QAA2C,IAAI,MAAM,GAAA;AAChE;AAEA,MAAM,YAAY;AAElB,SAAS,UAAU,OAA8C;AAC/D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,QAAM,QAA0B,CAAA;AAChC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,cAAc,SAAU;AAC3F,UAAM,KAAK;AAAA,MACT,KAAK,KAAK;AAAA,MACV,WAAW,KAAK;AAAA,MAChB,GAAI,OAAO,KAAK,aAAa,YAAY,EAAE,UAAU,KAAK,SAAA;AAAA,IAAS,CACpE;AAAA,EACH;AACA,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,gBAAgB,OAAoD;AAC3E,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,QAAM,cAAsC,CAAA;AAC5C,aAAW,cAAc,OAAO;AAC9B,QAAI,CAAC,SAAS,UAAU,KAAK,OAAO,WAAW,YAAY,YAAY,WAAW,QAAQ,WAAW,GAAG;AACtG;AAAA,IACF;AACA,gBAAY,KAAK;AAAA,MACf,SAAS,WAAW;AAAA,MACpB,OAAO,OAAO,WAAW,UAAU,WAAW,WAAW,QAAQ;AAAA,MACjE,GAAI,OAAO,WAAW,cAAc,YAAY,EAAE,WAAW,WAAW,UAAA;AAAA,IAAU,CACnF;AAAA,EACH;AACA,SAAO,YAAY,SAAS,IAAI,cAAc;AAChD;AAGO,SAAS,qBAAqB,OAAwC;AAC3E,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,EAAE,IAAI,UAAU,MAAM,aAAa;AACzC,MAAI,OAAO,OAAO,YAAY,OAAO,aAAa,YAAY,OAAO,SAAS,SAAU,QAAO;AAC/F,MAAI,OAAO,aAAa,YAAY,CAAC,OAAO,SAAS,QAAQ,EAAG,QAAO;AACvE,QAAM,QAAQ,UAAU,MAAM,KAAK;AACnC,QAAM,cAAc,gBAAgB,MAAM,WAAW;AACrD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,MAAM,YAAY;AAAA,IAC3B,GAAI,OAAO,MAAM,qBAAqB,YAAY,EAAE,kBAAkB,MAAM,iBAAA;AAAA,IAC5E,GAAI,OAAO,MAAM,iBAAiB,YAAY,EAAE,cAAc,MAAM,aAAA;AAAA,IACpE,GAAI,SAAS,MAAM,QAAQ,KAAK,EAAE,UAAU,MAAM,SAAA;AAAA,IAClD,GAAI,SAAS,EAAE,MAAA;AAAA,IACf,GAAI,eAAe,EAAE,YAAA;AAAA,EAAY;AAErC;AAEA,SAAS,WAAW,GAAqB,GAA6B;AACpE,SAAO,EAAE,MAAM,WAAW,EAAE,MAAM;AACpC;AAGA,SAAS,gBAAgB,aAGvB;AACA,QAAM,gCAAgB,IAAA;AAMtB,QAAM,cAAe,WAAoE;AACzF,MAAI,UAA0B;AAC9B,QAAM,OAAO,MAAM;AACjB,eAAW,YAAY,WAAW;AAChC,UAAI;AACF,iBAAA;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS;AACP,WAAA;AACA,UAAI;AACF,2CAAS,YAAY;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IACA,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,UAAI,CAAC,WAAW,aAAa;AAC3B,YAAI;AACF,oBAAU,IAAI,YAAY,WAAW;AACrC,kBAAQ,YAAY;AAAA,QACtB,QAAQ;AACN,oBAAU;AAAA,QACZ;AAAA,MACF;AACA,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AACzB,YAAI,UAAU,SAAS,KAAK,SAAS;AACnC,kBAAQ,MAAA;AACR,oBAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EAAA;AAEJ;AA4BO,SAAS,+BAA+B,SAA2D;AACxG,SAAO,6BAA6B,qCAAqC,OAAO,CAAC;AACnF;AAEA,SAAS,qCAAqC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA,UAAUA,UAAAA,WAAW;AAAA,EACrB,eAAe;AACjB,GAAqD;AACnD,QAAM,WAAWC,UAAAA,mBAAmB;AAAA,IAClC;AAAA,IACA,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AAAA,EAAA,CACV;AACD,QAAM,MAAM,gBAAgB,GAAG,YAAY,UAAU;AACrD,QAAM,QAAQ,OAAO,YAA6D;AAChF,QAAI;AACF,aAAO,MAAM,OAAO,SAAS,OAAO;AAAA,IACtC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,YAAY,MAAMC,UAAAA,UAAU,OAAO;AACzC,QAAM,aAAa,CAAC,aAAqBA,UAAAA,UAAU,GAAG,OAAO,IAAI,QAAQ,EAAE;AAC3E,QAAM,YAAY,CAAC,UAAkB,OAAeA,UAAAA,UAAU,GAAG,OAAO,IAAI,QAAQ,IAAI,EAAE,EAAE;AAC5F,QAAM,QAAQ,CAAC,OAAe,QAAgB,QAC5C,sBAAsB,kBAAkB,IAAI,KAAK,IAAI,MAAM,IAAI,GAAG;AAIpE,MAAI,QAA0B,QAAQ,QAAA;AACtC,QAAM,UAAU,CAAI,SAAuC;AACzD,UAAM,MAAM,MAAM,KAAK,MAAM,IAAI;AACjC,YAAQ,IAAI,MAAM,MAAM,MAAS;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,OAAO,YAAyD;AAClF,QAAI,QAAQ,WAAW,EAAG,QAAO,CAAA;AACjC,UAAM,YAAY,MAAM,MAAM,MAAM;AACpC,QAAI,CAAC,UAAW,QAAO,CAAA;AACvB,UAAM,SAA6B,CAAA;AACnC,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,UAAU,OAAO,SAAS,mBAAoB;AACnD,UAAI;AACF,cAAM,QAAQ,MAAMC,mBAAS;AAAA,UAC3B,KAAK;AAAA,UACL,QAAQ,OAAO;AAAA,UACf,KAAK,MAAM,OAAO,OAAO,OAAO,QAAQ,OAAO,GAAG;AAAA,QAAA,CACnD;AACD,cAAM,QAAQ,qBAAqB,KAAK;AACxC,YAAI,CAAC,MAAO;AACZ,cAAM,QAAQ,UAAU,OAAO,KAAK;AACpC,cAAM,UAAU,sBAAsB,OAAO,OAAO;AACpD,eAAO,KAAK,EAAE,OAAO,GAAI,SAAS,EAAE,MAAA,GAAU,GAAI,WAAW,EAAE,QAAA,GAAY;AAAA,MAC7E,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,OAAO,KAAK,UAAU;AAAA,EAC/B;AAEA,SAAO;AAAA,IACL,MAAM,KAAK,UAAU;AACnB,UAAI;AACF,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI,QAAO,CAAA;AAChB,cAAM,QAAQ,MAAM,UAAA;AACpB,cAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,cAAM,UAAW,MAAMC,UAAAA;AAAAA,UACrB,GAAG,YAAY,eAAe,UAAU,EAAE,YAAY,aAAa,EAAE,MAAM,eAAe,EAAE,OAAO,CAAC,OAAO,MAAM,CAAC;AAAA,QAAA;AAEpH,eAAO,MAAM,YAAY,OAAO;AAAA,MAClC,QAAQ;AACN,eAAO,CAAA;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM,UAAU;AACd,UAAI;AACF,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI,QAAO,CAAA;AAChB,cAAM,QAAQ,MAAM,UAAA;AACpB,cAAM,UAAW,MAAMA,UAAAA;AAAAA,UACrB,GAAG,YAAY,eAAe,UAAU,EAAE,YAAY,aAAa,EAAE,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,QAAA;AAEnG,eAAO,MAAM,YAAY,OAAO;AAAA,MAClC,QAAQ;AACN,eAAO,CAAA;AAAA,MACT;AAAA,IACF;AAAA,IACA,MAAM,QAAQ;AACZ,YAAM,KAAK,MAAM,SAAA;AACjB,UAAI,CAAC,GAAI,QAAO;AAChB,YAAM,QAAQ,MAAM,UAAA;AACpB,YAAM,OAAO,MAAMA,UAAAA;AAAAA,QACjB,GAAG,YAAY,eAAe,UAAU,EAAE,YAAY,aAAa,EAAE,MAAM,QAAQ,EAAE,WAAW,KAAK;AAAA,MAAA;AAEvG,aAAO,KAAK;AAAA,IACd;AAAA,IACA,IAAI,OAAO;AACT,aAAO,QAAQ,YAAY;AACzB,YAAI;AACF,gBAAM,KAAK,MAAM,SAAA;AACjB,cAAI,CAAC,GAAI,QAAO;AAChB,gBAAM,YAAY,MAAM,MAAM,OAAO;AACrC,cAAI,CAAC,UAAW,QAAO;AACvB,gBAAM,QAAQ,MAAM,UAAA;AACpB,gBAAM,SAAS,MAAM,WAAW,MAAM,QAAQ;AAC9C,gBAAM,MAAM,MAAM,UAAU,MAAM,UAAU,MAAM,EAAE;AACpD,gBAAM,SAAS,MAAMC,UAAAA,SAAS,EAAE,KAAK,WAAW,OAAO,OAAO,KAAK,MAAM,OAAO,QAAQ,GAAG,GAAG;AAC9F,gBAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,gBAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,gBAAM,WAAW,MAAMD,UAAAA,iBAAiB,MAAM,IAAI,GAAG,CAAC;AACtD,cAAI,OAAO;AAEX,cAAI,YAAY,QAAQ,GAAG;AACzB,kBAAM,IAAI;AAAA,cACR;AAAA,cACA;AAAA,cACA;AAAA,cACA,IAAI,MAAM;AAAA,cACV,QAAQ;AAAA,cACR;AAAA,YAAA,CACsB;AAAA,UAC1B,OAAO;AACL,mBAAO;AAAA,UACT;AACA,gBAAME,UAAAA,gBAAgB,EAAE;AACxB,cAAI,OAAA;AACJ,iBAAO;AAAA,QACT,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,OAAO,UAAU,IAAI;AACnB,aAAO,QAAQ,YAAY;AACzB,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI;AACT,cAAM,MAAM,MAAM,UAAU,UAAU,EAAE;AACxC,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,YAAI,YAAY,MAAMF,2BAAiB,MAAM,IAAI,GAAG,CAAC,CAAC,EAAG,OAAM,OAAO,GAAG;AACzE,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,OAAA;AAAA,MACN,CAAC;AAAA,IACH;AAAA,IACA,MAAM,EAAE,UAAU,IAAI,UAAU,KAAK,WAAW;AAC9C,aAAO,QAAQ,YAAY;AACzB,cAAM,KAAK,MAAM,SAAA;AAEjB,YAAI,CAAC,GAAI,QAAO;AAChB,cAAM,MAAM,MAAM,UAAU,UAAU,EAAE;AACxC,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,cAAM,WAAY,MAAMF,UAAAA,iBAAiB,MAAM,IAAI,GAAG,CAAC;AACvD,YAAI;AACJ,YAAI,CAAC,UAAU;AACb,mBAAS;AAAA,QACX,OAAO;AACL,gBAAM,QAAQ,UAAU,SAAS,KAAK;AACtC,cAAI,SAAS,MAAM,OAAO,YAAY,MAAM,MAAM,KAAK,SAAS;AAC9D,qBAAS;AAAA,UACX,OAAO;AACL,kBAAM,IAAI,EAAE,GAAG,UAAU,OAAO,EAAE,IAAI,UAAU,IAAI,IAAA,GAA8B;AAClF,qBAAS;AAAA,UACX;AAAA,QACF;AACA,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,WAAW,UAAW,KAAI,OAAA;AAC9B,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,QAAQ,EAAE,UAAU,IAAI,YAAY;AAClC,aAAO,QAAQ,YAAY;;AACzB,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI;AACT,cAAM,MAAM,MAAM,UAAU,UAAU,EAAE;AACxC,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,cAAM,WAAY,MAAMF,UAAAA,iBAAiB,MAAM,IAAI,GAAG,CAAC;AACvD,YAAI,cAAY,eAAU,SAAS,KAAK,MAAxB,mBAA2B,QAAO,UAAU;AAC1D,gBAAM,EAAE,OAAO,QAAQ,GAAG,SAAS;AACnC,gBAAM,IAAI,IAA2B;AAAA,QACvC;AACA,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,OAAA;AAAA,MACN,CAAC;AAAA,IACH;AAAA,IACA,WAAW,EAAE,UAAU,IAAI,UAAU,WAAW;AAC9C,aAAO,QAAQ,YAAY;;AACzB,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI;AACT,cAAM,MAAM,MAAM,UAAU,UAAU,EAAE;AACxC,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,cAAM,WAAY,MAAMF,UAAAA,iBAAiB,MAAM,IAAI,GAAG,CAAC;AACvD,YAAI,YAAY,YAAY,QAAQ,GAAG;AACrC,gBAAM,EAAE,OAAO,GAAG,KAAA,IAAS;AAC3B,gBAAM,YAAY,UAAU,YAAa,eAAU,KAAK,MAAf,mBAAkB,QAAO;AAClE,gBAAM,IAAI;AAAA,YACR,GAAG;AAAA,YACH,GAAI,aAAa,EAAE,MAAA;AAAA,YACnB,SAAS,EAAE,QAAQ,QAAQ,QAAQ,IAAI,QAAQ,GAAA;AAAA,UAAG,CAC5B;AAAA,QAC1B;AACA,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,OAAA;AAAA,MACN,CAAC;AAAA,IACH;AAAA,IACA,aAAa,EAAE,UAAU,MAAM;AAC7B,aAAO,QAAQ,YAAY;AACzB,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI;AACT,cAAM,MAAM,MAAM,UAAU,UAAU,EAAE;AACxC,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,cAAM,WAAY,MAAMF,UAAAA,iBAAiB,MAAM,IAAI,GAAG,CAAC;AACvD,aAAI,qCAAU,aAAY,UAAa,YAAY,QAAQ,GAAG;AAC5D,gBAAM,EAAE,SAAS,UAAU,GAAG,SAAS;AACvC,gBAAM,IAAI,IAA2B;AAAA,QACvC;AACA,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,OAAA;AAAA,MACN,CAAC;AAAA,IACH;AAAA,IACA,QAAQ;AACN,aAAO,QAAQ,YAAY;AACzB,cAAM,KAAK,MAAM,SAAA;AACjB,YAAI,CAAC,GAAI;AACT,cAAM,QAAQ,MAAM,UAAA;AACpB,cAAM,KAAK,GAAG,YAAY,eAAe,WAAW;AACpD,cAAM,QAAQ,GAAG,YAAY,aAAa;AAC1C,cAAM,OAAO,MAAMF,UAAAA,iBAAiB,MAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAC3E,mBAAW,OAAO,KAAM,OAAM,OAAO,GAAG;AACxC,cAAME,UAAAA,gBAAgB,EAAE;AACxB,YAAI,OAAA;AAAA,MACN,CAAC;AAAA,IACH;AAAA,IACA,WAAW,CAAC,aAAa,IAAI,UAAU,QAAQ;AAAA,EAAA;AAEnD;AAGA,SAAS,kBAAkB;AACzB,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAQ,CAAC,UAAkB,OAAe,GAAG,QAAQ,KAAS,EAAE;AACtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,KAAK,CAAC,UAAkB,OAAe,QAAQ,IAAI,MAAM,UAAU,EAAE,CAAC;AAAA,IACtE,QAAQ,CAAC,WACP,CAAC,GAAG,QAAQ,OAAA,CAAQ,EAAE,OAAO,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,GAAG,OAAA,EAAS,EAAE,KAAK,UAAU;AAAA,IACvF,MAAM,EAAE,UAAU,IAAI,UAAU,KAAK,WAA2E;AAC9G,YAAM,MAAM,MAAM,UAAU,EAAE;AAC9B,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,UAAI,CAAC,OAAQ,QAAO;AACpB,UAAI,OAAO,SAAS,OAAO,MAAM,OAAO,YAAY,MAAM,OAAO,MAAM,KAAK,QAAS,QAAO;AAC5F,cAAQ,IAAI,KAAK,EAAE,GAAG,QAAQ,OAAO,EAAE,IAAI,UAAU,IAAI,IAAA,EAAI,CAAG;AAChE,aAAO;AAAA,IACT;AAAA,IACA,QAAQ,EAAE,UAAU,IAAI,YAA6D;;AACnF,YAAM,MAAM,MAAM,UAAU,EAAE;AAC9B,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,YAAI,sCAAQ,UAAR,mBAAe,QAAO,SAAU;AACpC,YAAM,EAAE,OAAO,QAAQ,GAAG,SAAS;AACnC,cAAQ,IAAI,KAAK,IAAI;AAAA,IACvB;AAAA,IACA,WAAW,EAAE,UAAU,IAAI,UAAU,WAA+D;AAClG,YAAM,MAAM,MAAM,UAAU,EAAE;AAC9B,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,UAAI,CAAC,OAAQ;AACb,YAAM,EAAE,OAAO,GAAG,KAAA,IAAS;AAC3B,cAAQ,IAAI,KAAK,EAAE,GAAG,MAAM,GAAI,SAAS,MAAM,OAAO,YAAY,EAAE,SAAU,SAAS,EAAE,GAAG,QAAA,GAAW;AAAA,IACzG;AAAA,IACA,aAAa,EAAE,UAAU,MAA4D;AACnF,YAAM,MAAM,MAAM,UAAU,EAAE;AAC9B,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,UAAI,EAAC,iCAAQ,SAAS;AACtB,YAAM,EAAE,SAAS,UAAU,GAAG,SAAS;AACvC,cAAQ,IAAI,KAAK,IAAI;AAAA,IACvB;AAAA,EAAA;AAEJ;AAEA,SAAS,oBAAoB;AAC3B,QAAM,gCAAgB,IAAA;AACtB,SAAO;AAAA,IACL,SAAS;AACP,iBAAW,YAAY,WAAW;AAChC,YAAI;AACF,mBAAA;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AAAA,IACA,UAAU,UAAkC;AAC1C,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EAAA;AAEJ;AASO,SAAS,6BAA6B,OAAyC;AACpF,QAAM,SAAS,gBAAA;AACf,QAAM,QAAQ,kBAAA;AACd,QAAM,aAAa,CAAC,WAClB,OAAO,OAAO,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,YAAY,OAAgB;AAClF,QAAM,SAAS,CAAC,MAA0B,UAAkD;AAC1F,UAAM,MAAM,IAAI,IAAI,KAAK,IAAI,CAAC,WAAW,OAAO,MAAM,EAAE,CAAC;AACzD,WAAO,CAAC,GAAG,MAAM,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,IAAI,IAAI,OAAO,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,UAAU;AAAA,EAC1F;AACA,SAAO;AAAA,IACL,MAAM,KAAK,UAAU;AACnB,YAAM,OAAO,MAAM,MAAM,KAAK,QAAQ,EAAE,MAAM,MAAM,EAAE;AACtD,aAAO,OAAO,MAAM,WAAW,CAAC,WAAW,OAAO,MAAM,aAAa,QAAQ,CAAC;AAAA,IAChF;AAAA,IACA,MAAM,UAAU;AACd,YAAM,OAAO,MAAM,MAAM,QAAA,EAAU,MAAM,MAAM,EAAE;AACjD,aAAO,OAAO,MAAM,WAAW,MAAM,IAAI,CAAC;AAAA,IAC5C;AAAA,IACA,MAAM,QAAQ;AACZ,aAAQ,MAAM,MAAM,MAAA,IAAW,OAAO,QAAQ;AAAA,IAChD;AAAA,IACA,MAAM,IAAI,OAAO;AACf,UAAI,OAAO;AACX,UAAI;AACF,eAAO,MAAM,MAAM,IAAI,KAAK;AAAA,MAC9B,QAAQ;AACN,eAAO;AAAA,MACT;AACA,UAAI,KAAM,QAAO;AACjB,aAAO,QAAQ,IAAI,OAAO,MAAM,MAAM,UAAU,MAAM,EAAE,GAAG,EAAE,MAAA,CAAO;AACpE,YAAM,OAAA;AACN,aAAO;AAAA,IACT;AAAA,IACA,MAAM,OAAO,UAAU,IAAI;AACzB,UAAI,OAAO,QAAQ,OAAO,OAAO,MAAM,UAAU,EAAE,CAAC,GAAG;AACrD,cAAM,OAAA;AACN;AAAA,MACF;AACA,YAAM,MAAM,OAAO,UAAU,EAAE;AAAA,IACjC;AAAA,IACA,MAAM,MAAM,OAAO;AACjB,UAAI,CAAC,OAAO,IAAI,MAAM,UAAU,MAAM,EAAE,EAAG,QAAO,MAAM,MAAM,KAAK;AACnE,YAAM,SAAS,OAAO,MAAM,KAAK;AACjC,YAAM,OAAA;AACN,aAAO;AAAA,IACT;AAAA,IACA,MAAM,QAAQ,OAAO;AACnB,UAAI,CAAC,OAAO,IAAI,MAAM,UAAU,MAAM,EAAE,EAAG,QAAO,MAAM,QAAQ,KAAK;AACrE,aAAO,QAAQ,KAAK;AACpB,YAAM,OAAA;AAAA,IACR;AAAA,IACA,MAAM,WAAW,OAAO;AACtB,UAAI,CAAC,OAAO,IAAI,MAAM,UAAU,MAAM,EAAE,EAAG,QAAO,MAAM,WAAW,KAAK;AACxE,aAAO,WAAW,KAAK;AACvB,YAAM,OAAA;AAAA,IACR;AAAA,IACA,MAAM,aAAa,OAAO;AACxB,UAAI,CAAC,OAAO,IAAI,MAAM,UAAU,MAAM,EAAE,EAAG,QAAO,MAAM,aAAa,KAAK;AAC1E,aAAO,aAAa,KAAK;AACzB,YAAM,OAAA;AAAA,IACR;AAAA,IACA,MAAM,QAAQ;AACZ,aAAO,QAAQ,MAAA;AACf,YAAM,OAAA;AACN,YAAM,MAAM,MAAA;AAAA,IACd;AAAA,IACA,UAAU,UAAU;AAClB,YAAM,YAAY,MAAM,UAAU,QAAQ;AAC1C,YAAM,YAAY,MAAM,UAAU,QAAQ;AAC1C,aAAO,MAAM;AACX,kBAAA;AACA,kBAAA;AAAA,MACF;AAAA,IACF;AAAA,EAAA;AAEJ;;;;;;;;"}
1
+ {"version":3,"file":"chat-outbox-store.cjs","sources":["../../../../agent-runtime/src/chat-outbox-store.ts"],"sourcesContent":["import {\n type IdbDatabase,\n idbGlobals,\n lazySealedDatabase,\n openJson,\n requestToPromise,\n sealJson,\n sha256Hex,\n transactionDone,\n} from \"./sealed-idb\";\n\n/**\n * Where chat messages typed while Athena can't be reached wait until they are sent\n * (`chat-outbox.ts`): one store per person on this device, sealed with a key of theirs.\n *\n * This store can hold the only copy of what someone wrote, so it never deletes as a side effect:\n * a record goes when its message was handed to the conversation and the conversation's own journal\n * took it (`remove`), when the person cancels or discards it, or when the person chooses to sign\n * out (`clear`). A record that does not open (a key that is gone, a newer build's format) is left\n * where it is.\n */\n\n/** An already-uploaded file a waiting message refers to (never a file picked while offline). */\nexport interface ChatOutboxFile {\n /** A server-backed URL (an asset's image route); never a `blob:` or `data:` URL. */\n url: string;\n mediaType: string;\n filename?: string;\n}\n\n/**\n * A file already in Athena that a waiting message carries: an upload that finished before Athena\n * went out of reach (a file picked while it can't be reached is never attached: it can't upload).\n * Its reference rides the message's text, as a sent message's does; this copy names it so the\n * outbox can ask Athena, right before the message goes, that the file is still there and still the\n * person's to open (`ChatOutboxOptions.checkAttachments`).\n */\nexport interface ChatOutboxAttachment {\n assetId: string;\n /** Its name, as the composer showed it. */\n title: string;\n mediaType?: string;\n}\n\nexport interface ChatOutboxEntry {\n /**\n * The message id, minted when the message was typed. The message is sent under it, so a send\n * whose answer was lost can never post twice: statewire replays the same statement, and the\n * thread's host refuses an id it already holds (`duplicate-id`) or re-places one still queued.\n */\n id: string;\n threadId: string;\n /** The text exactly as it will be sent (what the model reads). */\n text: string;\n /** The composer draft the text was serialized from, for giving it back exactly as typed. */\n composerMarkdown?: string;\n /**\n * The draft as a composer whose draft is HTML holds it (the phone's: its mention chips\n * included), given back exactly on Cancel or Edit.\n */\n composerHtml?: string;\n /** The message metadata frozen when it was typed (its composer markdown, …). */\n metadata?: Record<string, unknown>;\n /**\n * The per-send settings the host puts back right before the message goes, kept with it so a\n * reload or another window hands it over with them (Olympus: the agents the message names).\n * Opaque to the outbox, except that a host's send gate may hand messages whose settings differ\n * over separately (`ChatOutboxSendGate`). An older build reads the message without them.\n */\n sendContext?: Record<string, unknown>;\n /** Already-uploaded files that ride along. */\n files?: ChatOutboxFile[];\n /**\n * The files already in Athena the message carries (uploads that finished before Athena went out\n * of reach), asked about right before it goes: a refused one keeps it as \"Couldn't send\".\n */\n attachments?: ChatOutboxAttachment[];\n /** When it was typed (epoch ms): a message that waited more than a day asks before it goes. */\n queuedAt: number;\n /** Typed into a chat that had never started: reopening that chat finds it again. */\n newChat: boolean;\n}\n\n/** Which window is handing a message to its conversation right now, and since when. */\nexport interface ChatOutboxClaim {\n by: string;\n at: number;\n}\n\n/** Why a hand-off failed. */\nexport type ChatOutboxFailureReason =\n /** The conversation refused it: stopped, or parked on an authorization denial. */\n | \"refused\"\n /** The send failed before the conversation's journal took it. */\n | \"error\"\n /** It could not be claimed for sending (the store failed). */\n | \"prepare\"\n /** Athena refused one of its attachments: gone, or no longer the person's to open. */\n | \"attachment\";\n\n/**\n * A failed hand-off, kept with the message: every window (and the next mount) shows it as\n * \"Couldn't send\", and nothing sends it again until the person says Retry.\n */\nexport interface ChatOutboxFailure {\n reason: ChatOutboxFailureReason;\n at: number;\n}\n\nexport interface ChatOutboxRecord {\n entry: ChatOutboxEntry;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n /**\n * Kept only in this store's memory, because this device could not keep it: it waits as long as\n * the page or the app stays open.\n */\n memoryOnly?: true;\n}\n\nexport type ChatOutboxClaimResult = \"claimed\" | \"taken\" | \"gone\";\n\n/**\n * A person's waiting messages. A store keeps every message put into it until it is removed: on\n * the device when it can (`put` resolves `true`), otherwise in its own memory for as long as the\n * store lives (`false`, listed with `memoryOnly`), so a host keeps one store per person and a\n * thread switch never loses a message (`withChatOutboxMemoryFallback`).\n */\nexport interface ChatOutboxStore {\n /** The thread's waiting messages, oldest first. Resolves `[]` when nothing can be read. */\n list(threadId: string): Promise<ChatOutboxRecord[]>;\n /** Every waiting message of this person, oldest first (sign-out, reopening a new chat). */\n listAll(): Promise<ChatOutboxRecord[]>;\n /**\n * How many messages wait, counted without opening them, so a record this build cannot read\n * still counts (a sign-out asks rather than deleting unread work); the ones kept only in memory\n * count too. Rejects when the device's records cannot be counted at all.\n */\n count(): Promise<number>;\n /** Keep a waiting message. Resolves `false` when it could not be kept on this device. */\n put(entry: ChatOutboxEntry): Promise<boolean>;\n remove(threadId: string, id: string): Promise<void>;\n /**\n * Claim a message for handing it to its conversation, atomically across windows: `taken` while\n * another claimant's claim is younger than `leaseMs`, `gone` when the record no longer exists\n * (cancelled, discarded, or handed by another window).\n */\n claim(input: {\n threadId: string;\n id: string;\n claimant: string;\n now: number;\n leaseMs: number;\n }): Promise<ChatOutboxClaimResult>;\n /** Drop `claimant`'s claim (its hand-off never reached the conversation). */\n release(input: { threadId: string; id: string; claimant: string }): Promise<void>;\n /** Keep the hand-off's failure with the message, dropping `claimant`'s claim. */\n markFailed(input: { threadId: string; id: string; claimant: string; failure: ChatOutboxFailure }): Promise<void>;\n /** The person said Retry: the message may be sent again. */\n clearFailure(input: { threadId: string; id: string }): Promise<void>;\n /** Every waiting message of this person, gone (a sign-out the person chose). */\n clear(): Promise<void>;\n /** Called after any change, this window's or another's. */\n subscribe(listener: () => void): () => void;\n}\n\nexport const CHAT_OUTBOX_DATABASE = \"athena-chat-outbox:v1\";\n/** The sealed record's format; an older build leaves a newer one alone. */\nexport const CHAT_OUTBOX_FORMAT = 1;\n\nconst DATABASE_VERSION = 1;\nconst ENTRIES_STORE = \"entries\";\nconst BY_OWNER = \"byOwner\";\nconst BY_OWNER_THREAD = \"byOwnerThread\";\n\ninterface StoredRecord {\n key: string;\n owner: string;\n thread: string;\n at: number;\n format: number;\n sealed: Uint8Array;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n}\n\nfunction upgradeOutboxDatabase(db: IdbDatabase): void {\n if (!db.objectStoreNames.contains(ENTRIES_STORE)) {\n const store = db.createObjectStore(ENTRIES_STORE, { keyPath: \"key\" });\n store.createIndex(BY_OWNER, \"owner\", { unique: false });\n store.createIndex(BY_OWNER_THREAD, [\"owner\", \"thread\"], { unique: false });\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** A record this build may replace or delete: none, or one in a format it writes itself. */\nfunction isOwnFormat(record: unknown): boolean {\n if (!isRecord(record)) return true;\n const format = record.format;\n return typeof format !== \"number\" || format <= CHAT_OUTBOX_FORMAT;\n}\n\nexport function readChatOutboxClaim(value: unknown): ChatOutboxClaim | undefined {\n if (!isRecord(value) || typeof value.by !== \"string\" || typeof value.at !== \"number\") return undefined;\n return { by: value.by, at: value.at };\n}\n\nconst FAILURE_REASONS: ReadonlySet<string> = new Set<ChatOutboxFailureReason>([\n \"refused\",\n \"error\",\n \"prepare\",\n \"attachment\",\n]);\n\n/** A failure read back from storage; an unknown reason reads as a send error (still failed). */\nexport function readChatOutboxFailure(value: unknown): ChatOutboxFailure | undefined {\n if (!isRecord(value) || typeof value.at !== \"number\") return undefined;\n const reason = typeof value.reason === \"string\" && FAILURE_REASONS.has(value.reason) ? value.reason : \"error\";\n return { reason: reason as ChatOutboxFailureReason, at: value.at };\n}\n\nconst readClaim = readChatOutboxClaim;\n\nfunction readFiles(value: unknown): ChatOutboxFile[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const files: ChatOutboxFile[] = [];\n for (const file of value) {\n if (!isRecord(file) || typeof file.url !== \"string\" || typeof file.mediaType !== \"string\") continue;\n files.push({\n url: file.url,\n mediaType: file.mediaType,\n ...(typeof file.filename === \"string\" && { filename: file.filename }),\n });\n }\n return files.length > 0 ? files : undefined;\n}\n\nfunction readAttachments(value: unknown): ChatOutboxAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const attachments: ChatOutboxAttachment[] = [];\n for (const attachment of value) {\n if (!isRecord(attachment) || typeof attachment.assetId !== \"string\" || attachment.assetId.length === 0) {\n continue;\n }\n attachments.push({\n assetId: attachment.assetId,\n title: typeof attachment.title === \"string\" ? attachment.title : \"\",\n ...(typeof attachment.mediaType === \"string\" && { mediaType: attachment.mediaType }),\n });\n }\n return attachments.length > 0 ? attachments : undefined;\n}\n\n/** A sealed entry read back, or `null` when it is not one. */\nexport function parseChatOutboxEntry(value: unknown): ChatOutboxEntry | null {\n if (!isRecord(value)) return null;\n const { id, threadId, text, queuedAt } = value;\n if (typeof id !== \"string\" || typeof threadId !== \"string\" || typeof text !== \"string\") return null;\n if (typeof queuedAt !== \"number\" || !Number.isFinite(queuedAt)) return null;\n const files = readFiles(value.files);\n const attachments = readAttachments(value.attachments);\n return {\n id,\n threadId,\n text,\n queuedAt,\n newChat: value.newChat === true,\n ...(typeof value.composerMarkdown === \"string\" && { composerMarkdown: value.composerMarkdown }),\n ...(typeof value.composerHtml === \"string\" && { composerHtml: value.composerHtml }),\n ...(isRecord(value.metadata) && { metadata: value.metadata }),\n ...(isRecord(value.sendContext) && !Array.isArray(value.sendContext) && { sendContext: value.sendContext }),\n ...(files && { files }),\n ...(attachments && { attachments }),\n };\n}\n\nfunction byQueuedAt(a: ChatOutboxRecord, b: ChatOutboxRecord): number {\n return a.entry.queuedAt - b.entry.queuedAt;\n}\n\n/** A tiny change bus: this window's listeners, plus other windows' through a BroadcastChannel. */\nfunction createChangeBus(channelName: string): {\n notify: () => void;\n subscribe: (listener: () => void) => () => void;\n} {\n const listeners = new Set<() => void>();\n type Channel = {\n postMessage(message: unknown): void;\n onmessage: ((event: unknown) => void) | null;\n close(): void;\n };\n const ChannelCtor = (globalThis as { BroadcastChannel?: new (name: string) => Channel }).BroadcastChannel;\n let channel: Channel | null = null;\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n };\n return {\n notify() {\n emit();\n try {\n channel?.postMessage(\"changed\");\n } catch {\n // Another window learns of the change on its next read.\n }\n },\n subscribe(listener) {\n listeners.add(listener);\n if (!channel && ChannelCtor) {\n try {\n channel = new ChannelCtor(channelName);\n channel.onmessage = emit;\n } catch {\n channel = null;\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && channel) {\n channel.close();\n channel = null;\n }\n };\n },\n };\n}\n\n/** What a store asks the host's key for: opening kept messages, or keeping a new one. */\nexport type ChatOutboxKeyPurpose = \"read\" | \"write\";\n\nexport interface IndexedDbChatOutboxStoreOptions {\n /** The person this store belongs to (with the environment, the key's scope). */\n ownerId: string;\n /**\n * The person's AES-GCM key, or `null` when there is none for `purpose`: `read` opens what is\n * already kept (a key that exists, whatever the deployment's policy now says, so kept messages\n * always show and drain); `write` keeps a new message (a key only while the deployment lets\n * devices keep copies). Without a key to write with, `put` resolves `false` and the message\n * waits in this store's memory.\n */\n getKey: (ownerId: string, purpose: ChatOutboxKeyPurpose) => Promise<CryptoKey | null>;\n /** An `IDBFactory` (the browser's `indexedDB` by default; a fake one in tests). */\n factory?: object;\n databaseName?: string;\n}\n\n/**\n * The browser's outbox store (Olympus, the desktop app): IndexedDB records sealed with AES-GCM-256\n * under the person's key, the owner and the thread hashed for the index and bound to each record\n * as additional data, so the database names no one and a record moved to another owner or thread\n * fails to open. A message it cannot keep on the device waits in its memory\n * ({@link withChatOutboxMemoryFallback}).\n */\nexport function createIndexedDbChatOutboxStore(options: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n return withChatOutboxMemoryFallback(createSealedIndexedDbChatOutboxStore(options));\n}\n\nfunction createSealedIndexedDbChatOutboxStore({\n ownerId,\n getKey,\n factory = idbGlobals.indexedDB,\n databaseName = CHAT_OUTBOX_DATABASE,\n}: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n const database = lazySealedDatabase({\n factory,\n name: databaseName,\n version: DATABASE_VERSION,\n upgrade: upgradeOutboxDatabase,\n });\n const bus = createChangeBus(`${databaseName}:changes`);\n const keyOf = async (purpose: ChatOutboxKeyPurpose): Promise<CryptoKey | null> => {\n try {\n return await getKey(ownerId, purpose);\n } catch {\n return null;\n }\n };\n const ownerHash = () => sha256Hex(ownerId);\n const threadHash = (threadId: string) => sha256Hex(`${ownerId}|${threadId}`);\n const recordKey = (threadId: string, id: string) => sha256Hex(`${ownerId}|${threadId}|${id}`);\n const aadOf = (owner: string, thread: string, key: string) =>\n `athena-chat-outbox|${CHAT_OUTBOX_FORMAT}|${owner}|${thread}|${key}`;\n\n // Writes are serialized so a claim never races a put or a clear in this window; IndexedDB's own\n // transactions order them against other windows.\n let queue: Promise<unknown> = Promise.resolve();\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const run = queue.then(task, task);\n queue = run.catch(() => undefined);\n return run;\n };\n\n const openRecords = async (records: StoredRecord[]): Promise<ChatOutboxRecord[]> => {\n if (records.length === 0) return [];\n const cryptoKey = await keyOf(\"read\");\n if (!cryptoKey) return [];\n const opened: ChatOutboxRecord[] = [];\n for (const record of records) {\n if (!record || record.format > CHAT_OUTBOX_FORMAT) continue;\n try {\n const value = await openJson({\n key: cryptoKey,\n sealed: record.sealed,\n aad: aadOf(record.owner, record.thread, record.key),\n });\n const entry = parseChatOutboxEntry(value);\n if (!entry) continue;\n const claim = readClaim(record.claim);\n const failure = readChatOutboxFailure(record.failure);\n opened.push({ entry, ...(claim && { claim }), ...(failure && { failure }) });\n } catch {\n // Not readable with this key: left as it is (it may be the only copy of someone's work).\n }\n }\n return opened.sort(byQueuedAt);\n };\n\n return {\n async list(threadId) {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const thread = await threadHash(threadId);\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER_THREAD).getAll([owner, thread]),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async listAll() {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAll(owner),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async count() {\n const db = await database();\n if (!db) return 0;\n const owner = await ownerHash();\n const keys = await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAllKeys(owner),\n );\n return keys.length;\n },\n put(entry) {\n return enqueue(async () => {\n try {\n const db = await database();\n if (!db) return false;\n const cryptoKey = await keyOf(\"write\");\n if (!cryptoKey) return false;\n const owner = await ownerHash();\n const thread = await threadHash(entry.threadId);\n const key = await recordKey(entry.threadId, entry.id);\n const sealed = await sealJson({ key: cryptoKey, value: entry, aad: aadOf(owner, thread, key) });\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = await requestToPromise(store.get(key));\n let kept = true;\n // A newer build's record of the same message stays as that build wrote it.\n if (isOwnFormat(existing)) {\n store.put({\n key,\n owner,\n thread,\n at: entry.queuedAt,\n format: CHAT_OUTBOX_FORMAT,\n sealed,\n } satisfies StoredRecord);\n } else {\n kept = false;\n }\n await transactionDone(tx);\n bus.notify();\n return kept;\n } catch {\n return false;\n }\n });\n },\n remove(threadId, id) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n if (isOwnFormat(await requestToPromise(store.get(key)))) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n claim({ threadId, id, claimant, now, leaseMs }) {\n return enqueue(async () => {\n const db = await database();\n // No database: nothing else can hand this message, so the one window holding it may.\n if (!db) return \"claimed\" as const;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n let result: ChatOutboxClaimResult;\n if (!existing) {\n result = \"gone\";\n } else {\n const claim = readClaim(existing.claim);\n if (claim && claim.by !== claimant && now - claim.at < leaseMs) {\n result = \"taken\";\n } else {\n store.put({ ...existing, claim: { by: claimant, at: now } } satisfies StoredRecord);\n result = \"claimed\";\n }\n }\n await transactionDone(tx);\n if (result === \"claimed\") bus.notify();\n return result;\n });\n },\n release({ threadId, id, claimant }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && readClaim(existing.claim)?.by === claimant) {\n const { claim: _claim, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n markFailed({ threadId, id, claimant, failure }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && isOwnFormat(existing)) {\n const { claim, ...rest } = existing;\n const keepClaim = claim !== undefined && readClaim(claim)?.by !== claimant;\n store.put({\n ...rest,\n ...(keepClaim && { claim }),\n failure: { reason: failure.reason, at: failure.at },\n } satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clearFailure({ threadId, id }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing?.failure !== undefined && isOwnFormat(existing)) {\n const { failure: _failure, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clear() {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const owner = await ownerHash();\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const keys = await requestToPromise(store.index(BY_OWNER).getAllKeys(owner));\n for (const key of keys) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n subscribe: (listener) => bus.subscribe(listener),\n };\n}\n\n/** Records kept in a process's memory, with the claim and failure rules every store shares. */\nfunction createRecordMap() {\n const records = new Map<string, ChatOutboxRecord>();\n const keyOf = (threadId: string, id: string) => `${threadId}\\u0000${id}`;\n return {\n records,\n keyOf,\n has: (threadId: string, id: string) => records.has(keyOf(threadId, id)),\n sorted: (filter: (record: ChatOutboxRecord) => boolean) =>\n [...records.values()].filter(filter).map((record) => ({ ...record })).sort(byQueuedAt),\n claim({ threadId, id, claimant, now, leaseMs }: Parameters<ChatOutboxStore[\"claim\"]>[0]): ChatOutboxClaimResult {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return \"gone\";\n if (record.claim && record.claim.by !== claimant && now - record.claim.at < leaseMs) return \"taken\";\n records.set(key, { ...record, claim: { by: claimant, at: now } });\n return \"claimed\";\n },\n release({ threadId, id, claimant }: Parameters<ChatOutboxStore[\"release\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (record?.claim?.by !== claimant) return;\n const { claim: _claim, ...rest } = record;\n records.set(key, rest);\n },\n markFailed({ threadId, id, claimant, failure }: Parameters<ChatOutboxStore[\"markFailed\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return;\n const { claim, ...rest } = record;\n records.set(key, { ...rest, ...(claim && claim.by !== claimant && { claim }), failure: { ...failure } });\n },\n clearFailure({ threadId, id }: Parameters<ChatOutboxStore[\"clearFailure\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record?.failure) return;\n const { failure: _failure, ...rest } = record;\n records.set(key, rest);\n },\n };\n}\n\nfunction createListenerSet() {\n const listeners = new Set<() => void>();\n return {\n notify() {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/**\n * The store contract's memory half, around a store that keeps messages on the device: a message\n * `inner` could not keep (no key, no storage, a failed write) waits in this wrapper's memory,\n * listed with `memoryOnly` and counted, for as long as the wrapper lives. A host keeps one store\n * per person, so switching conversations never loses such a message, and a sign-out still asks\n * about it.\n */\nexport function withChatOutboxMemoryFallback(inner: ChatOutboxStore): ChatOutboxStore {\n const memory = createRecordMap();\n const local = createListenerSet();\n const remembered = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => ({ ...record, memoryOnly: true as const }));\n const merged = (kept: ChatOutboxRecord[], extra: ChatOutboxRecord[]): ChatOutboxRecord[] => {\n const ids = new Set(kept.map((record) => record.entry.id));\n return [...kept, ...extra.filter((record) => !ids.has(record.entry.id))].sort(byQueuedAt);\n };\n return {\n async list(threadId) {\n const kept = await inner.list(threadId).catch(() => []);\n return merged(kept, remembered((record) => record.entry.threadId === threadId));\n },\n async listAll() {\n const kept = await inner.listAll().catch(() => []);\n return merged(kept, remembered(() => true));\n },\n async count() {\n return (await inner.count()) + memory.records.size;\n },\n async put(entry) {\n let kept = false;\n try {\n kept = await inner.put(entry);\n } catch {\n kept = false;\n }\n if (kept) return true;\n memory.records.set(memory.keyOf(entry.threadId, entry.id), { entry });\n local.notify();\n return false;\n },\n async remove(threadId, id) {\n if (memory.records.delete(memory.keyOf(threadId, id))) {\n local.notify();\n return;\n }\n await inner.remove(threadId, id);\n },\n async claim(input) {\n if (!memory.has(input.threadId, input.id)) return inner.claim(input);\n const result = memory.claim(input);\n local.notify();\n return result;\n },\n async release(input) {\n if (!memory.has(input.threadId, input.id)) return inner.release(input);\n memory.release(input);\n local.notify();\n },\n async markFailed(input) {\n if (!memory.has(input.threadId, input.id)) return inner.markFailed(input);\n memory.markFailed(input);\n local.notify();\n },\n async clearFailure(input) {\n if (!memory.has(input.threadId, input.id)) return inner.clearFailure(input);\n memory.clearFailure(input);\n local.notify();\n },\n async clear() {\n memory.records.clear();\n local.notify();\n await inner.clear();\n },\n subscribe(listener) {\n const stopLocal = local.subscribe(listener);\n const stopInner = inner.subscribe(listener);\n return () => {\n stopLocal();\n stopInner();\n };\n },\n };\n}\n\n/**\n * An outbox store in this process's memory: for tests, and for a host that keeps nothing on the\n * device (the messages then wait only as long as the page or app stays open). `durable: false`\n * makes every `put` report that nothing was kept on the device, and lists its messages as\n * `memoryOnly`.\n */\nexport function createMemoryChatOutboxStore({ durable = true }: { durable?: boolean } = {}): ChatOutboxStore & {\n /** The records as stored (tests). */\n records(): ChatOutboxRecord[];\n} {\n const memory = createRecordMap();\n const listeners = createListenerSet();\n const listed = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => (durable ? record : { ...record, memoryOnly: true as const }));\n return {\n records: () => listed(() => true),\n async list(threadId) {\n return listed((record) => record.entry.threadId === threadId);\n },\n async listAll() {\n return listed(() => true);\n },\n async count() {\n return memory.records.size;\n },\n async put(entry) {\n const key = memory.keyOf(entry.threadId, entry.id);\n const existing = memory.records.get(key);\n memory.records.set(key, { ...existing, entry });\n listeners.notify();\n return durable;\n },\n async remove(threadId, id) {\n memory.records.delete(memory.keyOf(threadId, id));\n listeners.notify();\n },\n async claim(input) {\n const result = memory.claim(input);\n if (result === \"claimed\") listeners.notify();\n return result;\n },\n async release(input) {\n memory.release(input);\n listeners.notify();\n },\n async markFailed(input) {\n memory.markFailed(input);\n listeners.notify();\n },\n async clearFailure(input) {\n memory.clearFailure(input);\n listeners.notify();\n },\n async clear() {\n memory.records.clear();\n listeners.notify();\n },\n subscribe: (listener) => listeners.subscribe(listener),\n };\n}\n"],"names":["idbGlobals","lazySealedDatabase","sha256Hex","openJson","requestToPromise","sealJson","transactionDone"],"mappings":"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"}
@@ -47,6 +47,13 @@ export interface ChatOutboxEntry {
47
47
  composerHtml?: string;
48
48
  /** The message metadata frozen when it was typed (its composer markdown, …). */
49
49
  metadata?: Record<string, unknown>;
50
+ /**
51
+ * The per-send settings the host puts back right before the message goes, kept with it so a
52
+ * reload or another window hands it over with them (Olympus: the agents the message names).
53
+ * Opaque to the outbox, except that a host's send gate may hand messages whose settings differ
54
+ * over separately (`ChatOutboxSendGate`). An older build reads the message without them.
55
+ */
56
+ sendContext?: Record<string, unknown>;
50
57
  /** Already-uploaded files that ride along. */
51
58
  files?: ChatOutboxFile[];
52
59
  /**
@@ -80,6 +80,7 @@ function parseChatOutboxEntry(value) {
80
80
  ...typeof value.composerMarkdown === "string" && { composerMarkdown: value.composerMarkdown },
81
81
  ...typeof value.composerHtml === "string" && { composerHtml: value.composerHtml },
82
82
  ...isRecord(value.metadata) && { metadata: value.metadata },
83
+ ...isRecord(value.sendContext) && !Array.isArray(value.sendContext) && { sendContext: value.sendContext },
83
84
  ...files && { files },
84
85
  ...attachments && { attachments }
85
86
  };
@@ -1 +1 @@
1
- {"version":3,"file":"chat-outbox-store.js","sources":["../../../../agent-runtime/src/chat-outbox-store.ts"],"sourcesContent":["import {\n type IdbDatabase,\n idbGlobals,\n lazySealedDatabase,\n openJson,\n requestToPromise,\n sealJson,\n sha256Hex,\n transactionDone,\n} from \"./sealed-idb\";\n\n/**\n * Where chat messages typed while Athena can't be reached wait until they are sent\n * (`chat-outbox.ts`): one store per person on this device, sealed with a key of theirs.\n *\n * This store can hold the only copy of what someone wrote, so it never deletes as a side effect:\n * a record goes when its message was handed to the conversation and the conversation's own journal\n * took it (`remove`), when the person cancels or discards it, or when the person chooses to sign\n * out (`clear`). A record that does not open (a key that is gone, a newer build's format) is left\n * where it is.\n */\n\n/** An already-uploaded file a waiting message refers to (never a file picked while offline). */\nexport interface ChatOutboxFile {\n /** A server-backed URL (an asset's image route); never a `blob:` or `data:` URL. */\n url: string;\n mediaType: string;\n filename?: string;\n}\n\n/**\n * A file already in Athena that a waiting message carries: an upload that finished before Athena\n * went out of reach (a file picked while it can't be reached is never attached: it can't upload).\n * Its reference rides the message's text, as a sent message's does; this copy names it so the\n * outbox can ask Athena, right before the message goes, that the file is still there and still the\n * person's to open (`ChatOutboxOptions.checkAttachments`).\n */\nexport interface ChatOutboxAttachment {\n assetId: string;\n /** Its name, as the composer showed it. */\n title: string;\n mediaType?: string;\n}\n\nexport interface ChatOutboxEntry {\n /**\n * The message id, minted when the message was typed. The message is sent under it, so a send\n * whose answer was lost can never post twice: statewire replays the same statement, and the\n * thread's host refuses an id it already holds (`duplicate-id`) or re-places one still queued.\n */\n id: string;\n threadId: string;\n /** The text exactly as it will be sent (what the model reads). */\n text: string;\n /** The composer draft the text was serialized from, for giving it back exactly as typed. */\n composerMarkdown?: string;\n /**\n * The draft as a composer whose draft is HTML holds it (the phone's: its mention chips\n * included), given back exactly on Cancel or Edit.\n */\n composerHtml?: string;\n /** The message metadata frozen when it was typed (its composer markdown, …). */\n metadata?: Record<string, unknown>;\n /** Already-uploaded files that ride along. */\n files?: ChatOutboxFile[];\n /**\n * The files already in Athena the message carries (uploads that finished before Athena went out\n * of reach), asked about right before it goes: a refused one keeps it as \"Couldn't send\".\n */\n attachments?: ChatOutboxAttachment[];\n /** When it was typed (epoch ms): a message that waited more than a day asks before it goes. */\n queuedAt: number;\n /** Typed into a chat that had never started: reopening that chat finds it again. */\n newChat: boolean;\n}\n\n/** Which window is handing a message to its conversation right now, and since when. */\nexport interface ChatOutboxClaim {\n by: string;\n at: number;\n}\n\n/** Why a hand-off failed. */\nexport type ChatOutboxFailureReason =\n /** The conversation refused it: stopped, or parked on an authorization denial. */\n | \"refused\"\n /** The send failed before the conversation's journal took it. */\n | \"error\"\n /** It could not be claimed for sending (the store failed). */\n | \"prepare\"\n /** Athena refused one of its attachments: gone, or no longer the person's to open. */\n | \"attachment\";\n\n/**\n * A failed hand-off, kept with the message: every window (and the next mount) shows it as\n * \"Couldn't send\", and nothing sends it again until the person says Retry.\n */\nexport interface ChatOutboxFailure {\n reason: ChatOutboxFailureReason;\n at: number;\n}\n\nexport interface ChatOutboxRecord {\n entry: ChatOutboxEntry;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n /**\n * Kept only in this store's memory, because this device could not keep it: it waits as long as\n * the page or the app stays open.\n */\n memoryOnly?: true;\n}\n\nexport type ChatOutboxClaimResult = \"claimed\" | \"taken\" | \"gone\";\n\n/**\n * A person's waiting messages. A store keeps every message put into it until it is removed: on\n * the device when it can (`put` resolves `true`), otherwise in its own memory for as long as the\n * store lives (`false`, listed with `memoryOnly`), so a host keeps one store per person and a\n * thread switch never loses a message (`withChatOutboxMemoryFallback`).\n */\nexport interface ChatOutboxStore {\n /** The thread's waiting messages, oldest first. Resolves `[]` when nothing can be read. */\n list(threadId: string): Promise<ChatOutboxRecord[]>;\n /** Every waiting message of this person, oldest first (sign-out, reopening a new chat). */\n listAll(): Promise<ChatOutboxRecord[]>;\n /**\n * How many messages wait, counted without opening them, so a record this build cannot read\n * still counts (a sign-out asks rather than deleting unread work); the ones kept only in memory\n * count too. Rejects when the device's records cannot be counted at all.\n */\n count(): Promise<number>;\n /** Keep a waiting message. Resolves `false` when it could not be kept on this device. */\n put(entry: ChatOutboxEntry): Promise<boolean>;\n remove(threadId: string, id: string): Promise<void>;\n /**\n * Claim a message for handing it to its conversation, atomically across windows: `taken` while\n * another claimant's claim is younger than `leaseMs`, `gone` when the record no longer exists\n * (cancelled, discarded, or handed by another window).\n */\n claim(input: {\n threadId: string;\n id: string;\n claimant: string;\n now: number;\n leaseMs: number;\n }): Promise<ChatOutboxClaimResult>;\n /** Drop `claimant`'s claim (its hand-off never reached the conversation). */\n release(input: { threadId: string; id: string; claimant: string }): Promise<void>;\n /** Keep the hand-off's failure with the message, dropping `claimant`'s claim. */\n markFailed(input: { threadId: string; id: string; claimant: string; failure: ChatOutboxFailure }): Promise<void>;\n /** The person said Retry: the message may be sent again. */\n clearFailure(input: { threadId: string; id: string }): Promise<void>;\n /** Every waiting message of this person, gone (a sign-out the person chose). */\n clear(): Promise<void>;\n /** Called after any change, this window's or another's. */\n subscribe(listener: () => void): () => void;\n}\n\nexport const CHAT_OUTBOX_DATABASE = \"athena-chat-outbox:v1\";\n/** The sealed record's format; an older build leaves a newer one alone. */\nexport const CHAT_OUTBOX_FORMAT = 1;\n\nconst DATABASE_VERSION = 1;\nconst ENTRIES_STORE = \"entries\";\nconst BY_OWNER = \"byOwner\";\nconst BY_OWNER_THREAD = \"byOwnerThread\";\n\ninterface StoredRecord {\n key: string;\n owner: string;\n thread: string;\n at: number;\n format: number;\n sealed: Uint8Array;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n}\n\nfunction upgradeOutboxDatabase(db: IdbDatabase): void {\n if (!db.objectStoreNames.contains(ENTRIES_STORE)) {\n const store = db.createObjectStore(ENTRIES_STORE, { keyPath: \"key\" });\n store.createIndex(BY_OWNER, \"owner\", { unique: false });\n store.createIndex(BY_OWNER_THREAD, [\"owner\", \"thread\"], { unique: false });\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** A record this build may replace or delete: none, or one in a format it writes itself. */\nfunction isOwnFormat(record: unknown): boolean {\n if (!isRecord(record)) return true;\n const format = record.format;\n return typeof format !== \"number\" || format <= CHAT_OUTBOX_FORMAT;\n}\n\nexport function readChatOutboxClaim(value: unknown): ChatOutboxClaim | undefined {\n if (!isRecord(value) || typeof value.by !== \"string\" || typeof value.at !== \"number\") return undefined;\n return { by: value.by, at: value.at };\n}\n\nconst FAILURE_REASONS: ReadonlySet<string> = new Set<ChatOutboxFailureReason>([\n \"refused\",\n \"error\",\n \"prepare\",\n \"attachment\",\n]);\n\n/** A failure read back from storage; an unknown reason reads as a send error (still failed). */\nexport function readChatOutboxFailure(value: unknown): ChatOutboxFailure | undefined {\n if (!isRecord(value) || typeof value.at !== \"number\") return undefined;\n const reason = typeof value.reason === \"string\" && FAILURE_REASONS.has(value.reason) ? value.reason : \"error\";\n return { reason: reason as ChatOutboxFailureReason, at: value.at };\n}\n\nconst readClaim = readChatOutboxClaim;\n\nfunction readFiles(value: unknown): ChatOutboxFile[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const files: ChatOutboxFile[] = [];\n for (const file of value) {\n if (!isRecord(file) || typeof file.url !== \"string\" || typeof file.mediaType !== \"string\") continue;\n files.push({\n url: file.url,\n mediaType: file.mediaType,\n ...(typeof file.filename === \"string\" && { filename: file.filename }),\n });\n }\n return files.length > 0 ? files : undefined;\n}\n\nfunction readAttachments(value: unknown): ChatOutboxAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const attachments: ChatOutboxAttachment[] = [];\n for (const attachment of value) {\n if (!isRecord(attachment) || typeof attachment.assetId !== \"string\" || attachment.assetId.length === 0) {\n continue;\n }\n attachments.push({\n assetId: attachment.assetId,\n title: typeof attachment.title === \"string\" ? attachment.title : \"\",\n ...(typeof attachment.mediaType === \"string\" && { mediaType: attachment.mediaType }),\n });\n }\n return attachments.length > 0 ? attachments : undefined;\n}\n\n/** A sealed entry read back, or `null` when it is not one. */\nexport function parseChatOutboxEntry(value: unknown): ChatOutboxEntry | null {\n if (!isRecord(value)) return null;\n const { id, threadId, text, queuedAt } = value;\n if (typeof id !== \"string\" || typeof threadId !== \"string\" || typeof text !== \"string\") return null;\n if (typeof queuedAt !== \"number\" || !Number.isFinite(queuedAt)) return null;\n const files = readFiles(value.files);\n const attachments = readAttachments(value.attachments);\n return {\n id,\n threadId,\n text,\n queuedAt,\n newChat: value.newChat === true,\n ...(typeof value.composerMarkdown === \"string\" && { composerMarkdown: value.composerMarkdown }),\n ...(typeof value.composerHtml === \"string\" && { composerHtml: value.composerHtml }),\n ...(isRecord(value.metadata) && { metadata: value.metadata }),\n ...(files && { files }),\n ...(attachments && { attachments }),\n };\n}\n\nfunction byQueuedAt(a: ChatOutboxRecord, b: ChatOutboxRecord): number {\n return a.entry.queuedAt - b.entry.queuedAt;\n}\n\n/** A tiny change bus: this window's listeners, plus other windows' through a BroadcastChannel. */\nfunction createChangeBus(channelName: string): {\n notify: () => void;\n subscribe: (listener: () => void) => () => void;\n} {\n const listeners = new Set<() => void>();\n type Channel = {\n postMessage(message: unknown): void;\n onmessage: ((event: unknown) => void) | null;\n close(): void;\n };\n const ChannelCtor = (globalThis as { BroadcastChannel?: new (name: string) => Channel }).BroadcastChannel;\n let channel: Channel | null = null;\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n };\n return {\n notify() {\n emit();\n try {\n channel?.postMessage(\"changed\");\n } catch {\n // Another window learns of the change on its next read.\n }\n },\n subscribe(listener) {\n listeners.add(listener);\n if (!channel && ChannelCtor) {\n try {\n channel = new ChannelCtor(channelName);\n channel.onmessage = emit;\n } catch {\n channel = null;\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && channel) {\n channel.close();\n channel = null;\n }\n };\n },\n };\n}\n\n/** What a store asks the host's key for: opening kept messages, or keeping a new one. */\nexport type ChatOutboxKeyPurpose = \"read\" | \"write\";\n\nexport interface IndexedDbChatOutboxStoreOptions {\n /** The person this store belongs to (with the environment, the key's scope). */\n ownerId: string;\n /**\n * The person's AES-GCM key, or `null` when there is none for `purpose`: `read` opens what is\n * already kept (a key that exists, whatever the deployment's policy now says, so kept messages\n * always show and drain); `write` keeps a new message (a key only while the deployment lets\n * devices keep copies). Without a key to write with, `put` resolves `false` and the message\n * waits in this store's memory.\n */\n getKey: (ownerId: string, purpose: ChatOutboxKeyPurpose) => Promise<CryptoKey | null>;\n /** An `IDBFactory` (the browser's `indexedDB` by default; a fake one in tests). */\n factory?: object;\n databaseName?: string;\n}\n\n/**\n * The browser's outbox store (Olympus, the desktop app): IndexedDB records sealed with AES-GCM-256\n * under the person's key, the owner and the thread hashed for the index and bound to each record\n * as additional data, so the database names no one and a record moved to another owner or thread\n * fails to open. A message it cannot keep on the device waits in its memory\n * ({@link withChatOutboxMemoryFallback}).\n */\nexport function createIndexedDbChatOutboxStore(options: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n return withChatOutboxMemoryFallback(createSealedIndexedDbChatOutboxStore(options));\n}\n\nfunction createSealedIndexedDbChatOutboxStore({\n ownerId,\n getKey,\n factory = idbGlobals.indexedDB,\n databaseName = CHAT_OUTBOX_DATABASE,\n}: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n const database = lazySealedDatabase({\n factory,\n name: databaseName,\n version: DATABASE_VERSION,\n upgrade: upgradeOutboxDatabase,\n });\n const bus = createChangeBus(`${databaseName}:changes`);\n const keyOf = async (purpose: ChatOutboxKeyPurpose): Promise<CryptoKey | null> => {\n try {\n return await getKey(ownerId, purpose);\n } catch {\n return null;\n }\n };\n const ownerHash = () => sha256Hex(ownerId);\n const threadHash = (threadId: string) => sha256Hex(`${ownerId}|${threadId}`);\n const recordKey = (threadId: string, id: string) => sha256Hex(`${ownerId}|${threadId}|${id}`);\n const aadOf = (owner: string, thread: string, key: string) =>\n `athena-chat-outbox|${CHAT_OUTBOX_FORMAT}|${owner}|${thread}|${key}`;\n\n // Writes are serialized so a claim never races a put or a clear in this window; IndexedDB's own\n // transactions order them against other windows.\n let queue: Promise<unknown> = Promise.resolve();\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const run = queue.then(task, task);\n queue = run.catch(() => undefined);\n return run;\n };\n\n const openRecords = async (records: StoredRecord[]): Promise<ChatOutboxRecord[]> => {\n if (records.length === 0) return [];\n const cryptoKey = await keyOf(\"read\");\n if (!cryptoKey) return [];\n const opened: ChatOutboxRecord[] = [];\n for (const record of records) {\n if (!record || record.format > CHAT_OUTBOX_FORMAT) continue;\n try {\n const value = await openJson({\n key: cryptoKey,\n sealed: record.sealed,\n aad: aadOf(record.owner, record.thread, record.key),\n });\n const entry = parseChatOutboxEntry(value);\n if (!entry) continue;\n const claim = readClaim(record.claim);\n const failure = readChatOutboxFailure(record.failure);\n opened.push({ entry, ...(claim && { claim }), ...(failure && { failure }) });\n } catch {\n // Not readable with this key: left as it is (it may be the only copy of someone's work).\n }\n }\n return opened.sort(byQueuedAt);\n };\n\n return {\n async list(threadId) {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const thread = await threadHash(threadId);\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER_THREAD).getAll([owner, thread]),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async listAll() {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAll(owner),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async count() {\n const db = await database();\n if (!db) return 0;\n const owner = await ownerHash();\n const keys = await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAllKeys(owner),\n );\n return keys.length;\n },\n put(entry) {\n return enqueue(async () => {\n try {\n const db = await database();\n if (!db) return false;\n const cryptoKey = await keyOf(\"write\");\n if (!cryptoKey) return false;\n const owner = await ownerHash();\n const thread = await threadHash(entry.threadId);\n const key = await recordKey(entry.threadId, entry.id);\n const sealed = await sealJson({ key: cryptoKey, value: entry, aad: aadOf(owner, thread, key) });\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = await requestToPromise(store.get(key));\n let kept = true;\n // A newer build's record of the same message stays as that build wrote it.\n if (isOwnFormat(existing)) {\n store.put({\n key,\n owner,\n thread,\n at: entry.queuedAt,\n format: CHAT_OUTBOX_FORMAT,\n sealed,\n } satisfies StoredRecord);\n } else {\n kept = false;\n }\n await transactionDone(tx);\n bus.notify();\n return kept;\n } catch {\n return false;\n }\n });\n },\n remove(threadId, id) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n if (isOwnFormat(await requestToPromise(store.get(key)))) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n claim({ threadId, id, claimant, now, leaseMs }) {\n return enqueue(async () => {\n const db = await database();\n // No database: nothing else can hand this message, so the one window holding it may.\n if (!db) return \"claimed\" as const;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n let result: ChatOutboxClaimResult;\n if (!existing) {\n result = \"gone\";\n } else {\n const claim = readClaim(existing.claim);\n if (claim && claim.by !== claimant && now - claim.at < leaseMs) {\n result = \"taken\";\n } else {\n store.put({ ...existing, claim: { by: claimant, at: now } } satisfies StoredRecord);\n result = \"claimed\";\n }\n }\n await transactionDone(tx);\n if (result === \"claimed\") bus.notify();\n return result;\n });\n },\n release({ threadId, id, claimant }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && readClaim(existing.claim)?.by === claimant) {\n const { claim: _claim, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n markFailed({ threadId, id, claimant, failure }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && isOwnFormat(existing)) {\n const { claim, ...rest } = existing;\n const keepClaim = claim !== undefined && readClaim(claim)?.by !== claimant;\n store.put({\n ...rest,\n ...(keepClaim && { claim }),\n failure: { reason: failure.reason, at: failure.at },\n } satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clearFailure({ threadId, id }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing?.failure !== undefined && isOwnFormat(existing)) {\n const { failure: _failure, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clear() {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const owner = await ownerHash();\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const keys = await requestToPromise(store.index(BY_OWNER).getAllKeys(owner));\n for (const key of keys) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n subscribe: (listener) => bus.subscribe(listener),\n };\n}\n\n/** Records kept in a process's memory, with the claim and failure rules every store shares. */\nfunction createRecordMap() {\n const records = new Map<string, ChatOutboxRecord>();\n const keyOf = (threadId: string, id: string) => `${threadId}\\u0000${id}`;\n return {\n records,\n keyOf,\n has: (threadId: string, id: string) => records.has(keyOf(threadId, id)),\n sorted: (filter: (record: ChatOutboxRecord) => boolean) =>\n [...records.values()].filter(filter).map((record) => ({ ...record })).sort(byQueuedAt),\n claim({ threadId, id, claimant, now, leaseMs }: Parameters<ChatOutboxStore[\"claim\"]>[0]): ChatOutboxClaimResult {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return \"gone\";\n if (record.claim && record.claim.by !== claimant && now - record.claim.at < leaseMs) return \"taken\";\n records.set(key, { ...record, claim: { by: claimant, at: now } });\n return \"claimed\";\n },\n release({ threadId, id, claimant }: Parameters<ChatOutboxStore[\"release\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (record?.claim?.by !== claimant) return;\n const { claim: _claim, ...rest } = record;\n records.set(key, rest);\n },\n markFailed({ threadId, id, claimant, failure }: Parameters<ChatOutboxStore[\"markFailed\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return;\n const { claim, ...rest } = record;\n records.set(key, { ...rest, ...(claim && claim.by !== claimant && { claim }), failure: { ...failure } });\n },\n clearFailure({ threadId, id }: Parameters<ChatOutboxStore[\"clearFailure\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record?.failure) return;\n const { failure: _failure, ...rest } = record;\n records.set(key, rest);\n },\n };\n}\n\nfunction createListenerSet() {\n const listeners = new Set<() => void>();\n return {\n notify() {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/**\n * The store contract's memory half, around a store that keeps messages on the device: a message\n * `inner` could not keep (no key, no storage, a failed write) waits in this wrapper's memory,\n * listed with `memoryOnly` and counted, for as long as the wrapper lives. A host keeps one store\n * per person, so switching conversations never loses such a message, and a sign-out still asks\n * about it.\n */\nexport function withChatOutboxMemoryFallback(inner: ChatOutboxStore): ChatOutboxStore {\n const memory = createRecordMap();\n const local = createListenerSet();\n const remembered = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => ({ ...record, memoryOnly: true as const }));\n const merged = (kept: ChatOutboxRecord[], extra: ChatOutboxRecord[]): ChatOutboxRecord[] => {\n const ids = new Set(kept.map((record) => record.entry.id));\n return [...kept, ...extra.filter((record) => !ids.has(record.entry.id))].sort(byQueuedAt);\n };\n return {\n async list(threadId) {\n const kept = await inner.list(threadId).catch(() => []);\n return merged(kept, remembered((record) => record.entry.threadId === threadId));\n },\n async listAll() {\n const kept = await inner.listAll().catch(() => []);\n return merged(kept, remembered(() => true));\n },\n async count() {\n return (await inner.count()) + memory.records.size;\n },\n async put(entry) {\n let kept = false;\n try {\n kept = await inner.put(entry);\n } catch {\n kept = false;\n }\n if (kept) return true;\n memory.records.set(memory.keyOf(entry.threadId, entry.id), { entry });\n local.notify();\n return false;\n },\n async remove(threadId, id) {\n if (memory.records.delete(memory.keyOf(threadId, id))) {\n local.notify();\n return;\n }\n await inner.remove(threadId, id);\n },\n async claim(input) {\n if (!memory.has(input.threadId, input.id)) return inner.claim(input);\n const result = memory.claim(input);\n local.notify();\n return result;\n },\n async release(input) {\n if (!memory.has(input.threadId, input.id)) return inner.release(input);\n memory.release(input);\n local.notify();\n },\n async markFailed(input) {\n if (!memory.has(input.threadId, input.id)) return inner.markFailed(input);\n memory.markFailed(input);\n local.notify();\n },\n async clearFailure(input) {\n if (!memory.has(input.threadId, input.id)) return inner.clearFailure(input);\n memory.clearFailure(input);\n local.notify();\n },\n async clear() {\n memory.records.clear();\n local.notify();\n await inner.clear();\n },\n subscribe(listener) {\n const stopLocal = local.subscribe(listener);\n const stopInner = inner.subscribe(listener);\n return () => {\n stopLocal();\n stopInner();\n };\n },\n };\n}\n\n/**\n * An outbox store in this process's memory: for tests, and for a host that keeps nothing on the\n * device (the messages then wait only as long as the page or app stays open). `durable: false`\n * makes every `put` report that nothing was kept on the device, and lists its messages as\n * `memoryOnly`.\n */\nexport function createMemoryChatOutboxStore({ durable = true }: { durable?: boolean } = {}): ChatOutboxStore & {\n /** The records as stored (tests). */\n records(): ChatOutboxRecord[];\n} {\n const memory = createRecordMap();\n const listeners = createListenerSet();\n const listed = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => (durable ? record : { ...record, memoryOnly: true as const }));\n return {\n records: () => listed(() => true),\n async list(threadId) {\n return listed((record) => record.entry.threadId === threadId);\n },\n async listAll() {\n return listed(() => true);\n },\n async count() {\n return memory.records.size;\n },\n async put(entry) {\n const key = memory.keyOf(entry.threadId, entry.id);\n const existing = memory.records.get(key);\n memory.records.set(key, { ...existing, entry });\n listeners.notify();\n return durable;\n },\n async remove(threadId, id) {\n memory.records.delete(memory.keyOf(threadId, id));\n listeners.notify();\n },\n async claim(input) {\n const result = memory.claim(input);\n if (result === \"claimed\") listeners.notify();\n return result;\n },\n async release(input) {\n memory.release(input);\n listeners.notify();\n },\n async markFailed(input) {\n memory.markFailed(input);\n listeners.notify();\n },\n async clearFailure(input) {\n memory.clearFailure(input);\n listeners.notify();\n },\n async clear() {\n memory.records.clear();\n listeners.notify();\n },\n subscribe: (listener) => listeners.subscribe(listener),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-outbox-store.js","sources":["../../../../agent-runtime/src/chat-outbox-store.ts"],"sourcesContent":["import {\n type IdbDatabase,\n idbGlobals,\n lazySealedDatabase,\n openJson,\n requestToPromise,\n sealJson,\n sha256Hex,\n transactionDone,\n} from \"./sealed-idb\";\n\n/**\n * Where chat messages typed while Athena can't be reached wait until they are sent\n * (`chat-outbox.ts`): one store per person on this device, sealed with a key of theirs.\n *\n * This store can hold the only copy of what someone wrote, so it never deletes as a side effect:\n * a record goes when its message was handed to the conversation and the conversation's own journal\n * took it (`remove`), when the person cancels or discards it, or when the person chooses to sign\n * out (`clear`). A record that does not open (a key that is gone, a newer build's format) is left\n * where it is.\n */\n\n/** An already-uploaded file a waiting message refers to (never a file picked while offline). */\nexport interface ChatOutboxFile {\n /** A server-backed URL (an asset's image route); never a `blob:` or `data:` URL. */\n url: string;\n mediaType: string;\n filename?: string;\n}\n\n/**\n * A file already in Athena that a waiting message carries: an upload that finished before Athena\n * went out of reach (a file picked while it can't be reached is never attached: it can't upload).\n * Its reference rides the message's text, as a sent message's does; this copy names it so the\n * outbox can ask Athena, right before the message goes, that the file is still there and still the\n * person's to open (`ChatOutboxOptions.checkAttachments`).\n */\nexport interface ChatOutboxAttachment {\n assetId: string;\n /** Its name, as the composer showed it. */\n title: string;\n mediaType?: string;\n}\n\nexport interface ChatOutboxEntry {\n /**\n * The message id, minted when the message was typed. The message is sent under it, so a send\n * whose answer was lost can never post twice: statewire replays the same statement, and the\n * thread's host refuses an id it already holds (`duplicate-id`) or re-places one still queued.\n */\n id: string;\n threadId: string;\n /** The text exactly as it will be sent (what the model reads). */\n text: string;\n /** The composer draft the text was serialized from, for giving it back exactly as typed. */\n composerMarkdown?: string;\n /**\n * The draft as a composer whose draft is HTML holds it (the phone's: its mention chips\n * included), given back exactly on Cancel or Edit.\n */\n composerHtml?: string;\n /** The message metadata frozen when it was typed (its composer markdown, …). */\n metadata?: Record<string, unknown>;\n /**\n * The per-send settings the host puts back right before the message goes, kept with it so a\n * reload or another window hands it over with them (Olympus: the agents the message names).\n * Opaque to the outbox, except that a host's send gate may hand messages whose settings differ\n * over separately (`ChatOutboxSendGate`). An older build reads the message without them.\n */\n sendContext?: Record<string, unknown>;\n /** Already-uploaded files that ride along. */\n files?: ChatOutboxFile[];\n /**\n * The files already in Athena the message carries (uploads that finished before Athena went out\n * of reach), asked about right before it goes: a refused one keeps it as \"Couldn't send\".\n */\n attachments?: ChatOutboxAttachment[];\n /** When it was typed (epoch ms): a message that waited more than a day asks before it goes. */\n queuedAt: number;\n /** Typed into a chat that had never started: reopening that chat finds it again. */\n newChat: boolean;\n}\n\n/** Which window is handing a message to its conversation right now, and since when. */\nexport interface ChatOutboxClaim {\n by: string;\n at: number;\n}\n\n/** Why a hand-off failed. */\nexport type ChatOutboxFailureReason =\n /** The conversation refused it: stopped, or parked on an authorization denial. */\n | \"refused\"\n /** The send failed before the conversation's journal took it. */\n | \"error\"\n /** It could not be claimed for sending (the store failed). */\n | \"prepare\"\n /** Athena refused one of its attachments: gone, or no longer the person's to open. */\n | \"attachment\";\n\n/**\n * A failed hand-off, kept with the message: every window (and the next mount) shows it as\n * \"Couldn't send\", and nothing sends it again until the person says Retry.\n */\nexport interface ChatOutboxFailure {\n reason: ChatOutboxFailureReason;\n at: number;\n}\n\nexport interface ChatOutboxRecord {\n entry: ChatOutboxEntry;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n /**\n * Kept only in this store's memory, because this device could not keep it: it waits as long as\n * the page or the app stays open.\n */\n memoryOnly?: true;\n}\n\nexport type ChatOutboxClaimResult = \"claimed\" | \"taken\" | \"gone\";\n\n/**\n * A person's waiting messages. A store keeps every message put into it until it is removed: on\n * the device when it can (`put` resolves `true`), otherwise in its own memory for as long as the\n * store lives (`false`, listed with `memoryOnly`), so a host keeps one store per person and a\n * thread switch never loses a message (`withChatOutboxMemoryFallback`).\n */\nexport interface ChatOutboxStore {\n /** The thread's waiting messages, oldest first. Resolves `[]` when nothing can be read. */\n list(threadId: string): Promise<ChatOutboxRecord[]>;\n /** Every waiting message of this person, oldest first (sign-out, reopening a new chat). */\n listAll(): Promise<ChatOutboxRecord[]>;\n /**\n * How many messages wait, counted without opening them, so a record this build cannot read\n * still counts (a sign-out asks rather than deleting unread work); the ones kept only in memory\n * count too. Rejects when the device's records cannot be counted at all.\n */\n count(): Promise<number>;\n /** Keep a waiting message. Resolves `false` when it could not be kept on this device. */\n put(entry: ChatOutboxEntry): Promise<boolean>;\n remove(threadId: string, id: string): Promise<void>;\n /**\n * Claim a message for handing it to its conversation, atomically across windows: `taken` while\n * another claimant's claim is younger than `leaseMs`, `gone` when the record no longer exists\n * (cancelled, discarded, or handed by another window).\n */\n claim(input: {\n threadId: string;\n id: string;\n claimant: string;\n now: number;\n leaseMs: number;\n }): Promise<ChatOutboxClaimResult>;\n /** Drop `claimant`'s claim (its hand-off never reached the conversation). */\n release(input: { threadId: string; id: string; claimant: string }): Promise<void>;\n /** Keep the hand-off's failure with the message, dropping `claimant`'s claim. */\n markFailed(input: { threadId: string; id: string; claimant: string; failure: ChatOutboxFailure }): Promise<void>;\n /** The person said Retry: the message may be sent again. */\n clearFailure(input: { threadId: string; id: string }): Promise<void>;\n /** Every waiting message of this person, gone (a sign-out the person chose). */\n clear(): Promise<void>;\n /** Called after any change, this window's or another's. */\n subscribe(listener: () => void): () => void;\n}\n\nexport const CHAT_OUTBOX_DATABASE = \"athena-chat-outbox:v1\";\n/** The sealed record's format; an older build leaves a newer one alone. */\nexport const CHAT_OUTBOX_FORMAT = 1;\n\nconst DATABASE_VERSION = 1;\nconst ENTRIES_STORE = \"entries\";\nconst BY_OWNER = \"byOwner\";\nconst BY_OWNER_THREAD = \"byOwnerThread\";\n\ninterface StoredRecord {\n key: string;\n owner: string;\n thread: string;\n at: number;\n format: number;\n sealed: Uint8Array;\n claim?: ChatOutboxClaim;\n failure?: ChatOutboxFailure;\n}\n\nfunction upgradeOutboxDatabase(db: IdbDatabase): void {\n if (!db.objectStoreNames.contains(ENTRIES_STORE)) {\n const store = db.createObjectStore(ENTRIES_STORE, { keyPath: \"key\" });\n store.createIndex(BY_OWNER, \"owner\", { unique: false });\n store.createIndex(BY_OWNER_THREAD, [\"owner\", \"thread\"], { unique: false });\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** A record this build may replace or delete: none, or one in a format it writes itself. */\nfunction isOwnFormat(record: unknown): boolean {\n if (!isRecord(record)) return true;\n const format = record.format;\n return typeof format !== \"number\" || format <= CHAT_OUTBOX_FORMAT;\n}\n\nexport function readChatOutboxClaim(value: unknown): ChatOutboxClaim | undefined {\n if (!isRecord(value) || typeof value.by !== \"string\" || typeof value.at !== \"number\") return undefined;\n return { by: value.by, at: value.at };\n}\n\nconst FAILURE_REASONS: ReadonlySet<string> = new Set<ChatOutboxFailureReason>([\n \"refused\",\n \"error\",\n \"prepare\",\n \"attachment\",\n]);\n\n/** A failure read back from storage; an unknown reason reads as a send error (still failed). */\nexport function readChatOutboxFailure(value: unknown): ChatOutboxFailure | undefined {\n if (!isRecord(value) || typeof value.at !== \"number\") return undefined;\n const reason = typeof value.reason === \"string\" && FAILURE_REASONS.has(value.reason) ? value.reason : \"error\";\n return { reason: reason as ChatOutboxFailureReason, at: value.at };\n}\n\nconst readClaim = readChatOutboxClaim;\n\nfunction readFiles(value: unknown): ChatOutboxFile[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const files: ChatOutboxFile[] = [];\n for (const file of value) {\n if (!isRecord(file) || typeof file.url !== \"string\" || typeof file.mediaType !== \"string\") continue;\n files.push({\n url: file.url,\n mediaType: file.mediaType,\n ...(typeof file.filename === \"string\" && { filename: file.filename }),\n });\n }\n return files.length > 0 ? files : undefined;\n}\n\nfunction readAttachments(value: unknown): ChatOutboxAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const attachments: ChatOutboxAttachment[] = [];\n for (const attachment of value) {\n if (!isRecord(attachment) || typeof attachment.assetId !== \"string\" || attachment.assetId.length === 0) {\n continue;\n }\n attachments.push({\n assetId: attachment.assetId,\n title: typeof attachment.title === \"string\" ? attachment.title : \"\",\n ...(typeof attachment.mediaType === \"string\" && { mediaType: attachment.mediaType }),\n });\n }\n return attachments.length > 0 ? attachments : undefined;\n}\n\n/** A sealed entry read back, or `null` when it is not one. */\nexport function parseChatOutboxEntry(value: unknown): ChatOutboxEntry | null {\n if (!isRecord(value)) return null;\n const { id, threadId, text, queuedAt } = value;\n if (typeof id !== \"string\" || typeof threadId !== \"string\" || typeof text !== \"string\") return null;\n if (typeof queuedAt !== \"number\" || !Number.isFinite(queuedAt)) return null;\n const files = readFiles(value.files);\n const attachments = readAttachments(value.attachments);\n return {\n id,\n threadId,\n text,\n queuedAt,\n newChat: value.newChat === true,\n ...(typeof value.composerMarkdown === \"string\" && { composerMarkdown: value.composerMarkdown }),\n ...(typeof value.composerHtml === \"string\" && { composerHtml: value.composerHtml }),\n ...(isRecord(value.metadata) && { metadata: value.metadata }),\n ...(isRecord(value.sendContext) && !Array.isArray(value.sendContext) && { sendContext: value.sendContext }),\n ...(files && { files }),\n ...(attachments && { attachments }),\n };\n}\n\nfunction byQueuedAt(a: ChatOutboxRecord, b: ChatOutboxRecord): number {\n return a.entry.queuedAt - b.entry.queuedAt;\n}\n\n/** A tiny change bus: this window's listeners, plus other windows' through a BroadcastChannel. */\nfunction createChangeBus(channelName: string): {\n notify: () => void;\n subscribe: (listener: () => void) => () => void;\n} {\n const listeners = new Set<() => void>();\n type Channel = {\n postMessage(message: unknown): void;\n onmessage: ((event: unknown) => void) | null;\n close(): void;\n };\n const ChannelCtor = (globalThis as { BroadcastChannel?: new (name: string) => Channel }).BroadcastChannel;\n let channel: Channel | null = null;\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n };\n return {\n notify() {\n emit();\n try {\n channel?.postMessage(\"changed\");\n } catch {\n // Another window learns of the change on its next read.\n }\n },\n subscribe(listener) {\n listeners.add(listener);\n if (!channel && ChannelCtor) {\n try {\n channel = new ChannelCtor(channelName);\n channel.onmessage = emit;\n } catch {\n channel = null;\n }\n }\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && channel) {\n channel.close();\n channel = null;\n }\n };\n },\n };\n}\n\n/** What a store asks the host's key for: opening kept messages, or keeping a new one. */\nexport type ChatOutboxKeyPurpose = \"read\" | \"write\";\n\nexport interface IndexedDbChatOutboxStoreOptions {\n /** The person this store belongs to (with the environment, the key's scope). */\n ownerId: string;\n /**\n * The person's AES-GCM key, or `null` when there is none for `purpose`: `read` opens what is\n * already kept (a key that exists, whatever the deployment's policy now says, so kept messages\n * always show and drain); `write` keeps a new message (a key only while the deployment lets\n * devices keep copies). Without a key to write with, `put` resolves `false` and the message\n * waits in this store's memory.\n */\n getKey: (ownerId: string, purpose: ChatOutboxKeyPurpose) => Promise<CryptoKey | null>;\n /** An `IDBFactory` (the browser's `indexedDB` by default; a fake one in tests). */\n factory?: object;\n databaseName?: string;\n}\n\n/**\n * The browser's outbox store (Olympus, the desktop app): IndexedDB records sealed with AES-GCM-256\n * under the person's key, the owner and the thread hashed for the index and bound to each record\n * as additional data, so the database names no one and a record moved to another owner or thread\n * fails to open. A message it cannot keep on the device waits in its memory\n * ({@link withChatOutboxMemoryFallback}).\n */\nexport function createIndexedDbChatOutboxStore(options: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n return withChatOutboxMemoryFallback(createSealedIndexedDbChatOutboxStore(options));\n}\n\nfunction createSealedIndexedDbChatOutboxStore({\n ownerId,\n getKey,\n factory = idbGlobals.indexedDB,\n databaseName = CHAT_OUTBOX_DATABASE,\n}: IndexedDbChatOutboxStoreOptions): ChatOutboxStore {\n const database = lazySealedDatabase({\n factory,\n name: databaseName,\n version: DATABASE_VERSION,\n upgrade: upgradeOutboxDatabase,\n });\n const bus = createChangeBus(`${databaseName}:changes`);\n const keyOf = async (purpose: ChatOutboxKeyPurpose): Promise<CryptoKey | null> => {\n try {\n return await getKey(ownerId, purpose);\n } catch {\n return null;\n }\n };\n const ownerHash = () => sha256Hex(ownerId);\n const threadHash = (threadId: string) => sha256Hex(`${ownerId}|${threadId}`);\n const recordKey = (threadId: string, id: string) => sha256Hex(`${ownerId}|${threadId}|${id}`);\n const aadOf = (owner: string, thread: string, key: string) =>\n `athena-chat-outbox|${CHAT_OUTBOX_FORMAT}|${owner}|${thread}|${key}`;\n\n // Writes are serialized so a claim never races a put or a clear in this window; IndexedDB's own\n // transactions order them against other windows.\n let queue: Promise<unknown> = Promise.resolve();\n const enqueue = <T>(task: () => Promise<T>): Promise<T> => {\n const run = queue.then(task, task);\n queue = run.catch(() => undefined);\n return run;\n };\n\n const openRecords = async (records: StoredRecord[]): Promise<ChatOutboxRecord[]> => {\n if (records.length === 0) return [];\n const cryptoKey = await keyOf(\"read\");\n if (!cryptoKey) return [];\n const opened: ChatOutboxRecord[] = [];\n for (const record of records) {\n if (!record || record.format > CHAT_OUTBOX_FORMAT) continue;\n try {\n const value = await openJson({\n key: cryptoKey,\n sealed: record.sealed,\n aad: aadOf(record.owner, record.thread, record.key),\n });\n const entry = parseChatOutboxEntry(value);\n if (!entry) continue;\n const claim = readClaim(record.claim);\n const failure = readChatOutboxFailure(record.failure);\n opened.push({ entry, ...(claim && { claim }), ...(failure && { failure }) });\n } catch {\n // Not readable with this key: left as it is (it may be the only copy of someone's work).\n }\n }\n return opened.sort(byQueuedAt);\n };\n\n return {\n async list(threadId) {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const thread = await threadHash(threadId);\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER_THREAD).getAll([owner, thread]),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async listAll() {\n try {\n const db = await database();\n if (!db) return [];\n const owner = await ownerHash();\n const records = (await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAll(owner),\n )) as StoredRecord[];\n return await openRecords(records);\n } catch {\n return [];\n }\n },\n async count() {\n const db = await database();\n if (!db) return 0;\n const owner = await ownerHash();\n const keys = await requestToPromise(\n db.transaction(ENTRIES_STORE, \"readonly\").objectStore(ENTRIES_STORE).index(BY_OWNER).getAllKeys(owner),\n );\n return keys.length;\n },\n put(entry) {\n return enqueue(async () => {\n try {\n const db = await database();\n if (!db) return false;\n const cryptoKey = await keyOf(\"write\");\n if (!cryptoKey) return false;\n const owner = await ownerHash();\n const thread = await threadHash(entry.threadId);\n const key = await recordKey(entry.threadId, entry.id);\n const sealed = await sealJson({ key: cryptoKey, value: entry, aad: aadOf(owner, thread, key) });\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = await requestToPromise(store.get(key));\n let kept = true;\n // A newer build's record of the same message stays as that build wrote it.\n if (isOwnFormat(existing)) {\n store.put({\n key,\n owner,\n thread,\n at: entry.queuedAt,\n format: CHAT_OUTBOX_FORMAT,\n sealed,\n } satisfies StoredRecord);\n } else {\n kept = false;\n }\n await transactionDone(tx);\n bus.notify();\n return kept;\n } catch {\n return false;\n }\n });\n },\n remove(threadId, id) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n if (isOwnFormat(await requestToPromise(store.get(key)))) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n claim({ threadId, id, claimant, now, leaseMs }) {\n return enqueue(async () => {\n const db = await database();\n // No database: nothing else can hand this message, so the one window holding it may.\n if (!db) return \"claimed\" as const;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n let result: ChatOutboxClaimResult;\n if (!existing) {\n result = \"gone\";\n } else {\n const claim = readClaim(existing.claim);\n if (claim && claim.by !== claimant && now - claim.at < leaseMs) {\n result = \"taken\";\n } else {\n store.put({ ...existing, claim: { by: claimant, at: now } } satisfies StoredRecord);\n result = \"claimed\";\n }\n }\n await transactionDone(tx);\n if (result === \"claimed\") bus.notify();\n return result;\n });\n },\n release({ threadId, id, claimant }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && readClaim(existing.claim)?.by === claimant) {\n const { claim: _claim, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n markFailed({ threadId, id, claimant, failure }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing && isOwnFormat(existing)) {\n const { claim, ...rest } = existing;\n const keepClaim = claim !== undefined && readClaim(claim)?.by !== claimant;\n store.put({\n ...rest,\n ...(keepClaim && { claim }),\n failure: { reason: failure.reason, at: failure.at },\n } satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clearFailure({ threadId, id }) {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const key = await recordKey(threadId, id);\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const existing = (await requestToPromise(store.get(key))) as StoredRecord | undefined;\n if (existing?.failure !== undefined && isOwnFormat(existing)) {\n const { failure: _failure, ...rest } = existing;\n store.put(rest satisfies StoredRecord);\n }\n await transactionDone(tx);\n bus.notify();\n });\n },\n clear() {\n return enqueue(async () => {\n const db = await database();\n if (!db) return;\n const owner = await ownerHash();\n const tx = db.transaction(ENTRIES_STORE, \"readwrite\");\n const store = tx.objectStore(ENTRIES_STORE);\n const keys = await requestToPromise(store.index(BY_OWNER).getAllKeys(owner));\n for (const key of keys) store.delete(key);\n await transactionDone(tx);\n bus.notify();\n });\n },\n subscribe: (listener) => bus.subscribe(listener),\n };\n}\n\n/** Records kept in a process's memory, with the claim and failure rules every store shares. */\nfunction createRecordMap() {\n const records = new Map<string, ChatOutboxRecord>();\n const keyOf = (threadId: string, id: string) => `${threadId}\\u0000${id}`;\n return {\n records,\n keyOf,\n has: (threadId: string, id: string) => records.has(keyOf(threadId, id)),\n sorted: (filter: (record: ChatOutboxRecord) => boolean) =>\n [...records.values()].filter(filter).map((record) => ({ ...record })).sort(byQueuedAt),\n claim({ threadId, id, claimant, now, leaseMs }: Parameters<ChatOutboxStore[\"claim\"]>[0]): ChatOutboxClaimResult {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return \"gone\";\n if (record.claim && record.claim.by !== claimant && now - record.claim.at < leaseMs) return \"taken\";\n records.set(key, { ...record, claim: { by: claimant, at: now } });\n return \"claimed\";\n },\n release({ threadId, id, claimant }: Parameters<ChatOutboxStore[\"release\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (record?.claim?.by !== claimant) return;\n const { claim: _claim, ...rest } = record;\n records.set(key, rest);\n },\n markFailed({ threadId, id, claimant, failure }: Parameters<ChatOutboxStore[\"markFailed\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record) return;\n const { claim, ...rest } = record;\n records.set(key, { ...rest, ...(claim && claim.by !== claimant && { claim }), failure: { ...failure } });\n },\n clearFailure({ threadId, id }: Parameters<ChatOutboxStore[\"clearFailure\"]>[0]): void {\n const key = keyOf(threadId, id);\n const record = records.get(key);\n if (!record?.failure) return;\n const { failure: _failure, ...rest } = record;\n records.set(key, rest);\n },\n };\n}\n\nfunction createListenerSet() {\n const listeners = new Set<() => void>();\n return {\n notify() {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // A listener's fault never reaches the store.\n }\n }\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/**\n * The store contract's memory half, around a store that keeps messages on the device: a message\n * `inner` could not keep (no key, no storage, a failed write) waits in this wrapper's memory,\n * listed with `memoryOnly` and counted, for as long as the wrapper lives. A host keeps one store\n * per person, so switching conversations never loses such a message, and a sign-out still asks\n * about it.\n */\nexport function withChatOutboxMemoryFallback(inner: ChatOutboxStore): ChatOutboxStore {\n const memory = createRecordMap();\n const local = createListenerSet();\n const remembered = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => ({ ...record, memoryOnly: true as const }));\n const merged = (kept: ChatOutboxRecord[], extra: ChatOutboxRecord[]): ChatOutboxRecord[] => {\n const ids = new Set(kept.map((record) => record.entry.id));\n return [...kept, ...extra.filter((record) => !ids.has(record.entry.id))].sort(byQueuedAt);\n };\n return {\n async list(threadId) {\n const kept = await inner.list(threadId).catch(() => []);\n return merged(kept, remembered((record) => record.entry.threadId === threadId));\n },\n async listAll() {\n const kept = await inner.listAll().catch(() => []);\n return merged(kept, remembered(() => true));\n },\n async count() {\n return (await inner.count()) + memory.records.size;\n },\n async put(entry) {\n let kept = false;\n try {\n kept = await inner.put(entry);\n } catch {\n kept = false;\n }\n if (kept) return true;\n memory.records.set(memory.keyOf(entry.threadId, entry.id), { entry });\n local.notify();\n return false;\n },\n async remove(threadId, id) {\n if (memory.records.delete(memory.keyOf(threadId, id))) {\n local.notify();\n return;\n }\n await inner.remove(threadId, id);\n },\n async claim(input) {\n if (!memory.has(input.threadId, input.id)) return inner.claim(input);\n const result = memory.claim(input);\n local.notify();\n return result;\n },\n async release(input) {\n if (!memory.has(input.threadId, input.id)) return inner.release(input);\n memory.release(input);\n local.notify();\n },\n async markFailed(input) {\n if (!memory.has(input.threadId, input.id)) return inner.markFailed(input);\n memory.markFailed(input);\n local.notify();\n },\n async clearFailure(input) {\n if (!memory.has(input.threadId, input.id)) return inner.clearFailure(input);\n memory.clearFailure(input);\n local.notify();\n },\n async clear() {\n memory.records.clear();\n local.notify();\n await inner.clear();\n },\n subscribe(listener) {\n const stopLocal = local.subscribe(listener);\n const stopInner = inner.subscribe(listener);\n return () => {\n stopLocal();\n stopInner();\n };\n },\n };\n}\n\n/**\n * An outbox store in this process's memory: for tests, and for a host that keeps nothing on the\n * device (the messages then wait only as long as the page or app stays open). `durable: false`\n * makes every `put` report that nothing was kept on the device, and lists its messages as\n * `memoryOnly`.\n */\nexport function createMemoryChatOutboxStore({ durable = true }: { durable?: boolean } = {}): ChatOutboxStore & {\n /** The records as stored (tests). */\n records(): ChatOutboxRecord[];\n} {\n const memory = createRecordMap();\n const listeners = createListenerSet();\n const listed = (filter: (record: ChatOutboxRecord) => boolean): ChatOutboxRecord[] =>\n memory.sorted(filter).map((record) => (durable ? record : { ...record, memoryOnly: true as const }));\n return {\n records: () => listed(() => true),\n async list(threadId) {\n return listed((record) => record.entry.threadId === threadId);\n },\n async listAll() {\n return listed(() => true);\n },\n async count() {\n return memory.records.size;\n },\n async put(entry) {\n const key = memory.keyOf(entry.threadId, entry.id);\n const existing = memory.records.get(key);\n memory.records.set(key, { ...existing, entry });\n listeners.notify();\n return durable;\n },\n async remove(threadId, id) {\n memory.records.delete(memory.keyOf(threadId, id));\n listeners.notify();\n },\n async claim(input) {\n const result = memory.claim(input);\n if (result === \"claimed\") listeners.notify();\n return result;\n },\n async release(input) {\n memory.release(input);\n listeners.notify();\n },\n async markFailed(input) {\n memory.markFailed(input);\n listeners.notify();\n },\n async clearFailure(input) {\n memory.clearFailure(input);\n listeners.notify();\n },\n async clear() {\n memory.records.clear();\n listeners.notify();\n },\n subscribe: (listener) => listeners.subscribe(listener),\n };\n}\n"],"names":[],"mappings":"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"}
@@ -89,6 +89,7 @@ function createChatOutbox(options) {
89
89
  openThread,
90
90
  onEvent,
91
91
  checkAttachments,
92
+ sendGate,
92
93
  now = () => Date.now(),
93
94
  countdownMs = CHAT_OUTBOX_COUNTDOWN_MS,
94
95
  askAfterMs = CHAT_OUTBOX_ASK_AFTER_MS,
@@ -167,11 +168,17 @@ function createChatOutbox(options) {
167
168
  if (line.askId === entry.id) return { kind: "ask" };
168
169
  return { kind: "waiting" };
169
170
  };
171
+ const keyOf = (entry) => (sendGate == null ? void 0 : sendGate.keyOf(entry)) ?? "";
172
+ const mayHandOff = (entry) => (sendGate == null ? void 0 : sendGate.mayHandOff(entry)) ?? true;
170
173
  const computeLine = () => {
171
174
  const run = [];
175
+ let lineKey = null;
172
176
  for (const entry of entries) {
173
177
  const phase = phaseOf(entry.id);
174
178
  if (phase.kind === "failed") continue;
179
+ const key = keyOf(entry);
180
+ if (lineKey === null) lineKey = key;
181
+ else if (key !== lineKey) break;
175
182
  if (isBusy(phase) || claimedElsewhere(entry.id)) continue;
176
183
  if (handedInRegain.get(entry.id) === regain && !sendNow.has(entry.id)) continue;
177
184
  if (!sendNow.has(entry.id) && now() - entry.queuedAt > askAfterMs) {
@@ -179,6 +186,8 @@ function createChatOutbox(options) {
179
186
  }
180
187
  run.push(entry);
181
188
  }
189
+ const head = run[0];
190
+ if (head !== void 0 && !mayHandOff(head)) return { run: [], askId: null };
182
191
  return { run, askId: null };
183
192
  };
184
193
  const gateOpen = () => reachability.get() && foreground.get();
@@ -334,22 +343,46 @@ function createChatOutbox(options) {
334
343
  function releaseReady() {
335
344
  if (releasing || disposed) return;
336
345
  releasing = true;
346
+ let heldBack = false;
337
347
  try {
338
348
  for (const entry of [...entries]) {
339
349
  if (phaseOf(entry.id).kind !== "claiming") continue;
340
350
  const auto = readyToGo.get(entry.id);
341
351
  if (auto === void 0) return;
352
+ if (!mayHandOff(entry)) {
353
+ holdBackFrom(entry);
354
+ heldBack = true;
355
+ return;
356
+ }
342
357
  readyToGo.delete(entry.id);
343
358
  if (stillGoes(entry)) dispatch(entry, auto);
344
359
  }
345
360
  } finally {
346
361
  releasing = false;
362
+ if (heldBack) evaluateLine();
363
+ }
364
+ }
365
+ function holdBackFrom(first) {
366
+ let behind = false;
367
+ for (const entry of entries) {
368
+ if (entry.id === first.id) behind = true;
369
+ if (!behind || phaseOf(entry.id).kind !== "claiming") continue;
370
+ readyToGo.delete(entry.id);
371
+ phases.set(entry.id, { kind: "idle" });
372
+ releaseClaim(entry.id);
347
373
  }
348
374
  }
349
375
  const dispatch = (entry, auto) => {
350
376
  handedInRegain.set(entry.id, regain);
351
377
  phases.set(entry.id, { kind: "handed", journaled: false });
352
- emit({ type: "handed", threadId, messageId: entry.id, auto, ageMs: now() - entry.queuedAt });
378
+ emit({
379
+ type: "handed",
380
+ threadId,
381
+ messageId: entry.id,
382
+ auto,
383
+ ageMs: now() - entry.queuedAt,
384
+ ...entry.sendContext !== void 0 && { sendContext: entry.sendContext }
385
+ });
353
386
  let sent;
354
387
  try {
355
388
  sent = thread.send(entry);
@@ -396,6 +429,7 @@ function createChatOutbox(options) {
396
429
  if (!entry) continue;
397
430
  const phase = phaseOf(id);
398
431
  if (phase.kind !== "idle" || claimedElsewhere(id)) continue;
432
+ if (!mayHandOff(entry)) break;
399
433
  handOff(entry, true);
400
434
  }
401
435
  evaluate();
@@ -439,11 +473,16 @@ function createChatOutbox(options) {
439
473
  if (phaseOf(entry.id).kind === "awaiting-thread") phases.set(entry.id, { kind: "idle" });
440
474
  }
441
475
  } else if (thread.readiness() !== "connecting") {
476
+ let held2 = false;
477
+ let handedKey = null;
442
478
  for (const entry of entries) {
443
- if (phaseOf(entry.id).kind === "awaiting-thread") {
444
- phases.set(entry.id, { kind: "idle" });
445
- handOff(entry, true);
446
- }
479
+ if (phaseOf(entry.id).kind !== "awaiting-thread") continue;
480
+ phases.set(entry.id, { kind: "idle" });
481
+ const key = keyOf(entry);
482
+ held2 || (held2 = handedKey !== null && key !== handedKey || !mayHandOff(entry));
483
+ if (held2) continue;
484
+ handedKey = key;
485
+ handOff(entry, true);
447
486
  }
448
487
  }
449
488
  }
@@ -554,6 +593,7 @@ function createChatOutbox(options) {
554
593
  reachability.subscribe(evaluate),
555
594
  foreground.subscribe(evaluate),
556
595
  thread.subscribe(evaluate),
596
+ ...sendGate ? [sendGate.subscribe(evaluate)] : [],
557
597
  ...store ? [store.subscribe(reload)] : []
558
598
  ];
559
599
  reload();
@@ -586,6 +626,7 @@ function createChatOutbox(options) {
586
626
  ...input.composerMarkdown !== void 0 && { composerMarkdown: input.composerMarkdown },
587
627
  ...input.composerHtml !== void 0 && { composerHtml: input.composerHtml },
588
628
  ...input.metadata !== void 0 && { metadata: input.metadata },
629
+ ...input.sendContext !== void 0 && { sendContext: input.sendContext },
589
630
  ...input.files !== void 0 && input.files.length > 0 && { files: input.files },
590
631
  ...input.attachments !== void 0 && input.attachments.length > 0 && { attachments: input.attachments }
591
632
  };