@athenaintel/react 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (761) hide show
  1. package/README.md +418 -10
  2. package/dist/agent-runtime/src/asset-attachment.cjs.map +1 -0
  3. package/dist/agent-runtime/src/asset-attachment.js.map +1 -0
  4. package/dist/agent-runtime/src/auth-denial.cjs.map +1 -0
  5. package/dist/agent-runtime/src/auth-denial.js.map +1 -0
  6. package/dist/agent-runtime/src/client-tool-bridge.cjs.map +1 -0
  7. package/dist/agent-runtime/src/client-tool-bridge.js.map +1 -0
  8. package/dist/agent-runtime/src/client-tools.cjs.map +1 -0
  9. package/dist/agent-runtime/src/client-tools.js.map +1 -0
  10. package/dist/agent-runtime/src/composer-markdown.cjs +124 -0
  11. package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -0
  12. package/dist/agent-runtime/src/composer-markdown.js +124 -0
  13. package/dist/agent-runtime/src/composer-markdown.js.map +1 -0
  14. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -0
  15. package/dist/agent-runtime/src/conversation-index.js.map +1 -0
  16. package/dist/{packages/agent-runtime → agent-runtime}/src/conversation-list.cjs +36 -0
  17. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -0
  18. package/dist/{packages/agent-runtime → agent-runtime}/src/conversation-list.js +36 -0
  19. package/dist/agent-runtime/src/conversation-list.js.map +1 -0
  20. package/dist/{packages/agent-runtime → agent-runtime}/src/deep-agent-transport-recipe.cjs +5 -3
  21. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -0
  22. package/dist/{packages/agent-runtime → agent-runtime}/src/deep-agent-transport-recipe.js +6 -4
  23. package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -0
  24. package/dist/agent-runtime/src/edit-command.cjs.map +1 -0
  25. package/dist/agent-runtime/src/edit-command.js.map +1 -0
  26. package/dist/{packages/agent-runtime → agent-runtime}/src/errors.cjs +23 -0
  27. package/dist/agent-runtime/src/errors.cjs.map +1 -0
  28. package/dist/{packages/agent-runtime → agent-runtime}/src/errors.js +23 -0
  29. package/dist/agent-runtime/src/errors.js.map +1 -0
  30. package/dist/agent-runtime/src/execute-output.cjs +41 -0
  31. package/dist/agent-runtime/src/execute-output.cjs.map +1 -0
  32. package/dist/agent-runtime/src/execute-output.js +41 -0
  33. package/dist/agent-runtime/src/execute-output.js.map +1 -0
  34. package/dist/{packages/agent-runtime → agent-runtime}/src/generated/runconfig-contract.cjs +12 -2
  35. package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -0
  36. package/dist/{packages/agent-runtime → agent-runtime}/src/generated/runconfig-contract.js +12 -2
  37. package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -0
  38. package/dist/agent-runtime/src/github-wake-event.cjs.map +1 -0
  39. package/dist/agent-runtime/src/github-wake-event.js.map +1 -0
  40. package/dist/{packages/agent-runtime → agent-runtime}/src/interrupts.cjs +24 -1
  41. package/dist/agent-runtime/src/interrupts.cjs.map +1 -0
  42. package/dist/{packages/agent-runtime → agent-runtime}/src/interrupts.js +24 -1
  43. package/dist/agent-runtime/src/interrupts.js.map +1 -0
  44. package/dist/agent-runtime/src/message-author.cjs.map +1 -0
  45. package/dist/agent-runtime/src/message-author.js.map +1 -0
  46. package/dist/agent-runtime/src/message-window.cjs.map +1 -0
  47. package/dist/agent-runtime/src/message-window.js.map +1 -0
  48. package/dist/{packages/agent-runtime → agent-runtime}/src/messages.cjs +8 -4
  49. package/dist/agent-runtime/src/messages.cjs.map +1 -0
  50. package/dist/{packages/agent-runtime → agent-runtime}/src/messages.js +6 -2
  51. package/dist/agent-runtime/src/messages.js.map +1 -0
  52. package/dist/agent-runtime/src/model-context.cjs.map +1 -0
  53. package/dist/agent-runtime/src/model-context.js.map +1 -0
  54. package/dist/agent-runtime/src/optimistic-content.cjs.map +1 -0
  55. package/dist/agent-runtime/src/optimistic-content.js.map +1 -0
  56. package/dist/agent-runtime/src/protocol.cjs.map +1 -0
  57. package/dist/agent-runtime/src/protocol.js.map +1 -0
  58. package/dist/{packages/agent-runtime → agent-runtime}/src/queue.cjs +23 -2
  59. package/dist/agent-runtime/src/queue.cjs.map +1 -0
  60. package/dist/{packages/agent-runtime → agent-runtime}/src/queue.js +23 -2
  61. package/dist/agent-runtime/src/queue.js.map +1 -0
  62. package/dist/{packages/agent-runtime → agent-runtime}/src/quota-safe-local-storage-session.cjs +23 -13
  63. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -0
  64. package/dist/{packages/agent-runtime → agent-runtime}/src/quota-safe-local-storage-session.js +24 -14
  65. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -0
  66. package/dist/agent-runtime/src/runtime-messages.cjs.map +1 -0
  67. package/dist/agent-runtime/src/runtime-messages.js.map +1 -0
  68. package/dist/agent-runtime/src/screen-point.cjs.map +1 -0
  69. package/dist/agent-runtime/src/screen-point.js.map +1 -0
  70. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -0
  71. package/dist/agent-runtime/src/send-recovery.js.map +1 -0
  72. package/dist/agent-runtime/src/statewire-attach-observer.cjs.map +1 -0
  73. package/dist/agent-runtime/src/statewire-attach-observer.js.map +1 -0
  74. package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-state.cjs +8 -0
  75. package/dist/agent-runtime/src/statewire-state.cjs.map +1 -0
  76. package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-state.js +8 -0
  77. package/dist/agent-runtime/src/statewire-state.js.map +1 -0
  78. package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-suspended.cjs +4 -0
  79. package/dist/agent-runtime/src/statewire-suspended.cjs.map +1 -0
  80. package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-suspended.js +4 -0
  81. package/dist/agent-runtime/src/statewire-suspended.js.map +1 -0
  82. package/dist/agent-runtime/src/statewire-transport.cjs +43 -0
  83. package/dist/agent-runtime/src/statewire-transport.cjs.map +1 -0
  84. package/dist/agent-runtime/src/statewire-transport.js +43 -0
  85. package/dist/agent-runtime/src/statewire-transport.js.map +1 -0
  86. package/dist/agent-runtime/src/streaming-markdown.cjs.map +1 -0
  87. package/dist/agent-runtime/src/streaming-markdown.js.map +1 -0
  88. package/dist/agent-runtime/src/subscription-wake-event.cjs +252 -0
  89. package/dist/agent-runtime/src/subscription-wake-event.cjs.map +1 -0
  90. package/dist/agent-runtime/src/subscription-wake-event.js +252 -0
  91. package/dist/agent-runtime/src/subscription-wake-event.js.map +1 -0
  92. package/dist/agent-runtime/src/thread-extras.cjs.map +1 -0
  93. package/dist/agent-runtime/src/thread-extras.js.map +1 -0
  94. package/dist/agent-runtime/src/thread-snapshot-cache.cjs.map +1 -0
  95. package/dist/agent-runtime/src/thread-snapshot-cache.js.map +1 -0
  96. package/dist/agent-runtime/src/thread-status.cjs.map +1 -0
  97. package/dist/agent-runtime/src/thread-status.js.map +1 -0
  98. package/dist/{packages/agent-runtime → agent-runtime}/src/tool-metadata.cjs +365 -3
  99. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -0
  100. package/dist/{packages/agent-runtime → agent-runtime}/src/tool-metadata.js +365 -3
  101. package/dist/agent-runtime/src/tool-metadata.js.map +1 -0
  102. package/dist/agent-runtime/src/tool-names.cjs +5 -0
  103. package/dist/agent-runtime/src/tool-names.cjs.map +1 -0
  104. package/dist/agent-runtime/src/tool-names.js +5 -0
  105. package/dist/agent-runtime/src/tool-names.js.map +1 -0
  106. package/dist/agent-runtime/src/tool-outputs.cjs.map +1 -0
  107. package/dist/agent-runtime/src/tool-outputs.js.map +1 -0
  108. package/dist/agent-runtime/src/tool-result.cjs.map +1 -0
  109. package/dist/agent-runtime/src/tool-result.js.map +1 -0
  110. package/dist/{packages/agent-runtime → agent-runtime}/src/use-conversation-list.cjs +33 -22
  111. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -0
  112. package/dist/{packages/agent-runtime → agent-runtime}/src/use-conversation-list.js +34 -23
  113. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -0
  114. package/dist/agent-runtime/src/use-deep-agent-config.cjs.map +1 -0
  115. package/dist/agent-runtime/src/use-deep-agent-config.js.map +1 -0
  116. package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-edit-submit.cjs +2 -0
  117. package/dist/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -0
  118. package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-edit-submit.js +2 -0
  119. package/dist/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -0
  120. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -0
  121. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -0
  122. package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-thread.cjs +1 -0
  123. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -0
  124. package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-thread.js +2 -1
  125. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -0
  126. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -0
  127. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -0
  128. package/dist/agent-runtime/src/use-historical-edit-attachments.cjs +52 -0
  129. package/dist/agent-runtime/src/use-historical-edit-attachments.cjs.map +1 -0
  130. package/dist/agent-runtime/src/use-historical-edit-attachments.js +52 -0
  131. package/dist/agent-runtime/src/use-historical-edit-attachments.js.map +1 -0
  132. package/dist/{packages/agent-runtime → agent-runtime}/src/voice-presentation.cjs +14 -0
  133. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -0
  134. package/dist/{packages/agent-runtime → agent-runtime}/src/voice-presentation.js +14 -0
  135. package/dist/agent-runtime/src/voice-presentation.js.map +1 -0
  136. package/dist/agent-runtime/src/voice-turn-grouping.cjs.map +1 -0
  137. package/dist/agent-runtime/src/voice-turn-grouping.js.map +1 -0
  138. package/dist/assets/AthenaAssetEmbed.cjs +8 -2
  139. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  140. package/dist/assets/AthenaAssetEmbed.d.ts +8 -0
  141. package/dist/assets/AthenaAssetEmbed.js +8 -2
  142. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  143. package/dist/assets/AthenaLayout.cjs +97 -31
  144. package/dist/assets/AthenaLayout.cjs.map +1 -1
  145. package/dist/assets/AthenaLayout.d.ts +13 -0
  146. package/dist/assets/AthenaLayout.js +98 -32
  147. package/dist/assets/AthenaLayout.js.map +1 -1
  148. package/dist/assets/asset-embed-messages.cjs.map +1 -1
  149. package/dist/assets/asset-embed-messages.d.ts +6 -0
  150. package/dist/assets/asset-embed-messages.js.map +1 -1
  151. package/dist/assets/asset-panel-store.cjs +68 -32
  152. package/dist/assets/asset-panel-store.cjs.map +1 -1
  153. package/dist/assets/asset-panel-store.d.ts +47 -1
  154. package/dist/assets/asset-panel-store.js +68 -32
  155. package/dist/assets/asset-panel-store.js.map +1 -1
  156. package/dist/assets/use-asset-embed.cjs +29 -8
  157. package/dist/assets/use-asset-embed.cjs.map +1 -1
  158. package/dist/assets/use-asset-embed.d.ts +7 -0
  159. package/dist/assets/use-asset-embed.js +29 -8
  160. package/dist/assets/use-asset-embed.js.map +1 -1
  161. package/dist/catalog/chat-pickers.cjs +152 -0
  162. package/dist/catalog/chat-pickers.cjs.map +1 -0
  163. package/dist/catalog/chat-pickers.d.ts +22 -0
  164. package/dist/catalog/chat-pickers.js +152 -0
  165. package/dist/catalog/chat-pickers.js.map +1 -0
  166. package/dist/catalog/runtime-catalog.cjs +181 -0
  167. package/dist/catalog/runtime-catalog.cjs.map +1 -0
  168. package/dist/catalog/runtime-catalog.d.ts +100 -0
  169. package/dist/catalog/runtime-catalog.js +181 -0
  170. package/dist/catalog/runtime-catalog.js.map +1 -0
  171. package/dist/chat/AthenaChat.cjs +86 -46
  172. package/dist/chat/AthenaChat.cjs.map +1 -1
  173. package/dist/chat/AthenaChat.d.ts +21 -4
  174. package/dist/chat/AthenaChat.js +88 -48
  175. package/dist/chat/AthenaChat.js.map +1 -1
  176. package/dist/chat/StatewireApprovalCard.cjs +38 -37
  177. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  178. package/dist/chat/StatewireApprovalCard.d.ts +2 -0
  179. package/dist/chat/StatewireApprovalCard.js +39 -38
  180. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  181. package/dist/chat/StatewireClientToolBridge.cjs +2 -2
  182. package/dist/chat/StatewireClientToolBridge.js +2 -2
  183. package/dist/chat/StatewireQueuedMessages.cjs +15 -12
  184. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  185. package/dist/chat/StatewireQueuedMessages.d.ts +22 -2
  186. package/dist/chat/StatewireQueuedMessages.js +15 -12
  187. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  188. package/dist/chat/StatewireRunRefusalNotice.cjs +48 -0
  189. package/dist/chat/StatewireRunRefusalNotice.cjs.map +1 -0
  190. package/dist/chat/StatewireRunRefusalNotice.d.ts +25 -0
  191. package/dist/chat/StatewireRunRefusalNotice.js +48 -0
  192. package/dist/chat/StatewireRunRefusalNotice.js.map +1 -0
  193. package/dist/chat/WaitingOnYouBanner.cjs +2 -2
  194. package/dist/chat/WaitingOnYouBanner.js +2 -2
  195. package/dist/chat/athena-citation-links.cjs +19 -15
  196. package/dist/chat/athena-citation-links.cjs.map +1 -1
  197. package/dist/chat/athena-citation-links.js +19 -15
  198. package/dist/chat/athena-citation-links.js.map +1 -1
  199. package/dist/chat/run-activity.cjs +9 -1
  200. package/dist/chat/run-activity.cjs.map +1 -1
  201. package/dist/chat/run-activity.d.ts +6 -0
  202. package/dist/chat/run-activity.js +10 -2
  203. package/dist/chat/run-activity.js.map +1 -1
  204. package/dist/chat/statewire-banners.cjs +30 -1
  205. package/dist/chat/statewire-banners.cjs.map +1 -1
  206. package/dist/chat/statewire-banners.d.ts +20 -1
  207. package/dist/chat/statewire-banners.js +32 -3
  208. package/dist/chat/statewire-banners.js.map +1 -1
  209. package/dist/chat/statewire-client-tools.cjs +1 -1
  210. package/dist/chat/statewire-client-tools.js +1 -1
  211. package/dist/chat/super-grouping/CollapsibleGroup.cjs +1 -1
  212. package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
  213. package/dist/chat/super-grouping/CollapsibleGroup.js +1 -1
  214. package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
  215. package/dist/chat/super-grouping/SuperGroupingCard.cjs +7 -5
  216. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  217. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +8 -2
  218. package/dist/chat/super-grouping/SuperGroupingCard.js +7 -5
  219. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  220. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +350 -0
  221. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -0
  222. package/dist/chat/super-grouping/SuperGroupingTranscript.d.ts +31 -0
  223. package/dist/chat/super-grouping/SuperGroupingTranscript.js +350 -0
  224. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -0
  225. package/dist/chat/super-grouping/ToolTimeline.cjs +13 -2
  226. package/dist/chat/super-grouping/ToolTimeline.cjs.map +1 -1
  227. package/dist/chat/super-grouping/ToolTimeline.d.ts +10 -3
  228. package/dist/chat/super-grouping/ToolTimeline.js +13 -2
  229. package/dist/chat/super-grouping/ToolTimeline.js.map +1 -1
  230. package/dist/chat/super-grouping/activity-timeline.cjs +51 -26
  231. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  232. package/dist/chat/super-grouping/activity-timeline.d.ts +40 -8
  233. package/dist/chat/super-grouping/activity-timeline.js +52 -27
  234. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  235. package/dist/chat/super-grouping/index.cjs +12 -0
  236. package/dist/chat/super-grouping/index.cjs.map +1 -1
  237. package/dist/chat/super-grouping/index.d.ts +7 -2
  238. package/dist/chat/super-grouping/index.js +14 -2
  239. package/dist/chat/super-grouping/index.js.map +1 -1
  240. package/dist/chat/super-grouping/timeline-step.cjs +14 -5
  241. package/dist/chat/super-grouping/timeline-step.cjs.map +1 -1
  242. package/dist/chat/super-grouping/timeline-step.d.ts +10 -1
  243. package/dist/chat/super-grouping/timeline-step.js +15 -6
  244. package/dist/chat/super-grouping/timeline-step.js.map +1 -1
  245. package/dist/chat/super-grouping/turn-segments.cjs +82 -0
  246. package/dist/chat/super-grouping/turn-segments.cjs.map +1 -0
  247. package/dist/chat/super-grouping/turn-segments.d.ts +69 -0
  248. package/dist/chat/super-grouping/turn-segments.js +82 -0
  249. package/dist/chat/super-grouping/turn-segments.js.map +1 -0
  250. package/dist/chat/super-grouping/turn-timing.cjs +110 -0
  251. package/dist/chat/super-grouping/turn-timing.cjs.map +1 -0
  252. package/dist/chat/super-grouping/turn-timing.d.ts +52 -0
  253. package/dist/chat/super-grouping/turn-timing.js +110 -0
  254. package/dist/chat/super-grouping/turn-timing.js.map +1 -0
  255. package/dist/chat/tiptap-text.cjs +1 -1
  256. package/dist/chat/tiptap-text.js +1 -1
  257. package/dist/chat/use-athena-run-live.cjs +1 -1
  258. package/dist/chat/use-athena-run-live.js +1 -1
  259. package/dist/chat/use-statewire-approval-request.cjs +73 -4
  260. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  261. package/dist/chat/use-statewire-approval-request.js +74 -5
  262. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  263. package/dist/chat-ui/composer/composer-message.cjs +284 -0
  264. package/dist/chat-ui/composer/composer-message.cjs.map +1 -0
  265. package/dist/chat-ui/composer/composer-message.d.ts +152 -0
  266. package/dist/chat-ui/composer/composer-message.js +284 -0
  267. package/dist/chat-ui/composer/composer-message.js.map +1 -0
  268. package/dist/chat-ui/composer/tiptap-composer.cjs +57 -11
  269. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  270. package/dist/chat-ui/composer/tiptap-composer.d.ts +34 -6
  271. package/dist/chat-ui/composer/tiptap-composer.js +57 -11
  272. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  273. package/dist/chat-ui/index.cjs +4 -1
  274. package/dist/chat-ui/index.cjs.map +1 -1
  275. package/dist/chat-ui/index.d.ts +3 -2
  276. package/dist/chat-ui/index.js +5 -2
  277. package/dist/chat-ui/index.js.map +1 -1
  278. package/dist/chat-ui/mentions/mention-chip.cjs +3 -2
  279. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  280. package/dist/chat-ui/mentions/mention-chip.js +4 -3
  281. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  282. package/dist/chat-ui/mentions/mention-extension.cjs +8 -2
  283. package/dist/chat-ui/mentions/mention-extension.cjs.map +1 -1
  284. package/dist/chat-ui/mentions/mention-extension.js +8 -2
  285. package/dist/chat-ui/mentions/mention-extension.js.map +1 -1
  286. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-popup.cjs +3 -1
  287. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-popup.cjs.map +1 -1
  288. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-popup.js +3 -1
  289. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-popup.js.map +1 -1
  290. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +65 -19
  291. package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
  292. package/dist/chat-ui/mentions/suggestions/data/sources/index.d.ts +15 -0
  293. package/dist/chat-ui/mentions/suggestions/data/sources/index.js +67 -21
  294. package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
  295. package/dist/chat-ui/mentions/suggestions/data/use-mention-suggestions.cjs +4 -2
  296. package/dist/chat-ui/mentions/suggestions/data/use-mention-suggestions.cjs.map +1 -1
  297. package/dist/chat-ui/mentions/suggestions/data/use-mention-suggestions.d.ts +2 -1
  298. package/dist/chat-ui/mentions/suggestions/data/use-mention-suggestions.js +5 -3
  299. package/dist/chat-ui/mentions/suggestions/data/use-mention-suggestions.js.map +1 -1
  300. package/dist/chat-ui/messages/message-components.cjs +30 -10
  301. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  302. package/dist/chat-ui/messages/message-components.d.ts +21 -1
  303. package/dist/chat-ui/messages/message-components.js +30 -10
  304. package/dist/chat-ui/messages/message-components.js.map +1 -1
  305. package/dist/chat-ui/messages/runtime-message.cjs +1 -1
  306. package/dist/chat-ui/messages/runtime-message.js +1 -1
  307. package/dist/chat-ui/messages/statewire-continue-run.cjs +5 -5
  308. package/dist/chat-ui/messages/statewire-continue-run.js +5 -5
  309. package/dist/chat-ui/messages/user-edit-composer.cjs +1 -1
  310. package/dist/chat-ui/messages/user-edit-composer.js +1 -1
  311. package/dist/chat-ui/messages/wake-marker.cjs +39 -4
  312. package/dist/chat-ui/messages/wake-marker.cjs.map +1 -1
  313. package/dist/chat-ui/messages/wake-marker.d.ts +16 -5
  314. package/dist/chat-ui/messages/wake-marker.js +40 -5
  315. package/dist/chat-ui/messages/wake-marker.js.map +1 -1
  316. package/dist/chat-ui/rich-text/chat-message-body.cjs +12 -8
  317. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  318. package/dist/chat-ui/rich-text/chat-message-body.d.ts +6 -1
  319. package/dist/chat-ui/rich-text/chat-message-body.js +13 -9
  320. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  321. package/dist/chat-ui/rich-text/chat-text-schema.cjs +64 -6
  322. package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -1
  323. package/dist/chat-ui/rich-text/chat-text-schema.d.ts +5 -0
  324. package/dist/chat-ui/rich-text/chat-text-schema.js +66 -8
  325. package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -1
  326. package/dist/chat-ui/statewire.cjs +1 -1
  327. package/dist/chat-ui/statewire.d.ts +1 -1
  328. package/dist/chat-ui/statewire.js +1 -1
  329. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -2
  330. package/dist/chat-ui/thread/statewire-history-edge.js +2 -2
  331. package/dist/collab/client.cjs +218 -13
  332. package/dist/collab/client.cjs.map +1 -1
  333. package/dist/collab/client.d.ts +105 -2
  334. package/dist/collab/client.js +221 -17
  335. package/dist/collab/client.js.map +1 -1
  336. package/dist/collab/index.cjs +2 -0
  337. package/dist/collab/index.cjs.map +1 -1
  338. package/dist/collab/index.d.ts +1 -1
  339. package/dist/collab/index.js +3 -1
  340. package/dist/composer/AttachmentContext.cjs +38 -12
  341. package/dist/composer/AttachmentContext.cjs.map +1 -1
  342. package/dist/composer/AttachmentContext.d.ts +22 -1
  343. package/dist/composer/AttachmentContext.js +40 -14
  344. package/dist/composer/AttachmentContext.js.map +1 -1
  345. package/dist/composer/QuoteContext.cjs +80 -28
  346. package/dist/composer/QuoteContext.cjs.map +1 -1
  347. package/dist/composer/QuoteContext.d.ts +35 -6
  348. package/dist/composer/QuoteContext.js +82 -30
  349. package/dist/composer/QuoteContext.js.map +1 -1
  350. package/dist/composer/build-composed-message.cjs +27 -0
  351. package/dist/composer/build-composed-message.cjs.map +1 -1
  352. package/dist/composer/build-composed-message.d.ts +21 -0
  353. package/dist/composer/build-composed-message.js +28 -1
  354. package/dist/composer/build-composed-message.js.map +1 -1
  355. package/dist/composer/composer-handoff.cjs +13 -1
  356. package/dist/composer/composer-handoff.cjs.map +1 -1
  357. package/dist/composer/composer-handoff.d.ts +12 -0
  358. package/dist/composer/composer-handoff.js +15 -3
  359. package/dist/composer/composer-handoff.js.map +1 -1
  360. package/dist/composer/index.d.ts +6 -3
  361. package/dist/composer/useReadComposerDraft.cjs +16 -0
  362. package/dist/composer/useReadComposerDraft.cjs.map +1 -0
  363. package/dist/composer/useReadComposerDraft.d.ts +6 -0
  364. package/dist/composer/useReadComposerDraft.js +16 -0
  365. package/dist/composer/useReadComposerDraft.js.map +1 -0
  366. package/dist/diagnostics/errors.cjs +6 -0
  367. package/dist/diagnostics/errors.cjs.map +1 -1
  368. package/dist/diagnostics/errors.d.ts +6 -0
  369. package/dist/diagnostics/errors.js +6 -0
  370. package/dist/diagnostics/errors.js.map +1 -1
  371. package/dist/index.cjs +159 -7
  372. package/dist/index.cjs.map +1 -1
  373. package/dist/index.d.ts +53 -12
  374. package/dist/index.js +166 -14
  375. package/dist/index.js.map +1 -1
  376. package/dist/lib/owner-document.cjs +32 -0
  377. package/dist/lib/owner-document.cjs.map +1 -0
  378. package/dist/lib/owner-document.d.ts +17 -0
  379. package/dist/lib/owner-document.js +32 -0
  380. package/dist/lib/owner-document.js.map +1 -0
  381. package/dist/lib/portal-surface.cjs +55 -0
  382. package/dist/lib/portal-surface.cjs.map +1 -0
  383. package/dist/lib/portal-surface.d.ts +37 -0
  384. package/dist/lib/portal-surface.js +55 -0
  385. package/dist/lib/portal-surface.js.map +1 -0
  386. package/dist/lib/use-athena-rest-query.cjs +34 -0
  387. package/dist/lib/use-athena-rest-query.cjs.map +1 -0
  388. package/dist/lib/use-athena-rest-query.d.ts +31 -0
  389. package/dist/lib/use-athena-rest-query.js +34 -0
  390. package/dist/lib/use-athena-rest-query.js.map +1 -0
  391. package/dist/provider/AthenaProvider.cjs +36 -6
  392. package/dist/provider/AthenaProvider.cjs.map +1 -1
  393. package/dist/provider/AthenaProvider.d.ts +20 -1
  394. package/dist/provider/AthenaProvider.js +38 -8
  395. package/dist/provider/AthenaProvider.js.map +1 -1
  396. package/dist/provider/citation-links.d.ts +10 -2
  397. package/dist/{packages/react → react}/package.json.cjs +1 -1
  398. package/dist/{packages/react → react}/package.json.js +1 -1
  399. package/dist/runtime/statewire-lifecycle.cjs +15 -3
  400. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  401. package/dist/runtime/statewire-lifecycle.d.ts +6 -0
  402. package/dist/runtime/statewire-lifecycle.js +15 -3
  403. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  404. package/dist/runtime/useAthenaStatewireRuntime.cjs +11 -7
  405. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  406. package/dist/runtime/useAthenaStatewireRuntime.js +11 -7
  407. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  408. package/dist/search/useAthenaRepoSearch.cjs +5 -13
  409. package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
  410. package/dist/search/useAthenaRepoSearch.d.ts +3 -3
  411. package/dist/search/useAthenaRepoSearch.js +6 -14
  412. package/dist/search/useAthenaRepoSearch.js.map +1 -1
  413. package/dist/sheet-range/a1.cjs +162 -0
  414. package/dist/sheet-range/a1.cjs.map +1 -0
  415. package/dist/sheet-range/a1.d.ts +91 -0
  416. package/dist/sheet-range/a1.js +162 -0
  417. package/dist/sheet-range/a1.js.map +1 -0
  418. package/dist/sheet-range/index.cjs +22 -0
  419. package/dist/sheet-range/index.cjs.map +1 -0
  420. package/dist/sheet-range/index.d.ts +12 -0
  421. package/dist/sheet-range/index.js +22 -0
  422. package/dist/sheet-range/index.js.map +1 -0
  423. package/dist/sheet-range/range-preview-table.cjs +75 -0
  424. package/dist/sheet-range/range-preview-table.cjs.map +1 -0
  425. package/dist/sheet-range/range-preview-table.d.ts +20 -0
  426. package/dist/sheet-range/range-preview-table.js +75 -0
  427. package/dist/sheet-range/range-preview-table.js.map +1 -0
  428. package/dist/sheet-range/range-preview.cjs +116 -0
  429. package/dist/sheet-range/range-preview.cjs.map +1 -0
  430. package/dist/sheet-range/range-preview.d.ts +67 -0
  431. package/dist/sheet-range/range-preview.js +116 -0
  432. package/dist/sheet-range/range-preview.js.map +1 -0
  433. package/dist/styles.css +1 -1
  434. package/dist/threads/Inbox.cjs +289 -0
  435. package/dist/threads/Inbox.cjs.map +1 -0
  436. package/dist/threads/Inbox.d.ts +46 -0
  437. package/dist/threads/Inbox.js +289 -0
  438. package/dist/threads/Inbox.js.map +1 -0
  439. package/dist/threads/SessionList.cjs +51 -126
  440. package/dist/threads/SessionList.cjs.map +1 -1
  441. package/dist/threads/SessionList.d.ts +26 -1
  442. package/dist/threads/SessionList.js +45 -120
  443. package/dist/threads/SessionList.js.map +1 -1
  444. package/dist/threads/SessionsPage.cjs +753 -0
  445. package/dist/threads/SessionsPage.cjs.map +1 -0
  446. package/dist/threads/SessionsPage.d.ts +35 -0
  447. package/dist/threads/SessionsPage.js +753 -0
  448. package/dist/threads/SessionsPage.js.map +1 -0
  449. package/dist/threads/api.cjs +21 -1
  450. package/dist/threads/api.cjs.map +1 -1
  451. package/dist/threads/api.d.ts +59 -0
  452. package/dist/threads/api.js +21 -1
  453. package/dist/threads/api.js.map +1 -1
  454. package/dist/threads/conversation-list-fetcher.cjs +9 -2
  455. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  456. package/dist/threads/conversation-list-fetcher.d.ts +14 -1
  457. package/dist/threads/conversation-list-fetcher.js +9 -2
  458. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  459. package/dist/threads/index.d.ts +2 -1
  460. package/dist/threads/pull-request-display.cjs +25 -0
  461. package/dist/threads/pull-request-display.cjs.map +1 -0
  462. package/dist/threads/pull-request-display.d.ts +15 -0
  463. package/dist/threads/pull-request-display.js +25 -0
  464. package/dist/threads/pull-request-display.js.map +1 -0
  465. package/dist/threads/session-list-model.cjs +16 -2
  466. package/dist/threads/session-list-model.cjs.map +1 -1
  467. package/dist/threads/session-list-model.d.ts +10 -0
  468. package/dist/threads/session-list-model.js +16 -2
  469. package/dist/threads/session-list-model.js.map +1 -1
  470. package/dist/threads/session-row-parts.cjs +152 -0
  471. package/dist/threads/session-row-parts.cjs.map +1 -0
  472. package/dist/threads/session-row-parts.d.ts +50 -0
  473. package/dist/threads/session-row-parts.js +152 -0
  474. package/dist/threads/session-row-parts.js.map +1 -0
  475. package/dist/threads/sessions-page-model.cjs +305 -0
  476. package/dist/threads/sessions-page-model.cjs.map +1 -0
  477. package/dist/threads/sessions-page-model.d.ts +86 -0
  478. package/dist/threads/sessions-page-model.js +305 -0
  479. package/dist/threads/sessions-page-model.js.map +1 -0
  480. package/dist/threads/sessions-page-storage.cjs +93 -0
  481. package/dist/threads/sessions-page-storage.cjs.map +1 -0
  482. package/dist/threads/sessions-page-storage.d.ts +23 -0
  483. package/dist/threads/sessions-page-storage.js +93 -0
  484. package/dist/threads/sessions-page-storage.js.map +1 -0
  485. package/dist/threads/sessions-query.cjs +235 -0
  486. package/dist/threads/sessions-query.cjs.map +1 -0
  487. package/dist/threads/sessions-query.d.ts +119 -0
  488. package/dist/threads/sessions-query.js +235 -0
  489. package/dist/threads/sessions-query.js.map +1 -0
  490. package/dist/threads/statewire-thread-list.cjs +133 -40
  491. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  492. package/dist/threads/statewire-thread-list.d.ts +83 -2
  493. package/dist/threads/statewire-thread-list.js +134 -41
  494. package/dist/threads/statewire-thread-list.js.map +1 -1
  495. package/dist/threads/use-athena-sessions.cjs +198 -0
  496. package/dist/threads/use-athena-sessions.cjs.map +1 -0
  497. package/dist/threads/use-athena-sessions.d.ts +81 -0
  498. package/dist/threads/use-athena-sessions.js +198 -0
  499. package/dist/threads/use-athena-sessions.js.map +1 -0
  500. package/dist/threads/use-rest-conversation-history.cjs +1 -1
  501. package/dist/threads/use-rest-conversation-history.js +1 -1
  502. package/dist/threads/use-statewire-conversation-history.cjs +1 -1
  503. package/dist/threads/use-statewire-conversation-history.d.ts +3 -0
  504. package/dist/threads/use-statewire-conversation-history.js +1 -1
  505. package/dist/threads/use-thread-read-state.cjs +73 -0
  506. package/dist/threads/use-thread-read-state.cjs.map +1 -0
  507. package/dist/threads/use-thread-read-state.d.ts +51 -0
  508. package/dist/threads/use-thread-read-state.js +73 -0
  509. package/dist/threads/use-thread-read-state.js.map +1 -0
  510. package/dist/tiptap-markdown-table/src/markdown-table-serializer.cjs.map +1 -0
  511. package/dist/tiptap-markdown-table/src/markdown-table-serializer.js.map +1 -0
  512. package/dist/tools/auto-open-tools.cjs +2 -0
  513. package/dist/tools/auto-open-tools.cjs.map +1 -1
  514. package/dist/tools/auto-open-tools.js +2 -0
  515. package/dist/tools/auto-open-tools.js.map +1 -1
  516. package/dist/tools/file-tool-uis.cjs +18 -43
  517. package/dist/tools/file-tool-uis.cjs.map +1 -1
  518. package/dist/tools/file-tool-uis.d.ts +1 -2
  519. package/dist/tools/file-tool-uis.js +18 -42
  520. package/dist/tools/file-tool-uis.js.map +1 -1
  521. package/dist/tools/index.cjs +6 -2
  522. package/dist/tools/index.cjs.map +1 -1
  523. package/dist/tools/index.d.ts +1 -0
  524. package/dist/tools/index.js +6 -2
  525. package/dist/tools/index.js.map +1 -1
  526. package/dist/tools/misc-tool-uis.cjs +2 -2
  527. package/dist/tools/misc-tool-uis.js +2 -2
  528. package/dist/tools/primitives.cjs +1 -1
  529. package/dist/tools/primitives.js +1 -1
  530. package/dist/tools/read-asset-tool-ui.cjs +21 -70
  531. package/dist/tools/read-asset-tool-ui.cjs.map +1 -1
  532. package/dist/tools/read-asset-tool-ui.js +21 -70
  533. package/dist/tools/read-asset-tool-ui.js.map +1 -1
  534. package/dist/tools/step-text.cjs +21 -0
  535. package/dist/tools/step-text.cjs.map +1 -0
  536. package/dist/tools/step-text.d.ts +10 -0
  537. package/dist/tools/step-text.js +21 -0
  538. package/dist/tools/step-text.js.map +1 -0
  539. package/dist/tools/tool-fallback.cjs +24 -5
  540. package/dist/tools/tool-fallback.cjs.map +1 -1
  541. package/dist/tools/tool-fallback.d.ts +3 -1
  542. package/dist/tools/tool-fallback.js +24 -5
  543. package/dist/tools/tool-fallback.js.map +1 -1
  544. package/dist/tools/tool-summary.cjs +661 -0
  545. package/dist/tools/tool-summary.cjs.map +1 -0
  546. package/dist/tools/tool-summary.d.ts +99 -0
  547. package/dist/tools/tool-summary.js +661 -0
  548. package/dist/tools/tool-summary.js.map +1 -0
  549. package/dist/tools/tool-uis.cjs +39 -97
  550. package/dist/tools/tool-uis.cjs.map +1 -1
  551. package/dist/tools/tool-uis.js +39 -97
  552. package/dist/tools/tool-uis.js.map +1 -1
  553. package/dist/tools/voice-update-tool-ui.cjs +1 -1
  554. package/dist/tools/voice-update-tool-ui.js +1 -1
  555. package/dist/tools/write-todos-tool-ui.cjs +3 -2
  556. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  557. package/dist/tools/write-todos-tool-ui.d.ts +5 -0
  558. package/dist/tools/write-todos-tool-ui.js +5 -4
  559. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  560. package/dist/ui/popover.cjs +6 -6
  561. package/dist/ui/popover.cjs.map +1 -1
  562. package/dist/ui/popover.js +6 -6
  563. package/dist/ui/popover.js.map +1 -1
  564. package/dist/ui/tooltip.cjs +16 -2
  565. package/dist/ui/tooltip.cjs.map +1 -1
  566. package/dist/ui/tooltip.d.ts +1 -1
  567. package/dist/ui/tooltip.js +16 -2
  568. package/dist/ui/tooltip.js.map +1 -1
  569. package/dist/version.cjs +1 -1
  570. package/dist/version.js +1 -1
  571. package/dist/voice/use-athena-voice-session.cjs.map +1 -1
  572. package/dist/voice/use-athena-voice-session.d.ts +3 -3
  573. package/dist/voice/use-athena-voice-session.js.map +1 -1
  574. package/dist/workbench/SessionWorkbench.cjs +378 -0
  575. package/dist/workbench/SessionWorkbench.cjs.map +1 -0
  576. package/dist/workbench/SessionWorkbench.d.ts +30 -0
  577. package/dist/workbench/SessionWorkbench.js +378 -0
  578. package/dist/workbench/SessionWorkbench.js.map +1 -0
  579. package/dist/workbench/session-workbench-context.cjs +26 -0
  580. package/dist/workbench/session-workbench-context.cjs.map +1 -0
  581. package/dist/workbench/session-workbench-context.d.ts +55 -0
  582. package/dist/workbench/session-workbench-context.js +26 -0
  583. package/dist/workbench/session-workbench-context.js.map +1 -0
  584. package/dist/workbench/session-workbench-model.cjs +288 -0
  585. package/dist/workbench/session-workbench-model.cjs.map +1 -0
  586. package/dist/workbench/session-workbench-model.d.ts +168 -0
  587. package/dist/workbench/session-workbench-model.js +288 -0
  588. package/dist/workbench/session-workbench-model.js.map +1 -0
  589. package/dist/workbench/session-workbench-panes.cjs +580 -0
  590. package/dist/workbench/session-workbench-panes.cjs.map +1 -0
  591. package/dist/workbench/session-workbench-panes.d.ts +16 -0
  592. package/dist/workbench/session-workbench-panes.js +580 -0
  593. package/dist/workbench/session-workbench-panes.js.map +1 -0
  594. package/dist/workbench/session-workspace-api.cjs +157 -0
  595. package/dist/workbench/session-workspace-api.cjs.map +1 -0
  596. package/dist/workbench/session-workspace-api.d.ts +70 -0
  597. package/dist/workbench/session-workspace-api.js +157 -0
  598. package/dist/workbench/session-workspace-api.js.map +1 -0
  599. package/dist/workbench/use-session-workspace.cjs +100 -0
  600. package/dist/workbench/use-session-workspace.cjs.map +1 -0
  601. package/dist/workbench/use-session-workspace.d.ts +45 -0
  602. package/dist/workbench/use-session-workspace.js +100 -0
  603. package/dist/workbench/use-session-workspace.js.map +1 -0
  604. package/dist/workbench/workbench-layout-context.cjs +10 -0
  605. package/dist/workbench/workbench-layout-context.cjs.map +1 -0
  606. package/dist/workbench/workbench-layout-context.d.ts +17 -0
  607. package/dist/workbench/workbench-layout-context.js +10 -0
  608. package/dist/workbench/workbench-layout-context.js.map +1 -0
  609. package/package.json +20 -9
  610. package/dist/node_modules/@assistant-ui/react-langchain/dist/converter.cjs +0 -77
  611. package/dist/node_modules/@assistant-ui/react-langchain/dist/converter.cjs.map +0 -1
  612. package/dist/node_modules/@assistant-ui/react-langchain/dist/converter.js +0 -77
  613. package/dist/node_modules/@assistant-ui/react-langchain/dist/converter.js.map +0 -1
  614. package/dist/node_modules/@assistant-ui/react-langgraph/dist/convertLangChainMessages.cjs +0 -181
  615. package/dist/node_modules/@assistant-ui/react-langgraph/dist/convertLangChainMessages.cjs.map +0 -1
  616. package/dist/node_modules/@assistant-ui/react-langgraph/dist/convertLangChainMessages.js +0 -181
  617. package/dist/node_modules/@assistant-ui/react-langgraph/dist/convertLangChainMessages.js.map +0 -1
  618. package/dist/packages/agent-runtime/src/asset-attachment.cjs.map +0 -1
  619. package/dist/packages/agent-runtime/src/asset-attachment.js.map +0 -1
  620. package/dist/packages/agent-runtime/src/auth-denial.cjs.map +0 -1
  621. package/dist/packages/agent-runtime/src/auth-denial.js.map +0 -1
  622. package/dist/packages/agent-runtime/src/client-tool-bridge.cjs.map +0 -1
  623. package/dist/packages/agent-runtime/src/client-tool-bridge.js.map +0 -1
  624. package/dist/packages/agent-runtime/src/client-tools.cjs.map +0 -1
  625. package/dist/packages/agent-runtime/src/client-tools.js.map +0 -1
  626. package/dist/packages/agent-runtime/src/conversation-index.cjs.map +0 -1
  627. package/dist/packages/agent-runtime/src/conversation-index.js.map +0 -1
  628. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +0 -1
  629. package/dist/packages/agent-runtime/src/conversation-list.js.map +0 -1
  630. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.cjs.map +0 -1
  631. package/dist/packages/agent-runtime/src/deep-agent-transport-recipe.js.map +0 -1
  632. package/dist/packages/agent-runtime/src/edit-command.cjs.map +0 -1
  633. package/dist/packages/agent-runtime/src/edit-command.js.map +0 -1
  634. package/dist/packages/agent-runtime/src/errors.cjs.map +0 -1
  635. package/dist/packages/agent-runtime/src/errors.js.map +0 -1
  636. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +0 -1
  637. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +0 -1
  638. package/dist/packages/agent-runtime/src/github-wake-event.cjs.map +0 -1
  639. package/dist/packages/agent-runtime/src/github-wake-event.js.map +0 -1
  640. package/dist/packages/agent-runtime/src/interrupts.cjs.map +0 -1
  641. package/dist/packages/agent-runtime/src/interrupts.js.map +0 -1
  642. package/dist/packages/agent-runtime/src/message-author.cjs.map +0 -1
  643. package/dist/packages/agent-runtime/src/message-author.js.map +0 -1
  644. package/dist/packages/agent-runtime/src/message-window.cjs.map +0 -1
  645. package/dist/packages/agent-runtime/src/message-window.js.map +0 -1
  646. package/dist/packages/agent-runtime/src/messages.cjs.map +0 -1
  647. package/dist/packages/agent-runtime/src/messages.js.map +0 -1
  648. package/dist/packages/agent-runtime/src/model-context.cjs.map +0 -1
  649. package/dist/packages/agent-runtime/src/model-context.js.map +0 -1
  650. package/dist/packages/agent-runtime/src/optimistic-content.cjs.map +0 -1
  651. package/dist/packages/agent-runtime/src/optimistic-content.js.map +0 -1
  652. package/dist/packages/agent-runtime/src/protocol.cjs.map +0 -1
  653. package/dist/packages/agent-runtime/src/protocol.js.map +0 -1
  654. package/dist/packages/agent-runtime/src/queue.cjs.map +0 -1
  655. package/dist/packages/agent-runtime/src/queue.js.map +0 -1
  656. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +0 -1
  657. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +0 -1
  658. package/dist/packages/agent-runtime/src/runtime-messages.cjs.map +0 -1
  659. package/dist/packages/agent-runtime/src/runtime-messages.js.map +0 -1
  660. package/dist/packages/agent-runtime/src/screen-point.cjs.map +0 -1
  661. package/dist/packages/agent-runtime/src/screen-point.js.map +0 -1
  662. package/dist/packages/agent-runtime/src/send-recovery.cjs.map +0 -1
  663. package/dist/packages/agent-runtime/src/send-recovery.js.map +0 -1
  664. package/dist/packages/agent-runtime/src/statewire-attach-observer.cjs.map +0 -1
  665. package/dist/packages/agent-runtime/src/statewire-attach-observer.js.map +0 -1
  666. package/dist/packages/agent-runtime/src/statewire-state.cjs.map +0 -1
  667. package/dist/packages/agent-runtime/src/statewire-state.js.map +0 -1
  668. package/dist/packages/agent-runtime/src/statewire-suspended.cjs.map +0 -1
  669. package/dist/packages/agent-runtime/src/statewire-suspended.js.map +0 -1
  670. package/dist/packages/agent-runtime/src/statewire-transport.cjs +0 -11
  671. package/dist/packages/agent-runtime/src/statewire-transport.cjs.map +0 -1
  672. package/dist/packages/agent-runtime/src/statewire-transport.js +0 -11
  673. package/dist/packages/agent-runtime/src/statewire-transport.js.map +0 -1
  674. package/dist/packages/agent-runtime/src/streaming-markdown.cjs.map +0 -1
  675. package/dist/packages/agent-runtime/src/streaming-markdown.js.map +0 -1
  676. package/dist/packages/agent-runtime/src/thread-extras.cjs.map +0 -1
  677. package/dist/packages/agent-runtime/src/thread-extras.js.map +0 -1
  678. package/dist/packages/agent-runtime/src/thread-snapshot-cache.cjs.map +0 -1
  679. package/dist/packages/agent-runtime/src/thread-snapshot-cache.js.map +0 -1
  680. package/dist/packages/agent-runtime/src/thread-status.cjs.map +0 -1
  681. package/dist/packages/agent-runtime/src/thread-status.js.map +0 -1
  682. package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +0 -1
  683. package/dist/packages/agent-runtime/src/tool-metadata.js.map +0 -1
  684. package/dist/packages/agent-runtime/src/tool-outputs.cjs.map +0 -1
  685. package/dist/packages/agent-runtime/src/tool-outputs.js.map +0 -1
  686. package/dist/packages/agent-runtime/src/tool-result.cjs.map +0 -1
  687. package/dist/packages/agent-runtime/src/tool-result.js.map +0 -1
  688. package/dist/packages/agent-runtime/src/use-conversation-list.cjs.map +0 -1
  689. package/dist/packages/agent-runtime/src/use-conversation-list.js.map +0 -1
  690. package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs.map +0 -1
  691. package/dist/packages/agent-runtime/src/use-deep-agent-config.js.map +0 -1
  692. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +0 -1
  693. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +0 -1
  694. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.cjs.map +0 -1
  695. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.js.map +0 -1
  696. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +0 -1
  697. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +0 -1
  698. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.cjs.map +0 -1
  699. package/dist/packages/agent-runtime/src/use-deep-agent-window-sync.js.map +0 -1
  700. package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +0 -1
  701. package/dist/packages/agent-runtime/src/voice-presentation.js.map +0 -1
  702. package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs.map +0 -1
  703. package/dist/packages/agent-runtime/src/voice-turn-grouping.js.map +0 -1
  704. package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.cjs.map +0 -1
  705. package/dist/packages/tiptap-markdown-table/src/markdown-table-serializer.js.map +0 -1
  706. /package/dist/{packages/agent-runtime → agent-runtime}/src/asset-attachment.cjs +0 -0
  707. /package/dist/{packages/agent-runtime → agent-runtime}/src/asset-attachment.js +0 -0
  708. /package/dist/{packages/agent-runtime → agent-runtime}/src/auth-denial.cjs +0 -0
  709. /package/dist/{packages/agent-runtime → agent-runtime}/src/auth-denial.js +0 -0
  710. /package/dist/{packages/agent-runtime → agent-runtime}/src/client-tool-bridge.cjs +0 -0
  711. /package/dist/{packages/agent-runtime → agent-runtime}/src/client-tool-bridge.js +0 -0
  712. /package/dist/{packages/agent-runtime → agent-runtime}/src/client-tools.cjs +0 -0
  713. /package/dist/{packages/agent-runtime → agent-runtime}/src/client-tools.js +0 -0
  714. /package/dist/{packages/agent-runtime → agent-runtime}/src/conversation-index.cjs +0 -0
  715. /package/dist/{packages/agent-runtime → agent-runtime}/src/conversation-index.js +0 -0
  716. /package/dist/{packages/agent-runtime → agent-runtime}/src/edit-command.cjs +0 -0
  717. /package/dist/{packages/agent-runtime → agent-runtime}/src/edit-command.js +0 -0
  718. /package/dist/{packages/agent-runtime → agent-runtime}/src/github-wake-event.cjs +0 -0
  719. /package/dist/{packages/agent-runtime → agent-runtime}/src/github-wake-event.js +0 -0
  720. /package/dist/{packages/agent-runtime → agent-runtime}/src/message-author.cjs +0 -0
  721. /package/dist/{packages/agent-runtime → agent-runtime}/src/message-author.js +0 -0
  722. /package/dist/{packages/agent-runtime → agent-runtime}/src/message-window.cjs +0 -0
  723. /package/dist/{packages/agent-runtime → agent-runtime}/src/message-window.js +0 -0
  724. /package/dist/{packages/agent-runtime → agent-runtime}/src/model-context.cjs +0 -0
  725. /package/dist/{packages/agent-runtime → agent-runtime}/src/model-context.js +0 -0
  726. /package/dist/{packages/agent-runtime → agent-runtime}/src/optimistic-content.cjs +0 -0
  727. /package/dist/{packages/agent-runtime → agent-runtime}/src/optimistic-content.js +0 -0
  728. /package/dist/{packages/agent-runtime → agent-runtime}/src/protocol.cjs +0 -0
  729. /package/dist/{packages/agent-runtime → agent-runtime}/src/protocol.js +0 -0
  730. /package/dist/{packages/agent-runtime → agent-runtime}/src/runtime-messages.cjs +0 -0
  731. /package/dist/{packages/agent-runtime → agent-runtime}/src/runtime-messages.js +0 -0
  732. /package/dist/{packages/agent-runtime → agent-runtime}/src/screen-point.cjs +0 -0
  733. /package/dist/{packages/agent-runtime → agent-runtime}/src/screen-point.js +0 -0
  734. /package/dist/{packages/agent-runtime → agent-runtime}/src/send-recovery.cjs +0 -0
  735. /package/dist/{packages/agent-runtime → agent-runtime}/src/send-recovery.js +0 -0
  736. /package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-attach-observer.cjs +0 -0
  737. /package/dist/{packages/agent-runtime → agent-runtime}/src/statewire-attach-observer.js +0 -0
  738. /package/dist/{packages/agent-runtime → agent-runtime}/src/streaming-markdown.cjs +0 -0
  739. /package/dist/{packages/agent-runtime → agent-runtime}/src/streaming-markdown.js +0 -0
  740. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-extras.cjs +0 -0
  741. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-extras.js +0 -0
  742. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-snapshot-cache.cjs +0 -0
  743. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-snapshot-cache.js +0 -0
  744. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-status.cjs +0 -0
  745. /package/dist/{packages/agent-runtime → agent-runtime}/src/thread-status.js +0 -0
  746. /package/dist/{packages/agent-runtime → agent-runtime}/src/tool-outputs.cjs +0 -0
  747. /package/dist/{packages/agent-runtime → agent-runtime}/src/tool-outputs.js +0 -0
  748. /package/dist/{packages/agent-runtime → agent-runtime}/src/tool-result.cjs +0 -0
  749. /package/dist/{packages/agent-runtime → agent-runtime}/src/tool-result.js +0 -0
  750. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-config.cjs +0 -0
  751. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-config.js +0 -0
  752. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-runtime.cjs +0 -0
  753. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-runtime.js +0 -0
  754. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-window-sync.cjs +0 -0
  755. /package/dist/{packages/agent-runtime → agent-runtime}/src/use-deep-agent-window-sync.js +0 -0
  756. /package/dist/{packages/agent-runtime → agent-runtime}/src/voice-turn-grouping.cjs +0 -0
  757. /package/dist/{packages/agent-runtime → agent-runtime}/src/voice-turn-grouping.js +0 -0
  758. /package/dist/{packages/react → react}/package.json.cjs.map +0 -0
  759. /package/dist/{packages/react → react}/package.json.js.map +0 -0
  760. /package/dist/{packages/tiptap-markdown-table → tiptap-markdown-table}/src/markdown-table-serializer.cjs +0 -0
  761. /package/dist/{packages/tiptap-markdown-table → tiptap-markdown-table}/src/markdown-table-serializer.js +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-tool-bridge.js","sources":["../../../../../agent-runtime/src/client-tool-bridge.ts"],"sourcesContent":["/**\n * The statewire client-tool bridge loop, shared by every surface that executes\n * frontend tool calls (Olympus, the React SDK, the Office add-in, mobile).\n *\n * Tools declared through `runConfig.custom.client_tools` have no backend\n * implementation: a call parks the run on an interrupt that arrives as a\n * statewire input request. A bridge with handlers for the whole batch claims\n * it (exactly-once per ownership domain), runs those handlers, and resumes\n * with per-call result envelopes. Other attached tabs leave it for the\n * executing surface; a thrown handler still returns an actionable error.\n *\n * Wire-adjacent correctness invariants this module owns, so they can never\n * drift apart per surface again:\n * - exactly-once claim per request, with un-claim on every path that did NOT\n * run any handler (abandoned before execution, consent dismissed, unmount\n * before the first handler) so a re-emitted request is executable instead\n * of stranding the run;\n * - once a batch has STARTED executing handlers, its claim is never released\n * by unmount: handlers cannot be cancelled, so releasing would let another\n * mount re-claim the re-emitted request and re-execute side effects that\n * already ran (double-executing a mutating tool). The completed wire-safe\n * result is cached and may be replayed across reconnects/remounts instead;\n * - liveness checks between awaits (the run can be stopped or answered\n * elsewhere while a handler runs) so a stale request is never resumed;\n * - per-call JSON wire-safety, so one unrepresentable handler result answers\n * that one call with an error instead of failing the whole batch.\n *\n * Like `interrupts.ts`, this is parameterized rather than self-wiring: the\n * host component reads `useStatewireRuns().inputRequests` and\n * `useStatewireSendCommand()` itself (both mockable per surface) and passes\n * them in, together with its ownership tracker and write-consent policy.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef } from 'react';\nimport {\n type ClientToolCallRequest,\n type ClientToolRequestTracker,\n type ClientToolResultEnvelope,\n clientToolCallArgs,\n clientToolErrorEnvelope,\n clientToolResultsResumeValue,\n clientToolSuccessEnvelope,\n isClientToolInterrupt,\n} from './client-tools';\n\n/** A locally executable tool, normalized across the per-surface tool shapes. */\nexport interface ClientToolBridgeTool {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n /** Mutating tools gate on the surface's write-consent flow (`confirmWrites`). */\n requiresWrite?: boolean;\n run: (\n args: Record<string, unknown>,\n context: { toolCallId: string },\n ) => Promise<unknown>;\n}\n\n/**\n * Batch write-consent outcome. `proceed` runs the write calls, `declined`\n * answers them with the declined message while reads still run, `abandoned`\n * releases the claim without resuming (the request died, or the consent UI was\n * dismissed by the run moving on). Grant persistence (a thread set, a stored\n * preference) is the surface's own policy, applied inside its callback.\n */\nexport type ClientToolWriteConsentDecision = 'proceed' | 'declined' | 'abandoned';\n\nexport interface ClientToolCallSettledEvent {\n toolName: string;\n status: 'success' | 'error' | 'declined';\n errorType?: string;\n}\n\n/** Default resume error for a write call the user declined. */\nexport const CLIENT_TOOL_WRITE_DECLINED_MESSAGE =\n 'The user declined this edit. Do not retry it; ask the user how to proceed.';\n\n/** Default resume error for a call naming a tool this surface does not have. */\nexport function defaultMissingClientToolMessage(toolName: string): string {\n return `This surface has no tool named '${toolName}'.`;\n}\n\n/** Statewire request ids are unique per run, but a thread scope keeps a\n * process-wide claim unambiguous when two conversations are open at once. */\nexport function clientToolClaimKey(threadId: string, requestId: string): string {\n return `${threadId}:${requestId}`;\n}\n\nconst CLIENT_TOOL_RESUME_RETRY_DELAYS_MS = [2_000, 5_000, 10_000, 15_000, 30_000];\nconst MAX_CLIENT_TOOL_RESUME_ATTEMPTS = 25;\n\nfunction clientToolResumeRetryDelayMs(attemptNumber: number): number {\n const index = Math.min(\n Math.max(attemptNumber - 1, 0),\n CLIENT_TOOL_RESUME_RETRY_DELAYS_MS.length - 1,\n );\n return CLIENT_TOOL_RESUME_RETRY_DELAYS_MS[index] ?? 30_000;\n}\n\n/**\n * A wire-safe copy of a handler's return value: handlers can return values\n * JSON cannot represent (DOM nodes, host objects, bigints), which must never\n * reach the transport. Throws for an unrepresentable value so the caller can\n * answer that one call with an error instead of stranding the whole batch.\n */\nexport function jsonSafeClientToolResult(value: unknown): unknown {\n const serialized = JSON.stringify(value);\n return serialized === undefined ? null : JSON.parse(serialized);\n}\n\n/** A parked, unanswered client-tool input request. */\nexport function isPendingClientToolRequest(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (request.type !== 'interrupt' || 'response' in request) return false;\n return isClientToolInterrupt((request as { payload?: unknown }).payload);\n}\n\nexport interface ExecuteClientToolBatchOptions {\n calls: readonly ClientToolCallRequest[];\n tools: readonly ClientToolBridgeTool[];\n /** Whether the parked request still exists and this surface may answer it.\n * Checked before consent resolution, before every call, and before the\n * results are handed back for resume. */\n isLive: () => boolean;\n /** Batch write gate; absent means writes run without consent (surfaces that\n * only register read tools). Called once per batch, before any handler. */\n confirmWrites?: (batch: {\n writeToolNames: readonly string[];\n }) => Promise<ClientToolWriteConsentDecision>;\n writeDeclinedMessage?: string;\n missingToolMessage?: (toolName: string) => string;\n /** Per-call telemetry (the Office add-in's tool events). */\n onCallSettled?: (event: ClientToolCallSettledEvent) => void;\n /**\n * Called once, after the write-consent gate resolves and immediately before\n * the first handler runs — the point where side effects begin. There is no\n * await between this callback and the first handler invocation, so a caller\n * marking \"execution started\" here can never miss a handler that ran.\n */\n onExecutionStart?: () => void;\n}\n\n/**\n * Execute one batched client-tool park: consent, per-call execution with\n * wire-safe envelopes, liveness aborts. Returns the per-interrupt result map\n * to resume with, or `null` when the batch was abandoned (the request died,\n * consent was abandoned, or the surface went away) — the caller must release\n * its claim and must NOT resume.\n */\nexport async function executeClientToolBatch(\n options: ExecuteClientToolBatchOptions,\n): Promise<Record<string, ClientToolResultEnvelope> | null> {\n const {\n calls,\n tools,\n isLive,\n confirmWrites,\n writeDeclinedMessage = CLIENT_TOOL_WRITE_DECLINED_MESSAGE,\n missingToolMessage = defaultMissingClientToolMessage,\n onCallSettled,\n onExecutionStart,\n } = options;\n const registry = new Map(tools.map((tool) => [tool.name, tool]));\n\n // One consent covers the whole batch (and whatever scope the surface's\n // callback persists): writes are declined together while reads still run.\n let writesDeclined = false;\n const writeToolNames = calls\n .filter((call) => registry.get(call.tool_name)?.requiresWrite === true)\n .map((call) => call.tool_name);\n if (writeToolNames.length > 0 && confirmWrites) {\n const decision = await confirmWrites({ writeToolNames });\n if (decision === 'abandoned' || !isLive()) {\n // The run was stopped (or answered elsewhere) while consent was\n // pending: abort before any handler mutates the user's asset.\n return null;\n }\n writesDeclined = decision === 'declined';\n }\n\n onExecutionStart?.();\n\n const results: Record<string, ClientToolResultEnvelope> = {};\n for (const call of calls) {\n if (!isLive()) return null;\n const tool = registry.get(call.tool_name);\n let envelope: ClientToolResultEnvelope;\n let status: ClientToolCallSettledEvent['status'] = 'success';\n let errorType: string | undefined;\n if (!tool) {\n envelope = clientToolErrorEnvelope(missingToolMessage(call.tool_name));\n status = 'error';\n errorType = 'UnknownClientTool';\n } else if (tool.requiresWrite === true && writesDeclined) {\n envelope = clientToolErrorEnvelope(writeDeclinedMessage);\n status = 'declined';\n } else {\n try {\n envelope = clientToolSuccessEnvelope(\n jsonSafeClientToolResult(\n await tool.run(clientToolCallArgs(call), {\n toolCallId: call.interrupt_id,\n }),\n ),\n );\n } catch (error) {\n envelope = clientToolErrorEnvelope(error);\n status = 'error';\n errorType = error instanceof Error ? error.name : typeof error;\n }\n }\n results[call.interrupt_id] = envelope;\n onCallSettled?.({\n toolName: call.tool_name,\n status,\n ...(errorType !== undefined ? { errorType } : {}),\n });\n }\n\n // Abandoned mid-flight: resuming a dead request would error, and a\n // re-emitted one must be executable again.\n return isLive() ? results : null;\n}\n\nexport interface UseStatewireClientToolBridgeOptions {\n threadId: string;\n tools: readonly ClientToolBridgeTool[];\n /**\n * Ownership domain for parked-request claims. Pass a module-scoped tracker\n * to make ownership process-wide across every mount of the surface (two\n * mounted surfaces of one conversation execute each request exactly once),\n * or a per-mount tracker to scope ownership to one mounted bridge.\n */\n tracker: ClientToolRequestTracker;\n /** The surface's `useStatewireRuns().inputRequests` read. */\n inputRequests: readonly StatewireThread.InputRequestState[] | undefined;\n /** Dispatch the `run/input` resume on the surface's transport. The value is\n * already wire-safe (per-call JSON round-trip inside the batch engine). */\n /** Dispatch the answer and settle only once the Statewire host accepts or\n * rejects the command. The bridge deliberately keeps replaying while this\n * promise is pending: `run/input` removes the request optimistically before\n * the server has accepted the answer. */\n sendResume: (\n requestId: string,\n value: Record<string, unknown>,\n ) => PromiseLike<unknown>;\n /** Batch write gate, with the parked request's id so the surface can close\n * its consent UI the instant that request disappears. */\n confirmWrites?: (batch: {\n requestId: string;\n writeToolNames: readonly string[];\n }) => Promise<ClientToolWriteConsentDecision>;\n writeDeclinedMessage?: string;\n missingToolMessage?: (toolName: string) => string;\n onCallSettled?: (event: ClientToolCallSettledEvent) => void;\n /** Log prefix for resume-dispatch failures. */\n logLabel?: string;\n}\n\n/**\n * The claim lifecycle around {@link executeClientToolBatch}: claim the pending\n * request once per ownership domain, execute, resume — releasing the claim on\n * every path that ran no handler, so re-emitted requests recover.\n *\n * Unmount splits by whether side effects began:\n * - a claim whose batch has NOT started executing (claimed this tick, or\n * parked on write consent) is released, so another mount — or a remount —\n * can execute the re-emitted request instead of stranding the parked run;\n * - a claim whose batch HAS started stays held: handlers cannot be cancelled,\n * so releasing would let a sibling mount re-claim and re-execute a mutation\n * whose side effects already ran. The detached execution caches its result\n * and attempts one resume through its captured transport; a later mount can\n * replay the same result without repeating the side effect.\n */\nexport function useStatewireClientToolBridge(\n options: UseStatewireClientToolBridgeOptions,\n): void {\n const { threadId, tracker, inputRequests, logLabel = '[AthenaSDK]' } = options;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const toolNames = new Set(options.tools.map((tool) => tool.name));\n const pendingRequest = inputRequests?.find((request) => {\n if (!isPendingClientToolRequest(request)) return false;\n const payload = (request as StatewireThread.CustomInputRequest).payload;\n if (!isClientToolInterrupt(payload)) return false;\n // Every attached tab sees the same park. An observer without these tools\n // must not claim it and race the executing surface with an unknown-tool\n // error (for example, Olympus watching an Excel conversation).\n // A cached result already belongs to this tracker and can be replayed\n // without loading its handlers again.\n return (\n tracker.getCachedResume(clientToolClaimKey(threadId, request.id)) !== undefined ||\n payload.context.requests.every((call) => toolNames.has(call.tool_name))\n );\n });\n\n // Claims this bridge holds but has not resumed, split by whether their\n // batch has begun running handlers. The ref Set instances are stable (only\n // their contents mutate), so the unmount cleanup sees every claim.\n const ownedClaimsRef = useRef<Set<string>>(new Set());\n const startedClaimsRef = useRef<Set<string>>(new Set());\n const resumeOwnerRef = useRef(Symbol('client-tool-resume-owner'));\n const resumeClaimsRef = useRef<Set<string>>(new Set());\n const resumeTimersRef = useRef<\n Map<string, { requestId: string; timer: ReturnType<typeof setTimeout> }>\n >(new Map());\n const activeRef = useRef(true);\n const releaseClaim = useCallback(\n (claim: string) => {\n // Release the shared tracker entry only while THIS mount still owns it.\n // A detached batch can settle after the unmount cleanup already released\n // (and disowned) its claim; by then the tracker entry may belong to\n // another mount that claimed, executed, and resumed — releasing it here\n // would let a stale re-emission of the same request execute again.\n const owned = ownedClaimsRef.current.delete(claim);\n startedClaimsRef.current.delete(claim);\n if (owned) tracker.release(claim);\n },\n [tracker],\n );\n const stopResumeReplay = useCallback(\n (claim: string) => {\n const timerEntry = resumeTimersRef.current.get(claim);\n if (timerEntry) clearTimeout(timerEntry.timer);\n resumeTimersRef.current.delete(claim);\n if (resumeClaimsRef.current.delete(claim)) {\n tracker.releaseResumeReplay(claim, resumeOwnerRef.current);\n }\n },\n [tracker],\n );\n useEffect(() => {\n activeRef.current = true;\n const ownedClaims = ownedClaimsRef.current;\n const startedClaims = startedClaimsRef.current;\n return () => {\n activeRef.current = false;\n for (const claim of Array.from(resumeClaimsRef.current)) {\n stopResumeReplay(claim);\n }\n for (const claim of ownedClaims) {\n // Started batches keep their claim — their detached execution settles\n // it (resume, or release on a failed dispatch). Releasing here would\n // double-execute the side effects that already ran.\n if (startedClaims.has(claim)) continue;\n ownedClaims.delete(claim);\n tracker.release(claim);\n }\n };\n }, [stopResumeReplay, tracker]);\n\n // Handlers (and consent) are async, so the run can be stopped or the request\n // answered elsewhere while we await. The live ids let us abandon an\n // unfinished local execution when its request is no longer present. Once a\n // result is cached, only command settlement may stop its replay: Statewire\n // removes submitted requests optimistically before the host accepts them.\n const liveRequestIdsRef = useRef<ReadonlySet<string>>(new Set());\n liveRequestIdsRef.current = new Set((inputRequests ?? []).map((request) => request.id));\n\n const startResumeReplay = useCallback(\n (claim: string, requestId: string) => {\n if (!activeRef.current || resumeClaimsRef.current.has(claim)) return;\n if (!tracker.claimResumeReplay(claim, resumeOwnerRef.current)) return;\n resumeClaimsRef.current.add(claim);\n\n const dispatch = (): void => {\n resumeTimersRef.current.delete(claim);\n if (!activeRef.current) {\n stopResumeReplay(claim);\n return;\n }\n const attempt = tracker.takeResumeAttempt(claim, {\n owner: resumeOwnerRef.current,\n maxAttempts: MAX_CLIENT_TOOL_RESUME_ATTEMPTS,\n });\n if (!attempt) {\n stopResumeReplay(claim);\n return;\n }\n\n try {\n const settled = optionsRef.current.sendResume(requestId, attempt.value);\n // A fulfilled command result is the authoritative acknowledgement:\n // the host accepted and applied the answer. The local input request\n // disappearing is NOT an acknowledgement because the Statewire\n // reducer does that optimistically as soon as the command is\n // submitted.\n void Promise.resolve(settled).then(\n () => {\n // Keep the execution claim, but discard the cached result once\n // the host accepts it. A stale pending-request render can no\n // longer replay the command or re-execute the local mutation.\n tracker.settleResume(claim);\n stopResumeReplay(claim);\n },\n (error) => {\n // A rejected attempt may have raced another still-pending\n // replay. Keep the bounded loop alive; any accepted sibling\n // stops it, while the global attempt cap prevents storms.\n console.error(`${logLabel} failed to resume client tool results:`, error);\n },\n );\n } catch (error) {\n // Keep the cached result and retry it. Re-executing a mutating local\n // handler would be unsafe; replaying its id-stamped result is safe.\n console.error(`${logLabel} failed to resume client tool results:`, error);\n }\n\n if (\n attempt.attemptNumber >= MAX_CLIENT_TOOL_RESUME_ATTEMPTS ||\n !activeRef.current ||\n !resumeClaimsRef.current.has(claim)\n ) {\n stopResumeReplay(claim);\n return;\n }\n const timer = setTimeout(dispatch, clientToolResumeRetryDelayMs(attempt.attemptNumber));\n resumeTimersRef.current.set(claim, { requestId, timer });\n };\n\n dispatch();\n },\n [logLabel, stopResumeReplay, tracker],\n );\n\n const executeRequest = useCallback(\n async (request: StatewireThread.CustomInputRequest) => {\n const payload = request.payload;\n if (!isClientToolInterrupt(payload)) return;\n const claim = clientToolClaimKey(threadId, request.id);\n const {\n tools,\n confirmWrites,\n writeDeclinedMessage,\n missingToolMessage,\n onCallSettled,\n sendResume,\n } = optionsRef.current;\n\n const results = await executeClientToolBatch({\n calls: payload.context.requests,\n tools,\n // Live while the request still exists AND this surface may answer it:\n // mounted, or already past the point of no return (handlers started —\n // the detached execution must finish and settle, never re-execute).\n // After unmount the request-id snapshot freezes at its last observed\n // state, which keeps a started batch's own request visible to it.\n isLive: () =>\n liveRequestIdsRef.current.has(request.id) &&\n (activeRef.current || startedClaimsRef.current.has(claim)),\n onExecutionStart: () => {\n startedClaimsRef.current.add(claim);\n },\n ...(confirmWrites\n ? {\n confirmWrites: ({ writeToolNames }: { writeToolNames: readonly string[] }) =>\n confirmWrites({ requestId: request.id, writeToolNames }),\n }\n : {}),\n ...(writeDeclinedMessage !== undefined ? { writeDeclinedMessage } : {}),\n ...(missingToolMessage ? { missingToolMessage } : {}),\n ...(onCallSettled ? { onCallSettled } : {}),\n });\n if (results === null) {\n releaseClaim(claim);\n return;\n }\n\n const resumeValue = clientToolResultsResumeValue(results);\n tracker.cacheResume(claim, resumeValue);\n // Execution is complete, but the exactly-once claim remains. A pending\n // re-emission must replay this cached value, never run the handlers again.\n ownedClaimsRef.current.delete(claim);\n startedClaimsRef.current.delete(claim);\n\n if (!activeRef.current) {\n // The handler crossed an unmount. Preserve the pre-existing best-effort\n // settle through its captured transport, but leave the cached result\n // available for a later mount if this dispatch is lost.\n if (tracker.claimResumeReplay(claim, resumeOwnerRef.current)) {\n const attempt = tracker.takeResumeAttempt(claim, {\n owner: resumeOwnerRef.current,\n maxAttempts: MAX_CLIENT_TOOL_RESUME_ATTEMPTS,\n });\n if (attempt) {\n try {\n void Promise.resolve(sendResume(request.id, attempt.value)).then(\n () => tracker.settleResume(claim),\n (error) => {\n console.error(`${logLabel} failed to resume client tool results:`, error);\n },\n );\n } catch (error) {\n console.error(`${logLabel} failed to resume client tool results:`, error);\n }\n }\n // Ownership only fences this best-effort dispatch; a future mount\n // may claim and replay the cached value if it did not settle.\n tracker.releaseResumeReplay(claim, resumeOwnerRef.current);\n }\n return;\n }\n\n startResumeReplay(claim, request.id);\n },\n [logLabel, releaseClaim, startResumeReplay, threadId, tracker],\n );\n\n useEffect(() => {\n if (!pendingRequest) return;\n const claim = clientToolClaimKey(threadId, pendingRequest.id);\n if (!tracker.claim(claim)) {\n // The local handler already ran. This is the reconnect/remount path:\n // replay its cached answer instead of double-executing the side effect.\n if (tracker.getCachedResume(claim)) {\n startResumeReplay(claim, pendingRequest.id);\n }\n return;\n }\n ownedClaimsRef.current.add(claim);\n void executeRequest(pendingRequest);\n }, [executeRequest, pendingRequest, startResumeReplay, threadId, tracker]);\n}\n"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-tools.cjs","sources":["../../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":["CLIENT_TOOL_INTERRUPT_SOURCE","CLIENT_TOOL_RESULT_STATUS_SUCCESS","CLIENT_TOOL_RESULT_STATUS_ERROR"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-tools.js","sources":["../../../../../agent-runtime/src/client-tools.ts"],"sourcesContent":["/**\n * Client-executed tools for statewire deep-agent threads.\n *\n * A client (the Office add-in, the Chrome extension, …) declares tools it can\n * execute locally by sending their JSON-schema definitions through\n * `runConfig.custom.client_tools`. The deep-agent runtime binds them as\n * interrupt-backed tools: when the model calls one, the run parks on a\n * LangGraph interrupt whose payload carries the tool name and arguments, the\n * statewire host publishes it as an `interrupt` input request, and the client\n * executes the tool and resumes the run with the result envelope.\n *\n * This module is transport-agnostic: it holds the wire contract (definitions,\n * interrupt payload shape, result envelopes) and a duplicate-execution guard.\n * The React hook that watches input requests lives with each client, next to\n * its host-specific tool handlers.\n */\n\nimport {\n CLIENT_TOOL_INTERRUPT_SOURCE,\n CLIENT_TOOL_RESULT_STATUS_ERROR,\n CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n} from './generated/runconfig-contract';\n\n/** `source`/`type` stamped on the interrupt payload by the agora runtime. */\nexport { CLIENT_TOOL_INTERRUPT_SOURCE } from './generated/runconfig-contract';\n\n/** JSON-schema description of one client-executed tool. */\nexport interface ClientToolDefinition {\n /** Snake_case tool id the model calls (e.g. `update_sheet_range`). */\n name: string;\n description: string;\n /** JSON schema for the tool arguments (`{\"type\": \"object\", ...}`). */\n parameters: Record<string, unknown>;\n /** Mutating tools gate on the client's write-approval flow before running. */\n requiresWrite: boolean;\n}\n\n/** Per-call context the client-tool bridge hands a handler; absent when a host\n * invokes a handler outside a parked run. */\nexport interface ClientToolHandlerContext {\n /** The model's tool-call id, for handlers that correlate calls. */\n toolCallId: string;\n}\n\nexport type ClientToolHandler = (\n args: Record<string, unknown>,\n context?: ClientToolHandlerContext,\n) => Promise<unknown>;\n\nexport interface RegisteredClientTool {\n definition: ClientToolDefinition;\n handler: ClientToolHandler;\n}\n\n/** One parked tool call inside a batched client-tool input request. */\nexport interface ClientToolCallRequest {\n /** LangGraph interrupt id — the key the resume result map must use. */\n interrupt_id: string;\n tool_name: string;\n tool_args: Record<string, unknown>;\n requires_write?: boolean;\n}\n\n/**\n * The input-request payload the agora runtime publishes for parked client\n * tool calls. A parallel model turn parks several interrupts at once; the\n * runtime batches them into one payload and the client answers once with\n * `{ client_tool_results: { <interrupt_id>: <result envelope> } }`.\n */\nexport interface ClientToolInterruptPayload {\n source: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n type: typeof CLIENT_TOOL_INTERRUPT_SOURCE;\n message: string;\n context: {\n requests: ClientToolCallRequest[];\n };\n}\n\nfunction isClientToolCallRequest(value: unknown): value is ClientToolCallRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const candidate = value as Partial<ClientToolCallRequest>;\n return (\n typeof candidate.interrupt_id === 'string' &&\n candidate.interrupt_id.length > 0 &&\n typeof candidate.tool_name === 'string'\n );\n}\n\n/**\n * Loose source check for surfaces that must NOT handle a client-tool park\n * (e.g. the generic approval banner). Unlike `isClientToolInterrupt`, this\n * matches any payload stamped with the client-tool source — including shapes\n * from a mismatched client/server version — so a skewed deploy can never\n * surface a generic \"Continue\" resume that corrupts a parked tool call.\n */\nexport function hasClientToolInterruptSource(payload: unknown): boolean {\n return (\n !!payload &&\n typeof payload === 'object' &&\n !Array.isArray(payload) &&\n (payload as { source?: unknown }).source === CLIENT_TOOL_INTERRUPT_SOURCE\n );\n}\n\nexport function isClientToolInterrupt(\n payload: unknown,\n): payload is ClientToolInterruptPayload {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return false;\n }\n const candidate = payload as Partial<ClientToolInterruptPayload>;\n return (\n candidate.source === CLIENT_TOOL_INTERRUPT_SOURCE &&\n candidate.type === CLIENT_TOOL_INTERRUPT_SOURCE &&\n typeof candidate.context === 'object' &&\n candidate.context !== null &&\n Array.isArray(candidate.context.requests) &&\n candidate.context.requests.length > 0 &&\n candidate.context.requests.every(isClientToolCallRequest)\n );\n}\n\n/** Tool arguments from a call request, defaulting to an empty object. */\nexport function clientToolCallArgs(\n request: ClientToolCallRequest,\n): Record<string, unknown> {\n const args = request.tool_args;\n return args && typeof args === 'object' && !Array.isArray(args) ? args : {};\n}\n\n/** The `run/input` resume value answering a batched client-tool park. */\nexport function clientToolResultsResumeValue(\n results: Record<string, ClientToolResultEnvelope>,\n): Record<string, unknown> {\n return { client_tool_results: results };\n}\n\n/** Wire entries for `runConfig.custom.client_tools`. */\nexport function clientToolRunConfigEntries(\n tools: readonly RegisteredClientTool[],\n): Record<string, unknown>[] {\n return tools.map(({ definition }) => ({\n name: definition.name,\n description: definition.description,\n parameters: definition.parameters,\n requires_write: definition.requiresWrite,\n }));\n}\n\n/** Successful tool execution, resumed back into the parked run. */\nexport interface ClientToolSuccessEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_SUCCESS;\n result: unknown;\n}\n\n/** Failed or declined tool execution, resumed back into the parked run. */\nexport interface ClientToolErrorEnvelope {\n status: typeof CLIENT_TOOL_RESULT_STATUS_ERROR;\n error: string;\n}\n\nexport type ClientToolResultEnvelope =\n | ClientToolSuccessEnvelope\n | ClientToolErrorEnvelope;\n\nexport function clientToolSuccessEnvelope(\n result: unknown,\n): ClientToolSuccessEnvelope {\n return {\n status: CLIENT_TOOL_RESULT_STATUS_SUCCESS,\n result: result === undefined ? null : result,\n };\n}\n\nexport function clientToolErrorEnvelope(error: unknown): ClientToolErrorEnvelope {\n const message =\n error instanceof Error\n ? error.message\n : typeof error === 'string'\n ? error\n : 'Client tool execution failed.';\n return { status: CLIENT_TOOL_RESULT_STATUS_ERROR, error: message };\n}\n\n/**\n * Once-per-request execution guard. Statewire re-emits the pending input\n * request on every state update, so an executor effect can observe the same\n * request many times; the tracker makes exactly one observation win.\n */\nexport interface ClientToolRequestTracker {\n /** True exactly once per request id; later calls return false. */\n claim(requestId: string): boolean;\n /** Un-claim a request that was NOT resumed (e.g. abandoned mid-approval),\n * so a re-emitted request can be executed instead of stranding the run. */\n release(requestId: string): void;\n has(requestId: string): boolean;\n /** Cache the completed, wire-safe resume value without releasing the\n * exactly-once execution claim. A reconnect may replay this value, but it\n * must never execute the local handler again. */\n cacheResume(requestId: string, value: Record<string, unknown>): void;\n getCachedResume(requestId: string): ClientToolCachedResume | undefined;\n /** Give one mounted bridge ownership of retrying a cached resume. */\n claimResumeReplay(requestId: string, owner: symbol): boolean;\n releaseResumeReplay(requestId: string, owner: symbol): void;\n /** Record authoritative host acceptance. The execution claim remains held\n * so a briefly stale request cannot execute its local side effects again. */\n settleResume(requestId: string): void;\n /** Atomically consume one bounded replay attempt for the current owner. */\n takeResumeAttempt(\n requestId: string,\n options: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined;\n}\n\nexport interface ClientToolCachedResume {\n value: Record<string, unknown>;\n attemptCount: number;\n}\n\nexport interface ClientToolResumeAttempt {\n value: Record<string, unknown>;\n attemptNumber: number;\n}\n\nexport interface CreateClientToolRequestTrackerOptions {\n /** Maximum completed resume values retained for reconnect recovery. Active\n * executions are never evicted. */\n maxCachedResumes?: number;\n}\n\ninterface ClientToolRequestEntry {\n resumeValue?: Record<string, unknown>;\n resumeAttemptCount: number;\n resumeOwner?: symbol;\n settled?: boolean;\n}\n\nconst DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES = 128;\n\nexport function createClientToolRequestTracker(\n options: CreateClientToolRequestTrackerOptions = {},\n): ClientToolRequestTracker {\n const maxCachedResumes = options.maxCachedResumes ?? DEFAULT_MAX_CACHED_CLIENT_TOOL_RESUMES;\n const requests = new Map<string, ClientToolRequestEntry>();\n\n const pruneCompletedRequests = (protectedRequestId?: string): void => {\n let completedCount = 0;\n for (const entry of requests.values()) {\n if (entry.resumeValue !== undefined || entry.settled === true) completedCount += 1;\n }\n if (completedCount <= maxCachedResumes) return;\n\n // Map iteration is insertion order. Only inactive, completed entries are\n // eligible: evicting an executing request could allow a document mutation\n // to run twice, evicting an active replay would split ownership, and the\n // newly completed request must survive long enough for its first dispatch.\n for (const [requestId, entry] of requests) {\n if (completedCount <= maxCachedResumes) break;\n if (\n requestId === protectedRequestId ||\n (entry.resumeValue === undefined && entry.settled !== true) ||\n entry.resumeOwner !== undefined\n ) {\n continue;\n }\n requests.delete(requestId);\n completedCount -= 1;\n }\n };\n\n return {\n claim(requestId: string): boolean {\n if (requests.has(requestId)) return false;\n requests.set(requestId, { resumeAttemptCount: 0 });\n return true;\n },\n release(requestId: string): void {\n requests.delete(requestId);\n },\n has(requestId: string): boolean {\n return requests.has(requestId);\n },\n cacheResume(requestId: string, value: Record<string, unknown>): void {\n const entry = requests.get(requestId) ?? { resumeAttemptCount: 0 };\n entry.resumeValue = value;\n entry.settled = false;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n getCachedResume(requestId: string): ClientToolCachedResume | undefined {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return undefined;\n return {\n value: entry.resumeValue,\n attemptCount: entry.resumeAttemptCount,\n };\n },\n claimResumeReplay(requestId: string, owner: symbol): boolean {\n const entry = requests.get(requestId);\n if (!entry || entry.resumeValue === undefined) return false;\n if (entry.resumeOwner !== undefined && entry.resumeOwner !== owner) {\n return false;\n }\n entry.resumeOwner = owner;\n return true;\n },\n releaseResumeReplay(requestId: string, owner: symbol): void {\n const entry = requests.get(requestId);\n if (entry?.resumeOwner === owner) entry.resumeOwner = undefined;\n pruneCompletedRequests();\n },\n settleResume(requestId: string): void {\n const entry = requests.get(requestId);\n if (!entry) return;\n entry.resumeValue = undefined;\n entry.resumeOwner = undefined;\n entry.settled = true;\n requests.delete(requestId);\n requests.set(requestId, entry);\n pruneCompletedRequests(requestId);\n },\n takeResumeAttempt(\n requestId: string,\n attemptOptions: { owner: symbol; maxAttempts: number },\n ): ClientToolResumeAttempt | undefined {\n const entry = requests.get(requestId);\n if (\n !entry ||\n entry.resumeValue === undefined ||\n entry.resumeOwner !== attemptOptions.owner ||\n entry.resumeAttemptCount >= attemptOptions.maxAttempts\n ) {\n return undefined;\n }\n entry.resumeAttemptCount += 1;\n return {\n value: entry.resumeValue,\n attemptNumber: entry.resumeAttemptCount,\n };\n },\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation-index.cjs","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport type { ConversationAttention, ConversationAttentionSource } from './conversation-list';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the conversation-index statewire (issue #29088): agora\n * replicates the newest page of one viewer's conversation list as one state\n * object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n *\n * A viewer is a `(workspaceId, userId)` pair and every pair is its own\n * instance (`/conversation-index/<workspaceId>-<userId>`): workspaces are\n * tenants, so a tab in one workspace never shares a window with a tab in\n * another, and switching workspaces attaches to a different instance instead\n * of retagging the one other tabs are still reading.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state and its one-line next action, present once\n * the publishing agora derives them (absent from snapshots replicated by\n * an older pod). Pull-request state is never pushed — it rides the list\n * fetch only (`include_pull_requests`).\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n is_favorited?: boolean | null;\n}\n\n/**\n * When one of this viewer's own asset subscriptions on a thread last queued or\n * settled an event: thread id → ISO instant. A contentless \"re-read now\" for\n * Olympus's Watching card, which reads what changed through its own gated\n * query; it never says what changed, who changed it, or where. Absent from an\n * older agora's snapshot.\n */\nexport type ConversationWatchActivity = Readonly<Record<string, string>>;\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n watch_activity?: ConversationWatchActivity;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The workspace being viewed — half of the instance id. Callers gate mounting on it. */\n workspaceId: string;\n /** The signed-in user's id — the other half. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n /**\n * The viewer's watch-activity stamps (see {@link ConversationWatchActivity});\n * `null` until the snapshot applies or when the host does not send them.\n */\n watchActivity: ConversationWatchActivity | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/** Instance id of one viewer's index; mirrors agora's `conversation_index_id`. */\nexport function conversationIndexId({\n workspaceId,\n userId,\n}: {\n workspaceId: string;\n userId: string;\n}): string {\n return `${workspaceId}-${userId}`;\n}\n\n/**\n * Attach to the live conversation index of one `(workspace, user)` viewer.\n */\nexport function useConversationIndex({\n baseUrl,\n workspaceId,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n const indexId = conversationIndexId({ workspaceId, userId });\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ indexId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ indexId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.indexId !== indexId) {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.indexId === indexId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n indexId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user and workspace.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(indexId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.indexId === indexId ? prev : { indexId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, indexId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n watchActivity: client.state?.watch_activity ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over a snapshot's watch-activity stamps: the\n * well-formed `[threadId, instant]` pairs, sorted by thread. An empty string\n * means \"nothing sent yet\" (no snapshot, or a host that predates the stamps),\n * so an effect keyed on it skips; an applied-but-empty map is `'[]'`.\n */\nexport function watchActivityFingerprint(activity: unknown): string {\n if (activity === null || typeof activity !== 'object' || Array.isArray(activity)) return '';\n return JSON.stringify(\n Object.entries(activity)\n .filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n );\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n entry.attention ?? null,\n entry.next_action ?? null,\n entry.attention_source ?? null,\n entry.is_favorited ?? null,\n ])\n );\n}\n"],"names":["useRef","useState","initialAuthDenialTrackerState","useStatewire","withKey","StatewireSuspended","statewireTransport","observeAuthDenial","projectDeepAgentConnection","useEffect","useCallback"],"mappings":";;;;;;;;;AA4GO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,WAAW,IAAI,MAAM;AACjC;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAaA,MAAAA,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,QAAM,UAAU,oBAAoB,EAAE,aAAa,QAAQ;AAM3D,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA;AAAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAYD,MAAAA,OAAO,EAAE,SAAS,OAAOE,WAAAA,8BAAA,GAAiC;AAC5E,MAAI,UAAU,QAAQ,YAAY,SAAS;AACzC,cAAU,UAAU,EAAE,SAAS,OAAOA,WAAAA,gCAA8B;AAAA,EACtE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,YAAY;AACvD,QAAM,SAASC,UAAAA,aAA4E;AAAA,IACzF,WAAWC,IAAAA;AAAAA,MACT;AAAA,MACA,CAAC,WAAW,WACRC,sCAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,SAAS,OAAOH,WAAAA,gCAA8B;AACpE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACDI,mBAAAA;AAAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,OAAO,CAAC;AAAA,UAClE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAASC,WAAAA;AAAAA,IACb,UAAU,QAAQ;AAAA,IAClBC,OAAAA,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjCC,QAAAA,UAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,aAAY,UAAU,OAAO,EAAE,SAAS,SAAS,YAAA;AAAA,IAAY;AAAA,EAEvE,GAAG,CAAC,SAAS,YAAY,aAAa,OAAO,CAAC;AAC9C,QAAM,cAAcT,MAAAA,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAUU,MAAAA,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAWA,MAAAA,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,iBAAe,YAAO,UAAP,mBAAc,mBAAkB;AAAA,IAC/C,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation-index.js","sources":["../../../../../agent-runtime/src/conversation-index.ts"],"sourcesContent":["import { withKey } from '@assistant-ui/tap';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useStatewire, type StatewireClient } from 'statewire';\nimport { initialAuthDenialTrackerState, observeAuthDenial } from './auth-denial';\nimport type { ConversationAttention, ConversationAttentionSource } from './conversation-list';\nimport { projectDeepAgentConnection } from './errors';\nimport { StatewireSuspended } from './statewire-suspended';\nimport { statewireTransport } from './statewire-transport';\nimport type { DeepAgentHeadersResolver } from './use-deep-agent-thread';\n\n/**\n * Client side of the conversation-index statewire (issue #29088): agora\n * replicates the newest page of one viewer's conversation list as one state\n * object, so thread creation / lifecycle / title changes arrive as ops\n * instead of newest-page polling. The replicated rows are renderable and the\n * typed commands extend or refresh the inclusive window; reconnects are free\n * because the protocol's snapshot-first attach IS the newest-page reconcile.\n *\n * A viewer is a `(workspaceId, userId)` pair and every pair is its own\n * instance (`/conversation-index/<workspaceId>-<userId>`): workspaces are\n * tenants, so a tab in one workspace never shares a window with a tab in\n * another, and switching workspaces attaches to a different instance instead\n * of retagging the one other tabs are still reading.\n */\nexport interface ConversationIndexEntry {\n thread_id: string;\n title: string;\n updated_at: string;\n state: string | null;\n runtime: string | null;\n start_channel: string | null;\n asset_type: string;\n asset_id: string | null;\n app_id: string | null;\n trigger_type: string | null;\n last_message_preview: string | null;\n message_count: number | null;\n is_unread: boolean | null;\n /**\n * Canonical status_v2 planes, present once the publishing agora reports them\n * (absent from snapshots replicated by an older pod during a rollout).\n */\n session_status?: string | null;\n run_status?: string | null;\n /**\n * The viewer's attention state and its one-line next action, present once\n * the publishing agora derives them (absent from snapshots replicated by\n * an older pod). Pull-request state is never pushed — it rides the list\n * fetch only (`include_pull_requests`).\n */\n attention?: ConversationAttention | null;\n next_action?: string | null;\n attention_source?: ConversationAttentionSource | null;\n is_favorited?: boolean | null;\n}\n\n/**\n * When one of this viewer's own asset subscriptions on a thread last queued or\n * settled an event: thread id → ISO instant. A contentless \"re-read now\" for\n * Olympus's Watching card, which reads what changed through its own gated\n * query; it never says what changed, who changed it, or where. Absent from an\n * older agora's snapshot.\n */\nexport type ConversationWatchActivity = Readonly<Record<string, string>>;\n\ninterface ConversationIndexState {\n conversations?: ConversationIndexEntry[];\n has_more?: boolean;\n total_count?: number;\n watch_activity?: ConversationWatchActivity;\n}\n\ninterface ConversationIndexCommands {\n [key: string]: (...args: never[]) => unknown;\n refresh(): void;\n load_more(): void;\n}\n\nexport interface UseConversationIndexOptions {\n /** Base URL of the conversation-index host (the `…/v2/conversation-index` mount). */\n baseUrl: string;\n /** The workspace being viewed — half of the instance id. Callers gate mounting on it. */\n workspaceId: string;\n /** The signed-in user's id — the other half. Callers gate mounting on it. */\n userId: string;\n /** Transport selection: SSE when true, WebSocket otherwise. */\n sse?: boolean;\n headers: DeepAgentHeadersResolver;\n /** Park the transport until the caller has a resolved principal. */\n enabled?: boolean;\n}\n\nexport interface UseConversationIndexResult {\n /** Newest-page entries, or `null` until the attach snapshot applies. */\n entries: ConversationIndexEntry[] | null;\n hasNextPage: boolean;\n totalCount: number | null;\n /**\n * The viewer's watch-activity stamps (see {@link ConversationWatchActivity});\n * `null` until the snapshot applies or when the host does not send them.\n */\n watchActivity: ConversationWatchActivity | null;\n connection: StatewireClient.Connection;\n refresh: () => Promise<void>;\n loadMore: () => Promise<void>;\n}\n\n/** The conversation-index host is mounted beside the threads host. */\nexport function conversationIndexUrlFromStatewireUrl(statewireUrl: string): string {\n return statewireUrl.replace(/\\/threads\\/?$/, '/conversation-index');\n}\n\n/** Instance id of one viewer's index; mirrors agora's `conversation_index_id`. */\nexport function conversationIndexId({\n workspaceId,\n userId,\n}: {\n workspaceId: string;\n userId: string;\n}): string {\n return `${workspaceId}-${userId}`;\n}\n\n/**\n * Attach to the live conversation index of one `(workspace, user)` viewer.\n */\nexport function useConversationIndex({\n baseUrl,\n workspaceId,\n userId,\n sse = false,\n headers,\n enabled = true,\n}: UseConversationIndexOptions): UseConversationIndexResult {\n const headersRef = useRef(headers);\n headersRef.current = headers;\n const indexId = conversationIndexId({ workspaceId, userId });\n // Auth-denial park (same policy as the thread transport): the index is a\n // pure push signal, so after a streak of `unauthorized` denials the retry\n // loop stops instead of hammering the attach endpoint every 30s for as long\n // as the tab lives. A remount or the parked connection's `reconnect()`\n // re-arms it.\n const [parked, setParked] = useState<{ indexId: string; message: string | undefined } | null>(\n null\n );\n const denialRef = useRef({ indexId, state: initialAuthDenialTrackerState() });\n if (denialRef.current.indexId !== indexId) {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n }\n const isParked = parked !== null && parked.indexId === indexId;\n const client = useStatewire<ConversationIndexState | undefined, ConversationIndexCommands>({\n transport: withKey(\n indexId,\n !enabled || isParked\n ? StatewireSuspended<ConversationIndexCommands>({\n message:\n parked?.message ??\n (enabled\n ? 'Conversation index authorization was refused.'\n : 'Conversation index is waiting for a signed-in user and workspace.'),\n ...(enabled\n ? {\n onReconnectRequested: () => {\n denialRef.current = { indexId, state: initialAuthDenialTrackerState() };\n setParked(null);\n },\n }\n : {}),\n })\n : statewireTransport<ConversationIndexState, ConversationIndexCommands>(\n {\n url: `${baseUrl.replace(/\\/+$/, '')}/${encodeURIComponent(indexId)}`,\n headers: (ctx: { staleAuth: boolean }) => headersRef.current(ctx),\n },\n { sse }\n )\n ),\n initialState: undefined,\n });\n // Observing during render is idempotent (the tracker dedupes by attempt\n // number), so a re-render of the same connection state cannot double-count.\n // The tracker consumes the projected vocabulary, never the raw connection.\n const denial = observeAuthDenial(\n denialRef.current.state,\n projectDeepAgentConnection(client.connection)\n );\n denialRef.current.state = denial.state;\n const shouldPark = denial.park;\n const parkMessage = denial.state.message;\n useEffect(() => {\n if (!enabled || !shouldPark) return;\n setParked((prev) =>\n prev?.indexId === indexId ? prev : { indexId, message: parkMessage }\n );\n }, [enabled, shouldPark, parkMessage, indexId]);\n const commandsRef = useRef(client.commands);\n commandsRef.current = client.commands;\n const refresh = useCallback(() => commandsRef.current.refresh(), []);\n const loadMore = useCallback(() => commandsRef.current.load_more(), []);\n return {\n entries: client.state?.conversations ?? null,\n hasNextPage: client.state?.has_more ?? false,\n totalCount: client.state?.total_count ?? null,\n watchActivity: client.state?.watch_activity ?? null,\n connection: client.connection,\n refresh,\n loadMore,\n };\n}\n\n/**\n * Stable change fingerprint over a snapshot's watch-activity stamps: the\n * well-formed `[threadId, instant]` pairs, sorted by thread. An empty string\n * means \"nothing sent yet\" (no snapshot, or a host that predates the stamps),\n * so an effect keyed on it skips; an applied-but-empty map is `'[]'`.\n */\nexport function watchActivityFingerprint(activity: unknown): string {\n if (activity === null || typeof activity !== 'object' || Array.isArray(activity)) return '';\n return JSON.stringify(\n Object.entries(activity)\n .filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n );\n}\n\n/**\n * Stable change fingerprint over the entries a list surface reacts to. An\n * empty string means \"no snapshot yet\" — effects keyed on the fingerprint\n * should treat it as a no-op rather than an empty list. An applied-but-empty\n * snapshot serializes to `'[]'`, so deleting the last conversation still\n * changes the fingerprint. JSON-encoding keeps arbitrary titles from\n * colliding across field or entry boundaries.\n */\nexport function conversationIndexFingerprint(\n entries: ConversationIndexEntry[] | null\n): string {\n if (entries === null) return '';\n return JSON.stringify(\n entries.map((entry) => [\n entry.thread_id,\n entry.updated_at,\n entry.state,\n entry.title,\n entry.run_status ?? null,\n entry.is_unread ?? null,\n entry.attention ?? null,\n entry.next_action ?? null,\n entry.attention_source ?? null,\n entry.is_favorited ?? null,\n ])\n );\n}\n"],"names":[],"mappings":";;;;;;;AA4GO,SAAS,qCAAqC,cAA8B;AACjF,SAAO,aAAa,QAAQ,iBAAiB,qBAAqB;AACpE;AAGO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,WAAW,IAAI,MAAM;AACjC;AAKO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA,UAAU;AACZ,GAA4D;;AAC1D,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AACrB,QAAM,UAAU,oBAAoB,EAAE,aAAa,QAAQ;AAM3D,QAAM,CAAC,QAAQ,SAAS,IAAI;AAAA,IAC1B;AAAA,EAAA;AAEF,QAAM,YAAY,OAAO,EAAE,SAAS,OAAO,8BAAA,GAAiC;AAC5E,MAAI,UAAU,QAAQ,YAAY,SAAS;AACzC,cAAU,UAAU,EAAE,SAAS,OAAO,gCAA8B;AAAA,EACtE;AACA,QAAM,WAAW,WAAW,QAAQ,OAAO,YAAY;AACvD,QAAM,SAAS,aAA4E;AAAA,IACzF,WAAW;AAAA,MACT;AAAA,MACA,CAAC,WAAW,WACR,mBAA8C;AAAA,QAC5C,UACE,iCAAQ,aACP,UACG,kDACA;AAAA,QACN,GAAI,UACA;AAAA,UACE,sBAAsB,MAAM;AAC1B,sBAAU,UAAU,EAAE,SAAS,OAAO,gCAA8B;AACpE,sBAAU,IAAI;AAAA,UAChB;AAAA,QAAA,IAEF,CAAA;AAAA,MAAC,CACN,IACD;AAAA,QACE;AAAA,UACE,KAAK,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,OAAO,CAAC;AAAA,UAClE,SAAS,CAAC,QAAgC,WAAW,QAAQ,GAAG;AAAA,QAAA;AAAA,QAElE,EAAE,IAAA;AAAA,MAAI;AAAA,IACR;AAAA,IAEN,cAAc;AAAA,EAAA,CACf;AAID,QAAM,SAAS;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB,2BAA2B,OAAO,UAAU;AAAA,EAAA;AAE9C,YAAU,QAAQ,QAAQ,OAAO;AACjC,QAAM,aAAa,OAAO;AAC1B,QAAM,cAAc,OAAO,MAAM;AACjC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,WAAY;AAC7B;AAAA,MAAU,CAAC,UACT,6BAAM,aAAY,UAAU,OAAO,EAAE,SAAS,SAAS,YAAA;AAAA,IAAY;AAAA,EAEvE,GAAG,CAAC,SAAS,YAAY,aAAa,OAAO,CAAC;AAC9C,QAAM,cAAc,OAAO,OAAO,QAAQ;AAC1C,cAAY,UAAU,OAAO;AAC7B,QAAM,UAAU,YAAY,MAAM,YAAY,QAAQ,QAAA,GAAW,EAAE;AACnE,QAAM,WAAW,YAAY,MAAM,YAAY,QAAQ,UAAA,GAAa,EAAE;AACtE,SAAO;AAAA,IACL,WAAS,YAAO,UAAP,mBAAc,kBAAiB;AAAA,IACxC,eAAa,YAAO,UAAP,mBAAc,aAAY;AAAA,IACvC,cAAY,YAAO,UAAP,mBAAc,gBAAe;AAAA,IACzC,iBAAe,YAAO,UAAP,mBAAc,mBAAkB;AAAA,IAC/C,YAAY,OAAO;AAAA,IACnB;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation-list.cjs","sources":["../../../../../agent-runtime/src/conversation-list.ts"],"sourcesContent":["import type { InfiniteData, QueryClient } from '@tanstack/react-query';\nimport type { ConversationIndexEntry } from './conversation-index';\n\n/**\n * What a session asks of the viewer, derived server-side from the run, the\n * parked interrupt, the pull requests it opened, its termination and its last\n * wake (the Devin-style session list). `needs_you` is the only state that\n * demands the viewer act; `working` is live; `ready` finished with something\n * to look at; `watching` waits on an external party (a review, a check);\n * `done` and `error` are terminal. A backend newer than this client may report\n * a value outside the union — render it as \"no attention\" rather than failing.\n */\nexport type ConversationAttention =\n | 'needs_you'\n | 'working'\n | 'ready'\n | 'watching'\n | 'done'\n | 'error';\n\nexport const CONVERSATION_ATTENTIONS: readonly ConversationAttention[] = [\n 'needs_you',\n 'working',\n 'ready',\n 'watching',\n 'done',\n 'error',\n];\n\n/** Which signal produced the attention state (and the `nextAction` copy). */\nexport type ConversationAttentionSource =\n | 'interrupt'\n | 'run'\n | 'pull_request'\n | 'termination'\n | 'wake'\n | 'handoff'\n | 'follow_up';\n\n/** The last event that woke a resting session (a GitHub webhook, a schedule, a handoff). */\nexport interface ConversationWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\n/**\n * Live GitHub state of a pull request the session opened, read by the server\n * when the list renders. Never pushed by the conversation index — it comes\n * from the list fetch only, so a fold keeps the fetched value.\n */\nexport interface ConversationPullRequest {\n /** `owner/repo` the pull request lives in. */\n repoFullName: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n isDraft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n reviewDecision: string | null;\n /** Combined state of the checks on the head commit. */\n checksState: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updatedAt: string;\n}\n\n/**\n * Shared conversation/thread LIST contract (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Every chat surface lists \"my conversations\" against the same underlying\n * session assets, but historically each declared its own row shape, paging\n * model, and filter set (Olympus GraphQL `listConversations`, the SDK/Chrome\n * REST `/api/conversations/threads/list`, mobile's `/api/v0/sessions`). This\n * module is the ONE contract those surfaces converge on:\n *\n * - **Row shape**: {@link ConversationSummary} — the canonical camelCase\n * superset. Fields a transport cannot supply are `null`, never invented.\n * - **Ordering**: newest first by `updatedAt`, always. Ordering is part of the\n * contract, not a parameter — no surface sorts any other way.\n * - **Paging**: offset/limit with a LIVE newest page and explicitly-fetched\n * frozen history pages (see `useConversationList`). `hasMore` continues the\n * scan; `totalCount` is advisory and may be `null` for transports that do\n * not count.\n * - **Filtering**: {@link ConversationListFilter}. `appId` follows\n * `docs/SESSION_APP_ID_GUIDE.md`: exact match when set (legacy `null`\n * sessions excluded), no filter when unset. A fetcher that cannot express a\n * requested filter field MUST throw rather than silently ignore it — a\n * dropped filter returns wrong rows under a cache key that claims otherwise.\n * - **Freshness**: the per-user conversation-index statewire\n * (`conversation-index.ts`) is the push signal; these queries are the data\n * source. Push handlers call {@link invalidateConversationListCaches}.\n */\nexport interface ConversationSummary {\n /** LangGraph thread id — the cross-surface conversation identity. */\n threadId: string;\n title: string;\n /** ISO 8601 timestamp of last update (the contract's sort key). */\n updatedAt: string;\n /** ISO 8601 creation timestamp, when the transport reports it. */\n createdAt: string | null;\n /** Session lifecycle state from athena_metadata (e.g. running, completed). */\n state: string | null;\n /** Agent runtime marker (e.g. 'athena_deep_agent'), when reported. */\n runtime: string | null;\n /** Channel the session was started from (e.g. agent_email, web). */\n sourceChannel: string | null;\n /** Asset type (session, video_session, desktop_session, mobile_session). */\n assetType: string | null;\n /** Session asset id (`asset_<uuid>`), when the transport reports it. */\n assetId: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n appId: string | null;\n /** Computer asset whose sandbox started the session (`sourceChannel` 'computer'); omitted by surfaces that don't carry it. */\n computerAssetId?: string | null;\n /** Session asset id this row is a sub-session of; omitted by surfaces that don't carry it. */\n parentSessionId?: string | null;\n /** Sub-sessions of this row the viewer can see; omitted by surfaces that don't carry it. */\n subSessionCount?: number;\n /** Trigger marker (`manual` remains user-initiated; other values are automated). */\n triggerType: string | null;\n /** Last-message preview used when a legacy session has no useful title. */\n preview: string | null;\n messageCount: number | null;\n /** Per-user unread flag derived server-side from the read receipt. */\n isUnread: boolean;\n /**\n * Canonical session status (status_v2: idle | active | needs_input | error),\n * or null when the transport does not report it.\n */\n sessionStatus: string | null;\n /**\n * Canonical status of the session's current run (status_v2: scheduled |\n * queued | running | needs_input | completed | failed | canceled). Null when\n * the transport does not report it — {@link isConversationRunning} then\n * falls back to the legacy `state`. Running badges on every surface derive\n * from this one helper so they cannot disagree.\n */\n runStatus: string | null;\n /**\n * Server-derived attention state. `undefined` when the transport (or an\n * older server) does not report it — {@link deriveConversationAttention}\n * then falls back to the status planes; `null` when the server reports none.\n */\n attention?: ConversationAttention | null;\n /** One line (≤ 80 chars) of what the viewer should do next, when the server derives one. */\n nextAction?: string | null;\n /** Which signal produced `attention` / `nextAction`. */\n attentionSource?: ConversationAttentionSource | null;\n /** Whether the viewer pinned (favorited) the session; `undefined` when not reported. */\n isFavorited?: boolean;\n /** The last wake of a resting session; `undefined` when not reported. */\n lastWake?: ConversationWake | null;\n /**\n * Pull requests the session opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now,\n * `undefined` when the transport did not ask for (or report) it.\n */\n pullRequests?: ConversationPullRequest[] | null;\n}\n\n/**\n * The canonical filter set. Semantics are defined here once; each fetcher\n * translates the fields its transport supports and throws on the rest.\n */\nexport interface ConversationListFilter {\n /** Exact-match client app filter; unset = all apps (SESSION_APP_ID_GUIDE). */\n appId?: string;\n /** Asset types to include (e.g. ['session']); unset = all session types. */\n assetTypes?: readonly string[];\n /** Case-insensitive title search. */\n search?: string;\n /** Hide triggered/background sessions (REST thread list only today). */\n excludeTriggered?: boolean;\n /** Start-channel filter (v0 sessions / REST thread list only today). */\n sourceChannels?: readonly string[];\n /** Start channels to hide (e.g. ['api']); GraphQL `listConversations` only today. */\n excludeSourceChannels?: readonly string[];\n /** Exact computer asset whose sandbox started the session; GraphQL `listConversations` only today. */\n computerAssetId?: string;\n /** Only the sub-sessions of this session asset id; GraphQL `listConversations` only today. */\n parentSessionId?: string;\n /**\n * Lifecycle-state filter (e.g. ['running'], ['completed']). Transports match\n * it with the shared status_v2 rule (running carries a 12-hour recency cutoff).\n */\n states?: readonly string[];\n /** Restrict to sessions run by any of these agents (athena_metadata.agent). */\n agentIds?: readonly string[];\n /**\n * Include branched sub-sessions and orchestration children, which every list\n * hides by default because they belong under their parent thread.\n */\n includeSubSessions?: boolean;\n}\n\nexport interface ConversationListPage {\n /** Rows in contract order (updatedAt desc). */\n items: ConversationSummary[];\n /** Whether another `offset + limit` scan would return more rows. */\n hasMore: boolean;\n /** Total matching rows, or null when the transport does not count. */\n totalCount: number | null;\n}\n\nexport interface ConversationListFetchParams {\n limit: number;\n offset: number;\n filter: ConversationListFilter;\n signal?: AbortSignal;\n}\n\n/**\n * One page fetch against some transport (GraphQL `listConversations`, the\n * REST thread list, …). Must reject on filter fields it cannot express.\n */\nexport type ConversationListFetcher = (\n params: ConversationListFetchParams\n) => Promise<ConversationListPage>;\n\n/** Root of every conversation-list query key (all surfaces, all filters). */\nexport const CONVERSATION_LIST_QUERY_KEY_ROOT = 'athena-conversation-list';\n\n/**\n * Scope used by `useConversationList` when the caller passes no `scopeKey`.\n * Exported so cache seeders (e.g. Olympus's spacesBootstrap adoption) can\n * address the exact keys default-scoped hooks read from.\n */\nexport const CONVERSATION_LIST_DEFAULT_SCOPE_KEY = 'default';\n\n/**\n * Normalize a filter to its semantic content: unset/empty fields are dropped\n * (so `{}` and `{ appId: undefined }` are the same filter) and array order is\n * canonicalized. Fetchers receive the normalized form, so what a cache entry\n * claims to hold is exactly what was fetched.\n */\nexport function normalizeConversationListFilter(\n filter: ConversationListFilter\n): ConversationListFilter {\n const normalized: ConversationListFilter = {};\n if (filter.appId) normalized.appId = filter.appId;\n if (filter.assetTypes && filter.assetTypes.length > 0) {\n normalized.assetTypes = [...filter.assetTypes].sort();\n }\n if (filter.search) normalized.search = filter.search;\n if (filter.excludeTriggered !== undefined) normalized.excludeTriggered = filter.excludeTriggered;\n if (filter.sourceChannels && filter.sourceChannels.length > 0) {\n normalized.sourceChannels = [...filter.sourceChannels].sort();\n }\n if (filter.excludeSourceChannels && filter.excludeSourceChannels.length > 0) {\n normalized.excludeSourceChannels = [...filter.excludeSourceChannels].sort();\n }\n if (filter.computerAssetId) normalized.computerAssetId = filter.computerAssetId;\n if (filter.parentSessionId) normalized.parentSessionId = filter.parentSessionId;\n if (filter.states && filter.states.length > 0) {\n normalized.states = [...filter.states].sort();\n }\n if (filter.agentIds && filter.agentIds.length > 0) {\n normalized.agentIds = [...filter.agentIds].sort();\n }\n if (filter.includeSubSessions) normalized.includeSubSessions = true;\n return normalized;\n}\n\n/** status_v2 run statuses during which a session is actively working. */\nconst RUNNING_RUN_STATUSES: ReadonlySet<string> = new Set(['queued', 'running']);\n\n/**\n * THE running rule for every conversation-list surface (sidebar pane, recent\n * dropdown, workspace section, pushed index rows): the canonical status_v2 run\n * status when the transport reports one, else the legacy `state`. A session\n * paused on a human gate (`needs_input`) is not running.\n */\nexport function isConversationRunning(summary: {\n runStatus?: string | null;\n state?: string | null;\n}): boolean {\n if (summary.runStatus !== null && summary.runStatus !== undefined) {\n return RUNNING_RUN_STATUSES.has(summary.runStatus);\n }\n return summary.state === 'running';\n}\n\nconst KNOWN_ATTENTIONS: ReadonlySet<string> = new Set(CONVERSATION_ATTENTIONS);\n\n/**\n * THE attention rule for every session-list surface: the server-derived\n * `attention` when the transport reports one it knows, else a client fallback\n * from the status planes so an older server still yields the two states a\n * list cannot do without — a human gate reads as `needs_you`, a live run as\n * `working`, a failed run as `error`. `null` when nothing applies.\n */\nexport function deriveConversationAttention(\n summary: Pick<ConversationSummary, 'attention' | 'runStatus' | 'sessionStatus' | 'state'>\n): ConversationAttention | null {\n if (typeof summary.attention === 'string' && KNOWN_ATTENTIONS.has(summary.attention)) {\n return summary.attention;\n }\n if (summary.runStatus === 'needs_input' || summary.sessionStatus === 'needs_input') {\n return 'needs_you';\n }\n if (isConversationRunning(summary)) return 'working';\n if (summary.runStatus === 'failed' || summary.sessionStatus === 'error') return 'error';\n return null;\n}\n\n/**\n * Stable cache identity for a filter: the normalized fields serialized in\n * sorted key order, so semantically equal filters share one cache entry.\n */\nexport function conversationListFilterKey(filter: ConversationListFilter): string {\n const normalized = normalizeConversationListFilter(filter) as Record<string, unknown>;\n return JSON.stringify(\n Object.keys(normalized)\n .sort()\n .map((key) => [key, normalized[key]])\n );\n}\n\n/**\n * Query keys for the two-tier pager. `scopeKey` isolates caches beyond the\n * filter — surfaces MUST include everything that changes whose list this is\n * (principal/user id, backend base URL) so a credential swap can never read\n * another identity's rows out of the cache.\n *\n * `pageSize` is part of the key because it is baked into every cached page's\n * offset boundaries: two hooks sharing a scope and filter but paging by\n * different sizes would otherwise reuse each other's incompatible boundaries\n * and skip or duplicate rows.\n */\nexport const conversationListQueryKeys = {\n root: [CONVERSATION_LIST_QUERY_KEY_ROOT] as const,\n scope: (scopeKey: string) => [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey] as const,\n firstPage: (scopeKey: string, filterKey: string, pageSize: number) =>\n [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey, filterKey, pageSize, 'first-page'] as const,\n history: (scopeKey: string, filterKey: string, pageSize: number) =>\n [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey, filterKey, pageSize, 'history'] as const,\n};\n\n/**\n * Flatten pages into one newest-first list, deduplicating by `threadId`.\n * The live first page wins over retained history copies of the same thread.\n */\nexport function mergeConversationPages(\n pages: readonly ConversationListPage[]\n): ConversationSummary[] {\n const seen = new Set<string>();\n const items: ConversationSummary[] = [];\n for (const page of pages) {\n for (const item of page.items) {\n if (seen.has(item.threadId)) continue;\n seen.add(item.threadId);\n items.push(item);\n }\n }\n return items;\n}\n\n/**\n * Prepend a conversation to a page unless its thread is already present\n * (optimistic \"new thread appears at the top\" write). Identity-preserving\n * no-op when present, so equal writes never notify subscribers.\n */\nexport function prependConversationToPage(\n page: ConversationListPage,\n item: ConversationSummary\n): ConversationListPage {\n if (page.items.some((existing) => existing.threadId === item.threadId)) return page;\n return {\n ...page,\n items: [item, ...page.items],\n totalCount: page.totalCount === null ? null : page.totalCount + 1,\n };\n}\n\n/** Remove a thread's row from a page; identity-preserving when absent. */\nexport function removeConversationFromPage(\n page: ConversationListPage,\n threadId: string\n): ConversationListPage {\n if (!page.items.some((item) => item.threadId === threadId)) return page;\n return { ...page, items: page.items.filter((item) => item.threadId !== threadId) };\n}\n\n/**\n * Patch every row matching `match`; identity-preserving when nothing matches\n * so cache writes cannot cause spurious re-renders.\n */\nexport function patchConversationsInPage(\n page: ConversationListPage,\n match: (item: ConversationSummary) => boolean,\n patch: Partial<ConversationSummary>\n): ConversationListPage {\n let changed = false;\n const items = page.items.map((item) => {\n if (!match(item)) return item;\n changed = true;\n return { ...item, ...patch };\n });\n return changed ? { ...page, items } : page;\n}\n\ntype CachedConversationList =\n | ConversationListPage\n | InfiniteData<ConversationListPage, number>\n | undefined;\n\nfunction isInfinitePages(\n data: ConversationListPage | InfiniteData<ConversationListPage, number>\n): data is InfiniteData<ConversationListPage, number> {\n return 'pages' in data;\n}\n\nfunction mapCachedPages(\n data: CachedConversationList,\n transform: (page: ConversationListPage) => ConversationListPage\n): CachedConversationList {\n if (!data) return data;\n if (isInfinitePages(data)) {\n let changed = false;\n const pages = data.pages.map((page) => {\n const next = transform(page);\n if (next !== page) changed = true;\n return next;\n });\n return changed ? { ...data, pages } : data;\n }\n return transform(data);\n}\n\ninterface ConversationListCacheScope {\n /** Restrict to one scope; omitted = every conversation-list cache. */\n scopeKey?: string;\n}\n\nfunction scopedKey(options?: ConversationListCacheScope): readonly string[] {\n return options?.scopeKey !== undefined\n ? conversationListQueryKeys.scope(options.scopeKey)\n : conversationListQueryKeys.root;\n}\n\n/**\n * Invalidate conversation-list caches (all filters, first page + history).\n * This is the hook for push signals — the conversation-index statewire, a\n * finished run, an archive/restore — anything that changed the server list.\n */\nexport function invalidateConversationListCaches(\n queryClient: QueryClient,\n options?: ConversationListCacheScope\n): Promise<void> {\n return queryClient.invalidateQueries({ queryKey: scopedKey(options) });\n}\n\n/** Cancel in-flight list fetches so an optimistic write cannot be overwritten. */\nexport function cancelConversationListCaches(\n queryClient: QueryClient,\n options?: ConversationListCacheScope\n): Promise<void> {\n return queryClient.cancelQueries({ queryKey: scopedKey(options) });\n}\n\n/**\n * Patch matching rows in every cached conversation list (first pages, history\n * pages, searches) so per-row state (read receipts, titles) flips in the same\n * frame as the interaction instead of after the next refetch.\n */\nexport function patchConversationListCaches(\n queryClient: QueryClient,\n options: ConversationListCacheScope & {\n match: (item: ConversationSummary) => boolean;\n patch: Partial<ConversationSummary>;\n }\n): void {\n queryClient.setQueriesData<CachedConversationList>({ queryKey: scopedKey(options) }, (data) =>\n mapCachedPages(data, (page) => patchConversationsInPage(page, options.match, options.patch))\n );\n}\n\n/** Remove a thread's row from every cached conversation list in scope. */\nexport function removeConversationFromListCaches(\n queryClient: QueryClient,\n options: ConversationListCacheScope & { threadId: string }\n): void {\n queryClient.setQueriesData<CachedConversationList>({ queryKey: scopedKey(options) }, (data) =>\n mapCachedPages(data, (page) => removeConversationFromPage(page, options.threadId))\n );\n}\n\nfunction timestampMs(value: string): number {\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? 0 : parsed;\n}\n\nfunction sameSummary(a: ConversationSummary, b: ConversationSummary): boolean {\n return (\n a.threadId === b.threadId &&\n a.title === b.title &&\n a.updatedAt === b.updatedAt &&\n a.createdAt === b.createdAt &&\n a.state === b.state &&\n a.runtime === b.runtime &&\n a.sourceChannel === b.sourceChannel &&\n a.assetType === b.assetType &&\n a.assetId === b.assetId &&\n a.appId === b.appId &&\n a.triggerType === b.triggerType &&\n a.preview === b.preview &&\n a.messageCount === b.messageCount &&\n a.isUnread === b.isUnread &&\n a.sessionStatus === b.sessionStatus &&\n a.runStatus === b.runStatus &&\n a.attention === b.attention &&\n a.nextAction === b.nextAction &&\n a.attentionSource === b.attentionSource &&\n a.isFavorited === b.isFavorited &&\n // Never pushed by the index, so a fold carries the previous references\n // through unchanged; identity is the equality that matters here.\n a.lastWake === b.lastWake &&\n a.pullRequests === b.pullRequests\n );\n}\n\n/**\n * Project one replicated conversation-index row onto the shared summary shape.\n * Fields the index never carries (`createdAt`) and viewer-specific fields a\n * lifecycle publish cannot know (`is_unread: null`) keep the row's previous\n * value. Canonical status is taken from the entry when the publishing pod\n * reports it; a snapshot from a pod that predates the fields falls back to the\n * legacy `state` (null status) rather than keeping a stale run status.\n *\n * Attention fields (`attention`, `next_action`, `attention_source`,\n * `is_favorited`) are taken from the entry when the publishing pod reports\n * them, so a live push flips a row to \"needs you\" the way it renames it; a\n * pod that predates them leaves the row's previous values. Pull-request state\n * and the last wake are never pushed and always keep the fetched value.\n */\nexport function summaryFromConversationIndexEntry(\n entry: ConversationIndexEntry,\n previous?: ConversationSummary\n): ConversationSummary {\n const carriesStatus = entry.run_status !== undefined || entry.session_status !== undefined;\n return {\n threadId: entry.thread_id,\n title: entry.title,\n updatedAt: entry.updated_at,\n createdAt: previous?.createdAt ?? null,\n state: entry.state,\n runtime: entry.runtime,\n sourceChannel: entry.start_channel,\n assetType: entry.asset_type,\n assetId: entry.asset_id ?? previous?.assetId ?? null,\n computerAssetId: previous?.computerAssetId ?? null,\n parentSessionId: previous?.parentSessionId,\n subSessionCount: previous?.subSessionCount,\n appId: entry.app_id,\n triggerType: entry.trigger_type,\n preview: entry.last_message_preview,\n messageCount: entry.message_count,\n isUnread: entry.is_unread ?? previous?.isUnread ?? false,\n sessionStatus: carriesStatus ? (entry.session_status ?? null) : null,\n runStatus: carriesStatus ? (entry.run_status ?? null) : null,\n attention: entry.attention !== undefined ? entry.attention : previous?.attention,\n nextAction: entry.next_action !== undefined ? entry.next_action : previous?.nextAction,\n attentionSource:\n entry.attention_source !== undefined ? entry.attention_source : previous?.attentionSource,\n isFavorited: entry.is_favorited ?? previous?.isFavorited,\n lastWake: previous?.lastWake,\n pullRequests: previous?.pullRequests,\n };\n}\n\n/**\n * Fold the user's replicated conversation index (the newest-page window the\n * per-user statewire pushes) into a cached live first page, so a thread\n * created, renamed, finished, or read anywhere updates every list surface in\n * the same frame — without refetching the page.\n *\n * - Rows in the window are upserted (new threads appear, existing rows take\n * the pushed fields, see {@link summaryFromConversationIndexEntry}).\n * - Rows NOT in the window but at least as new as the window's oldest row\n * should have been in it, so they left the list (archived, hidden, shared\n * away) and are dropped. Older rows are beyond the window and are kept.\n * - When the index reports no further rows (`indexHasMore === false`) the\n * window IS the whole list, so every row outside it is dropped.\n *\n * Newest-first ordering is restored and the page identity is preserved when\n * nothing changed, so an equal push never notifies subscribers.\n */\nexport function mergeConversationIndexIntoPage(\n page: ConversationListPage,\n entries: readonly ConversationIndexEntry[],\n options: { indexHasMore: boolean }\n): ConversationListPage {\n const previousByThread = new Map(page.items.map((item) => [item.threadId, item]));\n const inWindow = new Set(entries.map((entry) => entry.thread_id));\n const windowTailMs =\n entries.length > 0 ? Math.min(...entries.map((entry) => timestampMs(entry.updated_at))) : null;\n\n const merged: ConversationSummary[] = entries.map((entry) => {\n const previous = previousByThread.get(entry.thread_id);\n const next = summaryFromConversationIndexEntry(entry, previous);\n return previous && sameSummary(previous, next) ? previous : next;\n });\n\n for (const item of page.items) {\n if (inWindow.has(item.threadId)) continue;\n if (!options.indexHasMore) continue;\n if (windowTailMs !== null && timestampMs(item.updatedAt) >= windowTailMs) continue;\n merged.push(item);\n }\n\n merged.sort((a, b) => timestampMs(b.updatedAt) - timestampMs(a.updatedAt));\n\n const unchanged =\n merged.length === page.items.length &&\n merged.every((item, index) => item === page.items[index]);\n if (unchanged) return page;\n\n return { ...page, items: merged };\n}\n"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversation-list.js","sources":["../../../../../agent-runtime/src/conversation-list.ts"],"sourcesContent":["import type { InfiniteData, QueryClient } from '@tanstack/react-query';\nimport type { ConversationIndexEntry } from './conversation-index';\n\n/**\n * What a session asks of the viewer, derived server-side from the run, the\n * parked interrupt, the pull requests it opened, its termination and its last\n * wake (the Devin-style session list). `needs_you` is the only state that\n * demands the viewer act; `working` is live; `ready` finished with something\n * to look at; `watching` waits on an external party (a review, a check);\n * `done` and `error` are terminal. A backend newer than this client may report\n * a value outside the union — render it as \"no attention\" rather than failing.\n */\nexport type ConversationAttention =\n | 'needs_you'\n | 'working'\n | 'ready'\n | 'watching'\n | 'done'\n | 'error';\n\nexport const CONVERSATION_ATTENTIONS: readonly ConversationAttention[] = [\n 'needs_you',\n 'working',\n 'ready',\n 'watching',\n 'done',\n 'error',\n];\n\n/** Which signal produced the attention state (and the `nextAction` copy). */\nexport type ConversationAttentionSource =\n | 'interrupt'\n | 'run'\n | 'pull_request'\n | 'termination'\n | 'wake'\n | 'handoff'\n | 'follow_up';\n\n/** The last event that woke a resting session (a GitHub webhook, a schedule, a handoff). */\nexport interface ConversationWake {\n source: string | null;\n reason: string | null;\n /** ISO 8601 timestamp of the wake. */\n at: string | null;\n}\n\n/**\n * Live GitHub state of a pull request the session opened, read by the server\n * when the list renders. Never pushed by the conversation index — it comes\n * from the list fetch only, so a fold keeps the fetched value.\n */\nexport interface ConversationPullRequest {\n /** `owner/repo` the pull request lives in. */\n repoFullName: string;\n number: number;\n url: string;\n title: string;\n state: 'OPEN' | 'CLOSED' | 'MERGED';\n isDraft: boolean;\n /** GitHub's review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED) or null. */\n reviewDecision: string | null;\n /** Combined state of the checks on the head commit. */\n checksState: 'SUCCESS' | 'FAILURE' | 'PENDING' | 'NONE';\n /** ISO 8601 timestamp of the pull request's last update on GitHub. */\n updatedAt: string;\n}\n\n/**\n * Shared conversation/thread LIST contract (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Every chat surface lists \"my conversations\" against the same underlying\n * session assets, but historically each declared its own row shape, paging\n * model, and filter set (Olympus GraphQL `listConversations`, the SDK/Chrome\n * REST `/api/conversations/threads/list`, mobile's `/api/v0/sessions`). This\n * module is the ONE contract those surfaces converge on:\n *\n * - **Row shape**: {@link ConversationSummary} — the canonical camelCase\n * superset. Fields a transport cannot supply are `null`, never invented.\n * - **Ordering**: newest first by `updatedAt`, always. Ordering is part of the\n * contract, not a parameter — no surface sorts any other way.\n * - **Paging**: offset/limit with a LIVE newest page and explicitly-fetched\n * frozen history pages (see `useConversationList`). `hasMore` continues the\n * scan; `totalCount` is advisory and may be `null` for transports that do\n * not count.\n * - **Filtering**: {@link ConversationListFilter}. `appId` follows\n * `docs/SESSION_APP_ID_GUIDE.md`: exact match when set (legacy `null`\n * sessions excluded), no filter when unset. A fetcher that cannot express a\n * requested filter field MUST throw rather than silently ignore it — a\n * dropped filter returns wrong rows under a cache key that claims otherwise.\n * - **Freshness**: the per-user conversation-index statewire\n * (`conversation-index.ts`) is the push signal; these queries are the data\n * source. Push handlers call {@link invalidateConversationListCaches}.\n */\nexport interface ConversationSummary {\n /** LangGraph thread id — the cross-surface conversation identity. */\n threadId: string;\n title: string;\n /** ISO 8601 timestamp of last update (the contract's sort key). */\n updatedAt: string;\n /** ISO 8601 creation timestamp, when the transport reports it. */\n createdAt: string | null;\n /** Session lifecycle state from athena_metadata (e.g. running, completed). */\n state: string | null;\n /** Agent runtime marker (e.g. 'athena_deep_agent'), when reported. */\n runtime: string | null;\n /** Channel the session was started from (e.g. agent_email, web). */\n sourceChannel: string | null;\n /** Asset type (session, video_session, desktop_session, mobile_session). */\n assetType: string | null;\n /** Session asset id (`asset_<uuid>`), when the transport reports it. */\n assetId: string | null;\n /** Client app that created the session (SESSION_APP_ID_GUIDE). */\n appId: string | null;\n /** Computer asset whose sandbox started the session (`sourceChannel` 'computer'); omitted by surfaces that don't carry it. */\n computerAssetId?: string | null;\n /** Session asset id this row is a sub-session of; omitted by surfaces that don't carry it. */\n parentSessionId?: string | null;\n /** Sub-sessions of this row the viewer can see; omitted by surfaces that don't carry it. */\n subSessionCount?: number;\n /** Trigger marker (`manual` remains user-initiated; other values are automated). */\n triggerType: string | null;\n /** Last-message preview used when a legacy session has no useful title. */\n preview: string | null;\n messageCount: number | null;\n /** Per-user unread flag derived server-side from the read receipt. */\n isUnread: boolean;\n /**\n * Canonical session status (status_v2: idle | active | needs_input | error),\n * or null when the transport does not report it.\n */\n sessionStatus: string | null;\n /**\n * Canonical status of the session's current run (status_v2: scheduled |\n * queued | running | needs_input | completed | failed | canceled). Null when\n * the transport does not report it — {@link isConversationRunning} then\n * falls back to the legacy `state`. Running badges on every surface derive\n * from this one helper so they cannot disagree.\n */\n runStatus: string | null;\n /**\n * Server-derived attention state. `undefined` when the transport (or an\n * older server) does not report it — {@link deriveConversationAttention}\n * then falls back to the status planes; `null` when the server reports none.\n */\n attention?: ConversationAttention | null;\n /** One line (≤ 80 chars) of what the viewer should do next, when the server derives one. */\n nextAction?: string | null;\n /** Which signal produced `attention` / `nextAction`. */\n attentionSource?: ConversationAttentionSource | null;\n /** Whether the viewer pinned (favorited) the session; `undefined` when not reported. */\n isFavorited?: boolean;\n /** The last wake of a resting session; `undefined` when not reported. */\n lastWake?: ConversationWake | null;\n /**\n * Pull requests the session opened with their live GitHub state. `[]` when\n * it opened none, `null` when the state could not be read right now,\n * `undefined` when the transport did not ask for (or report) it.\n */\n pullRequests?: ConversationPullRequest[] | null;\n}\n\n/**\n * The canonical filter set. Semantics are defined here once; each fetcher\n * translates the fields its transport supports and throws on the rest.\n */\nexport interface ConversationListFilter {\n /** Exact-match client app filter; unset = all apps (SESSION_APP_ID_GUIDE). */\n appId?: string;\n /** Asset types to include (e.g. ['session']); unset = all session types. */\n assetTypes?: readonly string[];\n /** Case-insensitive title search. */\n search?: string;\n /** Hide triggered/background sessions (REST thread list only today). */\n excludeTriggered?: boolean;\n /** Start-channel filter (v0 sessions / REST thread list only today). */\n sourceChannels?: readonly string[];\n /** Start channels to hide (e.g. ['api']); GraphQL `listConversations` only today. */\n excludeSourceChannels?: readonly string[];\n /** Exact computer asset whose sandbox started the session; GraphQL `listConversations` only today. */\n computerAssetId?: string;\n /** Only the sub-sessions of this session asset id; GraphQL `listConversations` only today. */\n parentSessionId?: string;\n /**\n * Lifecycle-state filter (e.g. ['running'], ['completed']). Transports match\n * it with the shared status_v2 rule (running carries a 12-hour recency cutoff).\n */\n states?: readonly string[];\n /** Restrict to sessions run by any of these agents (athena_metadata.agent). */\n agentIds?: readonly string[];\n /**\n * Include branched sub-sessions and orchestration children, which every list\n * hides by default because they belong under their parent thread.\n */\n includeSubSessions?: boolean;\n}\n\nexport interface ConversationListPage {\n /** Rows in contract order (updatedAt desc). */\n items: ConversationSummary[];\n /** Whether another `offset + limit` scan would return more rows. */\n hasMore: boolean;\n /** Total matching rows, or null when the transport does not count. */\n totalCount: number | null;\n}\n\nexport interface ConversationListFetchParams {\n limit: number;\n offset: number;\n filter: ConversationListFilter;\n signal?: AbortSignal;\n}\n\n/**\n * One page fetch against some transport (GraphQL `listConversations`, the\n * REST thread list, …). Must reject on filter fields it cannot express.\n */\nexport type ConversationListFetcher = (\n params: ConversationListFetchParams\n) => Promise<ConversationListPage>;\n\n/** Root of every conversation-list query key (all surfaces, all filters). */\nexport const CONVERSATION_LIST_QUERY_KEY_ROOT = 'athena-conversation-list';\n\n/**\n * Scope used by `useConversationList` when the caller passes no `scopeKey`.\n * Exported so cache seeders (e.g. Olympus's spacesBootstrap adoption) can\n * address the exact keys default-scoped hooks read from.\n */\nexport const CONVERSATION_LIST_DEFAULT_SCOPE_KEY = 'default';\n\n/**\n * Normalize a filter to its semantic content: unset/empty fields are dropped\n * (so `{}` and `{ appId: undefined }` are the same filter) and array order is\n * canonicalized. Fetchers receive the normalized form, so what a cache entry\n * claims to hold is exactly what was fetched.\n */\nexport function normalizeConversationListFilter(\n filter: ConversationListFilter\n): ConversationListFilter {\n const normalized: ConversationListFilter = {};\n if (filter.appId) normalized.appId = filter.appId;\n if (filter.assetTypes && filter.assetTypes.length > 0) {\n normalized.assetTypes = [...filter.assetTypes].sort();\n }\n if (filter.search) normalized.search = filter.search;\n if (filter.excludeTriggered !== undefined) normalized.excludeTriggered = filter.excludeTriggered;\n if (filter.sourceChannels && filter.sourceChannels.length > 0) {\n normalized.sourceChannels = [...filter.sourceChannels].sort();\n }\n if (filter.excludeSourceChannels && filter.excludeSourceChannels.length > 0) {\n normalized.excludeSourceChannels = [...filter.excludeSourceChannels].sort();\n }\n if (filter.computerAssetId) normalized.computerAssetId = filter.computerAssetId;\n if (filter.parentSessionId) normalized.parentSessionId = filter.parentSessionId;\n if (filter.states && filter.states.length > 0) {\n normalized.states = [...filter.states].sort();\n }\n if (filter.agentIds && filter.agentIds.length > 0) {\n normalized.agentIds = [...filter.agentIds].sort();\n }\n if (filter.includeSubSessions) normalized.includeSubSessions = true;\n return normalized;\n}\n\n/** status_v2 run statuses during which a session is actively working. */\nconst RUNNING_RUN_STATUSES: ReadonlySet<string> = new Set(['queued', 'running']);\n\n/**\n * THE running rule for every conversation-list surface (sidebar pane, recent\n * dropdown, workspace section, pushed index rows): the canonical status_v2 run\n * status when the transport reports one, else the legacy `state`. A session\n * paused on a human gate (`needs_input`) is not running.\n */\nexport function isConversationRunning(summary: {\n runStatus?: string | null;\n state?: string | null;\n}): boolean {\n if (summary.runStatus !== null && summary.runStatus !== undefined) {\n return RUNNING_RUN_STATUSES.has(summary.runStatus);\n }\n return summary.state === 'running';\n}\n\nconst KNOWN_ATTENTIONS: ReadonlySet<string> = new Set(CONVERSATION_ATTENTIONS);\n\n/**\n * THE attention rule for every session-list surface: the server-derived\n * `attention` when the transport reports one it knows, else a client fallback\n * from the status planes so an older server still yields the two states a\n * list cannot do without — a human gate reads as `needs_you`, a live run as\n * `working`, a failed run as `error`. `null` when nothing applies.\n */\nexport function deriveConversationAttention(\n summary: Pick<ConversationSummary, 'attention' | 'runStatus' | 'sessionStatus' | 'state'>\n): ConversationAttention | null {\n if (typeof summary.attention === 'string' && KNOWN_ATTENTIONS.has(summary.attention)) {\n return summary.attention;\n }\n if (summary.runStatus === 'needs_input' || summary.sessionStatus === 'needs_input') {\n return 'needs_you';\n }\n if (isConversationRunning(summary)) return 'working';\n if (summary.runStatus === 'failed' || summary.sessionStatus === 'error') return 'error';\n return null;\n}\n\n/**\n * Stable cache identity for a filter: the normalized fields serialized in\n * sorted key order, so semantically equal filters share one cache entry.\n */\nexport function conversationListFilterKey(filter: ConversationListFilter): string {\n const normalized = normalizeConversationListFilter(filter) as Record<string, unknown>;\n return JSON.stringify(\n Object.keys(normalized)\n .sort()\n .map((key) => [key, normalized[key]])\n );\n}\n\n/**\n * Query keys for the two-tier pager. `scopeKey` isolates caches beyond the\n * filter — surfaces MUST include everything that changes whose list this is\n * (principal/user id, backend base URL) so a credential swap can never read\n * another identity's rows out of the cache.\n *\n * `pageSize` is part of the key because it is baked into every cached page's\n * offset boundaries: two hooks sharing a scope and filter but paging by\n * different sizes would otherwise reuse each other's incompatible boundaries\n * and skip or duplicate rows.\n */\nexport const conversationListQueryKeys = {\n root: [CONVERSATION_LIST_QUERY_KEY_ROOT] as const,\n scope: (scopeKey: string) => [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey] as const,\n firstPage: (scopeKey: string, filterKey: string, pageSize: number) =>\n [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey, filterKey, pageSize, 'first-page'] as const,\n history: (scopeKey: string, filterKey: string, pageSize: number) =>\n [CONVERSATION_LIST_QUERY_KEY_ROOT, scopeKey, filterKey, pageSize, 'history'] as const,\n};\n\n/**\n * Flatten pages into one newest-first list, deduplicating by `threadId`.\n * The live first page wins over retained history copies of the same thread.\n */\nexport function mergeConversationPages(\n pages: readonly ConversationListPage[]\n): ConversationSummary[] {\n const seen = new Set<string>();\n const items: ConversationSummary[] = [];\n for (const page of pages) {\n for (const item of page.items) {\n if (seen.has(item.threadId)) continue;\n seen.add(item.threadId);\n items.push(item);\n }\n }\n return items;\n}\n\n/**\n * Prepend a conversation to a page unless its thread is already present\n * (optimistic \"new thread appears at the top\" write). Identity-preserving\n * no-op when present, so equal writes never notify subscribers.\n */\nexport function prependConversationToPage(\n page: ConversationListPage,\n item: ConversationSummary\n): ConversationListPage {\n if (page.items.some((existing) => existing.threadId === item.threadId)) return page;\n return {\n ...page,\n items: [item, ...page.items],\n totalCount: page.totalCount === null ? null : page.totalCount + 1,\n };\n}\n\n/** Remove a thread's row from a page; identity-preserving when absent. */\nexport function removeConversationFromPage(\n page: ConversationListPage,\n threadId: string\n): ConversationListPage {\n if (!page.items.some((item) => item.threadId === threadId)) return page;\n return { ...page, items: page.items.filter((item) => item.threadId !== threadId) };\n}\n\n/**\n * Patch every row matching `match`; identity-preserving when nothing matches\n * so cache writes cannot cause spurious re-renders.\n */\nexport function patchConversationsInPage(\n page: ConversationListPage,\n match: (item: ConversationSummary) => boolean,\n patch: Partial<ConversationSummary>\n): ConversationListPage {\n let changed = false;\n const items = page.items.map((item) => {\n if (!match(item)) return item;\n changed = true;\n return { ...item, ...patch };\n });\n return changed ? { ...page, items } : page;\n}\n\ntype CachedConversationList =\n | ConversationListPage\n | InfiniteData<ConversationListPage, number>\n | undefined;\n\nfunction isInfinitePages(\n data: ConversationListPage | InfiniteData<ConversationListPage, number>\n): data is InfiniteData<ConversationListPage, number> {\n return 'pages' in data;\n}\n\nfunction mapCachedPages(\n data: CachedConversationList,\n transform: (page: ConversationListPage) => ConversationListPage\n): CachedConversationList {\n if (!data) return data;\n if (isInfinitePages(data)) {\n let changed = false;\n const pages = data.pages.map((page) => {\n const next = transform(page);\n if (next !== page) changed = true;\n return next;\n });\n return changed ? { ...data, pages } : data;\n }\n return transform(data);\n}\n\ninterface ConversationListCacheScope {\n /** Restrict to one scope; omitted = every conversation-list cache. */\n scopeKey?: string;\n}\n\nfunction scopedKey(options?: ConversationListCacheScope): readonly string[] {\n return options?.scopeKey !== undefined\n ? conversationListQueryKeys.scope(options.scopeKey)\n : conversationListQueryKeys.root;\n}\n\n/**\n * Invalidate conversation-list caches (all filters, first page + history).\n * This is the hook for push signals — the conversation-index statewire, a\n * finished run, an archive/restore — anything that changed the server list.\n */\nexport function invalidateConversationListCaches(\n queryClient: QueryClient,\n options?: ConversationListCacheScope\n): Promise<void> {\n return queryClient.invalidateQueries({ queryKey: scopedKey(options) });\n}\n\n/** Cancel in-flight list fetches so an optimistic write cannot be overwritten. */\nexport function cancelConversationListCaches(\n queryClient: QueryClient,\n options?: ConversationListCacheScope\n): Promise<void> {\n return queryClient.cancelQueries({ queryKey: scopedKey(options) });\n}\n\n/**\n * Patch matching rows in every cached conversation list (first pages, history\n * pages, searches) so per-row state (read receipts, titles) flips in the same\n * frame as the interaction instead of after the next refetch.\n */\nexport function patchConversationListCaches(\n queryClient: QueryClient,\n options: ConversationListCacheScope & {\n match: (item: ConversationSummary) => boolean;\n patch: Partial<ConversationSummary>;\n }\n): void {\n queryClient.setQueriesData<CachedConversationList>({ queryKey: scopedKey(options) }, (data) =>\n mapCachedPages(data, (page) => patchConversationsInPage(page, options.match, options.patch))\n );\n}\n\n/** Remove a thread's row from every cached conversation list in scope. */\nexport function removeConversationFromListCaches(\n queryClient: QueryClient,\n options: ConversationListCacheScope & { threadId: string }\n): void {\n queryClient.setQueriesData<CachedConversationList>({ queryKey: scopedKey(options) }, (data) =>\n mapCachedPages(data, (page) => removeConversationFromPage(page, options.threadId))\n );\n}\n\nfunction timestampMs(value: string): number {\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? 0 : parsed;\n}\n\nfunction sameSummary(a: ConversationSummary, b: ConversationSummary): boolean {\n return (\n a.threadId === b.threadId &&\n a.title === b.title &&\n a.updatedAt === b.updatedAt &&\n a.createdAt === b.createdAt &&\n a.state === b.state &&\n a.runtime === b.runtime &&\n a.sourceChannel === b.sourceChannel &&\n a.assetType === b.assetType &&\n a.assetId === b.assetId &&\n a.appId === b.appId &&\n a.triggerType === b.triggerType &&\n a.preview === b.preview &&\n a.messageCount === b.messageCount &&\n a.isUnread === b.isUnread &&\n a.sessionStatus === b.sessionStatus &&\n a.runStatus === b.runStatus &&\n a.attention === b.attention &&\n a.nextAction === b.nextAction &&\n a.attentionSource === b.attentionSource &&\n a.isFavorited === b.isFavorited &&\n // Never pushed by the index, so a fold carries the previous references\n // through unchanged; identity is the equality that matters here.\n a.lastWake === b.lastWake &&\n a.pullRequests === b.pullRequests\n );\n}\n\n/**\n * Project one replicated conversation-index row onto the shared summary shape.\n * Fields the index never carries (`createdAt`) and viewer-specific fields a\n * lifecycle publish cannot know (`is_unread: null`) keep the row's previous\n * value. Canonical status is taken from the entry when the publishing pod\n * reports it; a snapshot from a pod that predates the fields falls back to the\n * legacy `state` (null status) rather than keeping a stale run status.\n *\n * Attention fields (`attention`, `next_action`, `attention_source`,\n * `is_favorited`) are taken from the entry when the publishing pod reports\n * them, so a live push flips a row to \"needs you\" the way it renames it; a\n * pod that predates them leaves the row's previous values. Pull-request state\n * and the last wake are never pushed and always keep the fetched value.\n */\nexport function summaryFromConversationIndexEntry(\n entry: ConversationIndexEntry,\n previous?: ConversationSummary\n): ConversationSummary {\n const carriesStatus = entry.run_status !== undefined || entry.session_status !== undefined;\n return {\n threadId: entry.thread_id,\n title: entry.title,\n updatedAt: entry.updated_at,\n createdAt: previous?.createdAt ?? null,\n state: entry.state,\n runtime: entry.runtime,\n sourceChannel: entry.start_channel,\n assetType: entry.asset_type,\n assetId: entry.asset_id ?? previous?.assetId ?? null,\n computerAssetId: previous?.computerAssetId ?? null,\n parentSessionId: previous?.parentSessionId,\n subSessionCount: previous?.subSessionCount,\n appId: entry.app_id,\n triggerType: entry.trigger_type,\n preview: entry.last_message_preview,\n messageCount: entry.message_count,\n isUnread: entry.is_unread ?? previous?.isUnread ?? false,\n sessionStatus: carriesStatus ? (entry.session_status ?? null) : null,\n runStatus: carriesStatus ? (entry.run_status ?? null) : null,\n attention: entry.attention !== undefined ? entry.attention : previous?.attention,\n nextAction: entry.next_action !== undefined ? entry.next_action : previous?.nextAction,\n attentionSource:\n entry.attention_source !== undefined ? entry.attention_source : previous?.attentionSource,\n isFavorited: entry.is_favorited ?? previous?.isFavorited,\n lastWake: previous?.lastWake,\n pullRequests: previous?.pullRequests,\n };\n}\n\n/**\n * Fold the user's replicated conversation index (the newest-page window the\n * per-user statewire pushes) into a cached live first page, so a thread\n * created, renamed, finished, or read anywhere updates every list surface in\n * the same frame — without refetching the page.\n *\n * - Rows in the window are upserted (new threads appear, existing rows take\n * the pushed fields, see {@link summaryFromConversationIndexEntry}).\n * - Rows NOT in the window but at least as new as the window's oldest row\n * should have been in it, so they left the list (archived, hidden, shared\n * away) and are dropped. Older rows are beyond the window and are kept.\n * - When the index reports no further rows (`indexHasMore === false`) the\n * window IS the whole list, so every row outside it is dropped.\n *\n * Newest-first ordering is restored and the page identity is preserved when\n * nothing changed, so an equal push never notifies subscribers.\n */\nexport function mergeConversationIndexIntoPage(\n page: ConversationListPage,\n entries: readonly ConversationIndexEntry[],\n options: { indexHasMore: boolean }\n): ConversationListPage {\n const previousByThread = new Map(page.items.map((item) => [item.threadId, item]));\n const inWindow = new Set(entries.map((entry) => entry.thread_id));\n const windowTailMs =\n entries.length > 0 ? Math.min(...entries.map((entry) => timestampMs(entry.updated_at))) : null;\n\n const merged: ConversationSummary[] = entries.map((entry) => {\n const previous = previousByThread.get(entry.thread_id);\n const next = summaryFromConversationIndexEntry(entry, previous);\n return previous && sameSummary(previous, next) ? previous : next;\n });\n\n for (const item of page.items) {\n if (inWindow.has(item.threadId)) continue;\n if (!options.indexHasMore) continue;\n if (windowTailMs !== null && timestampMs(item.updatedAt) >= windowTailMs) continue;\n merged.push(item);\n }\n\n merged.sort((a, b) => timestampMs(b.updatedAt) - timestampMs(a.updatedAt));\n\n const unchanged =\n merged.length === page.items.length &&\n merged.every((item, index) => item === page.items[index]);\n if (unchanged) return page;\n\n return { ...page, items: merged };\n}\n"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"deep-agent-transport-recipe.cjs","sources":["../../../../../agent-runtime/src/deep-agent-transport-recipe.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { Statewire, StatewireClient } from \"statewire\";\nimport {\n type DeepAgentMessageWindowDeclaration,\n withDeclaredMessageWindow,\n} from \"./message-window\";\nimport { withDeclaredProtocolVersion } from \"./protocol\";\nimport {\n attachRetryGate,\n type AttachRetryGate,\n createAttachInstrumentation,\n type DeepAgentAttachTimingEvent,\n type DeepAgentWakeRefusal,\n parseRetryAfterHeaderMs,\n} from \"./statewire-attach-observer\";\nimport {\n statewireTransport,\n type StatewireTransportOptions,\n} from \"./statewire-transport\";\n\n/** Auth-header resolver for the transport; `staleAuth` is true right after an\n * auth rejection, so resolvers that can force-renew should honor it. */\nexport type DeepAgentHeadersResolver = (ctx: {\n staleAuth: boolean;\n}) => Record<string, string> | Promise<Record<string, string>>;\n\n/** The statewire attach URL for a thread: the host's `…/v2/threads` mount plus the id. */\nexport function deepAgentThreadUrl({\n baseUrl,\n threadId,\n}: {\n baseUrl: string;\n threadId: string;\n}): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/${encodeURIComponent(threadId)}`;\n}\n\nexport interface DeepAgentAttachTransportOptions {\n /** Base URL of the deep-agent statewire host (the `…/v2/threads` mount). */\n baseUrl: string;\n threadId: string;\n headers: DeepAgentHeadersResolver;\n /** SSE when true, WebSocket otherwise. */\n sse?: boolean;\n /** A never-started chat: the transport makes no network calls until the first command send. */\n isNew?: boolean;\n /** Host session store (mobile); browser hosts mount `storage.session` on the thread instead. */\n sessionStore?: StatewireClient.SessionStorage | undefined;\n /** Observes every headers resolve (the diagnostics recorder's stale-auth seam). */\n onAuthResolve?: ((ctx: { staleAuth: boolean }) => void) | undefined;\n /** Attach timing + retry hints (see `statewire-attach-observer`). */\n onAttachTiming?: ((event: DeepAgentAttachTimingEvent) => void) | undefined;\n /** Dial floors from server retry hints; the process-wide gate by default. */\n retryGate?: AttachRetryGate;\n /**\n * The head window declared on the attach (`x-athena-deep-agent-window`,\n * see `message-window.ts`): the hello of a client the host does not\n * remember carries the newest `size` messages. Part of the recipe so a\n * pooled dial asks for the same shape as the thread's own attach — a pooled\n * hello then seeds the very state the attach will reconcile. Omitted, the\n * shared default is declared.\n */\n messageWindow?: DeepAgentMessageWindowDeclaration | undefined;\n}\n\n/**\n * The ONE recipe for a deep-agent thread transport — shared by the mounted\n * thread (`useDeepAgentThread`) and the prewarm pool, so a pre-dialed\n * connection is byte-for-byte the connection the thread would have opened\n * itself: same URL, same auth + wire-contract headers, same session lane, same\n * instrumentation.\n *\n * Every attach declares this build's wire-contract version alongside the\n * host's auth headers (first WS frame / SSE request headers) — the server\n * half of version negotiation reads it in `authorize`. The headers resolve\n * also waits out any server `Retry-After` floor for the thread: both statewire\n * attach paths resolve headers before they dial, so this is the seam where a\n * hint can hold the next dial without touching the library's backoff.\n */\nexport function createDeepAgentAttachTransport<TState = unknown>({\n baseUrl,\n threadId,\n headers,\n sse = false,\n isNew = false,\n sessionStore,\n onAuthResolve,\n onAttachTiming,\n retryGate = attachRetryGate,\n messageWindow,\n}: DeepAgentAttachTransportOptions): ResourceElement<\n Statewire<TState | undefined>\n> {\n const url = deepAgentThreadUrl({ baseUrl, threadId });\n const instrumentation = createAttachInstrumentation({\n url,\n sse,\n onEvent: onAttachTiming,\n retryGate,\n });\n const options: StatewireTransportOptions = {\n url,\n ...(isNew && { isNew: true }),\n headers: async (ctx: { staleAuth: boolean }) => {\n await retryGate.waitForDial(url);\n onAuthResolve?.(ctx);\n return withDeclaredMessageWindow(\n withDeclaredProtocolVersion(await headers(ctx)),\n messageWindow,\n );\n },\n ...(sessionStore && { sessionStore }),\n // Structural types: this package compiles without the DOM lib, while\n // statewire's option types name the browser `WebSocket`/`fetch`.\n ...(instrumentation.webSocket && {\n webSocket:\n instrumentation.webSocket as unknown as StatewireTransportOptions[\"webSocket\"],\n }),\n ...(instrumentation.fetch && {\n fetch:\n instrumentation.fetch as unknown as StatewireTransportOptions[\"fetch\"],\n }),\n };\n return statewireTransport<TState>(options, { sse });\n}\n\n/** What `POST …/prewarm` answered. */\nexport interface DeepAgentWakeResult {\n status: number;\n /** The host activated the thread and its history is resident. */\n warm: boolean;\n /**\n * `'warm'` when the history was already memoized, `'fresh_load'` when this\n * call loaded it; `null` when the host did not say (a refusal, or a host\n * without the route).\n */\n history: \"fresh_load\" | \"warm\" | null;\n /**\n * The dial floor recorded for the thread off a 503/429 `Retry-After` — the\n * header's value, except a 503 `\"worker\"` refusal, capped at\n * {@link WAKE_WORKER_REFUSAL_FLOOR_MS}.\n */\n retryAfterMs: number | null;\n /** Who refused a 503/429 (see {@link DeepAgentWakeRefusal}); `null` for any other answer. */\n refusal: DeepAgentWakeRefusal;\n durationMs: number;\n}\n\n/**\n * Dial floor for a 503 the pinned worker produced about one instance's\n * lifecycle. Pinned answers every retriable 503 with a blanket\n * `Retry-After: 1`, but what those name — an instance record mid-teardown\n * (\"instance closing; retry\": fence release + placement release, ≈100 ms), a\n * full activation queue — clears far sooner than that, and the thread's own\n * attach is the request that carries the next activation. Holding it a full\n * second traded a ≈100 ms gap for a ≈1 s stall on every refused wake. A 429\n * is rate limiting and keeps its hint whatever the body says.\n */\nexport const WAKE_WORKER_REFUSAL_FLOOR_MS = 250;\n\n/**\n * The pinboard's own placement-refusal vocabulary — the `error` strings of\n * `@assistant-ui/pinboard` (`src/refusals.ts` plus the two inline refusals in\n * `src/server.ts`). Mirrors `PINBOARD_PLACEMENT_REFUSAL_ERRORS` in\n * `iris/scaler/src/v2AccessLog.ts`, whose test pins it to the installed\n * library; keep the two lists identical. A body with any other `error`\n * string is `\"unknown\"`, never `\"placement\"`.\n */\nexport const PINBOARD_PLACEMENT_REFUSAL_ERRORS: ReadonlySet<string> = new Set([\n \"no live backends for namespace\",\n \"registry cold start, retry\",\n \"not the serving fleet\",\n \"no live workers for hostname\",\n \"hostname binding displaced, retry\",\n \"hostname bindings inconsistent, retry\",\n \"takeover in progress, retry\",\n \"placement held by another hostname\",\n \"worker inflight cap exceeded, retry\",\n \"worker refused activation, retry\",\n]);\n\n/**\n * Pinned's instance-lifecycle refusals (`pinned/hosting/directory.py`,\n * `pinned/hosting/_records.py`): each names one instance on the worker the\n * thread is already placed on and clears in the time a teardown or an\n * activation slot takes. Anything else the worker answers 503 with — \"Thread\n * history load failed\", \"The regional conversation store is temporarily\n * unavailable\", a lease that expired — is not about one instance and keeps\n * the full hint.\n */\nexport const PINNED_TRANSIENT_WORKER_REFUSALS: ReadonlySet<string> = new Set([\n \"instance closing; retry\",\n \"instance aborted\",\n \"activation queue full; retry\",\n \"host draining; retry\",\n \"instance capacity reached; retry\",\n \"instance past its wind-down deadline; retry\",\n]);\n\n/**\n * Classify a 503/429 body (see {@link DeepAgentWakeRefusal}): a known pinboard\n * `error` is `\"placement\"`, a known pinned `detail` is `\"worker\"`, everything\n * else — including unfamiliar strings in either field — is `\"unknown\"`.\n */\nexport function classifyWakeRefusal(\n body: unknown,\n): Exclude<DeepAgentWakeRefusal, null> {\n if (typeof body === \"object\" && body !== null) {\n const record = body as Record<string, unknown>;\n if (\n typeof record.error === \"string\" &&\n PINBOARD_PLACEMENT_REFUSAL_ERRORS.has(record.error)\n ) {\n return \"placement\";\n }\n if (\n typeof record.detail === \"string\" &&\n PINNED_TRANSIENT_WORKER_REFUSALS.has(record.detail)\n ) {\n return \"worker\";\n }\n }\n return \"unknown\";\n}\n\n/** Structural `fetch` for the wake call — this package compiles without the DOM lib. */\nexport type DeepAgentWakeFetch = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n keepalive?: boolean;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n}>;\n\nexport interface DeepAgentWakeOptions {\n /** Base URL of the deep-agent statewire host (the `…/v2/threads` mount). */\n baseUrl: string;\n threadId: string;\n headers: DeepAgentHeadersResolver;\n fetch?: DeepAgentWakeFetch | undefined;\n /** Dial floors from server retry hints; the process-wide gate by default. */\n retryGate?: AttachRetryGate;\n onAttachTiming?: ((event: DeepAgentAttachTimingEvent) => void) | undefined;\n now?: () => number;\n}\n\n/**\n * Wake a thread's server instance ahead of its attach. `POST …/prewarm` runs\n * the host's `authorize` and history load — the fence adopt, graph compile and\n * checkpoint read that make a cold attach's hello take ≈1 s — and answers with\n * no thread content, only whether the history was already resident. Fire it\n * on hover intent, before a pooled attach or the click's own dial, so the\n * activation overlaps the handshake and the attach that follows finds the\n * instance warm.\n *\n * Fire-and-forget by contract: every failure resolves `null`, and a host\n * without the route (404) is reported like any other non-OK answer. A 503/429\n * `Retry-After` is recorded as a dial floor for the thread, so the attach that\n * follows waits it out instead of bouncing — in full, except a 503 that names\n * one of pinned's instance-lifecycle refusals (one instance mid-teardown on\n * the worker the thread already has; see {@link DeepAgentWakeRefusal}), whose\n * floor is capped at {@link WAKE_WORKER_REFUSAL_FLOOR_MS}.\n */\nexport async function wakeDeepAgentThread({\n baseUrl,\n threadId,\n headers,\n fetch: fetchOverride,\n retryGate = attachRetryGate,\n onAttachTiming,\n now = () => Date.now(),\n}: DeepAgentWakeOptions): Promise<DeepAgentWakeResult | null> {\n const fetchImpl =\n fetchOverride ?? (globalThis as { fetch?: DeepAgentWakeFetch }).fetch;\n if (typeof fetchImpl !== \"function\") return null;\n const threadUrl = deepAgentThreadUrl({ baseUrl, threadId });\n const emit = (event: DeepAgentAttachTimingEvent): void => {\n try {\n onAttachTiming?.(event);\n } catch {\n // Observers are best-effort.\n }\n };\n const startedAt = now();\n try {\n const resolved = withDeclaredProtocolVersion(\n await headers({ staleAuth: false }),\n );\n const response = await fetchImpl(`${threadUrl}/prewarm`, {\n method: \"POST\",\n headers: resolved,\n keepalive: true,\n });\n const durationMs = now() - startedAt;\n let retryAfterMs: number | null = null;\n let refusal: DeepAgentWakeRefusal = null;\n if (response.status === 503 || response.status === 429) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n // A refusal without a readable body is classified by nothing.\n }\n refusal = classifyWakeRefusal(body);\n const hinted = parseRetryAfterHeaderMs(\n response.headers.get(\"retry-after\"),\n now,\n );\n if (hinted !== null) {\n retryAfterMs =\n response.status === 503 && refusal === \"worker\"\n ? Math.min(hinted, WAKE_WORKER_REFUSAL_FLOOR_MS)\n : hinted;\n if (retryAfterMs > 0) {\n retryGate.noteRetryAfter(threadUrl, retryAfterMs);\n emit({\n type: \"retry-after\",\n url: threadUrl,\n delayMs: retryAfterMs,\n source: \"http-header\",\n });\n }\n }\n }\n let warm = false;\n let history: DeepAgentWakeResult[\"history\"] = null;\n if (response.ok) {\n try {\n const body = (await response.json()) as {\n warm?: unknown;\n history?: unknown;\n } | null;\n warm = body?.warm === true;\n history =\n body?.history === \"warm\" || body?.history === \"fresh_load\"\n ? body.history\n : null;\n } catch {\n // A 200 without a JSON body still means the instance is up.\n warm = true;\n }\n }\n emit({\n type: \"wake\",\n url: threadUrl,\n status: response.status,\n durationMs,\n history,\n refusal,\n });\n return {\n status: response.status,\n warm,\n history,\n retryAfterMs,\n refusal,\n durationMs,\n };\n } catch {\n return null;\n }\n}\n"],"names":["attachRetryGate","messageWindow","createAttachInstrumentation","withDeclaredMessageWindow","withDeclaredProtocolVersion","statewireTransport"],"mappings":";;;;;;AA2BO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,QAAQ,CAAC;AACvE;AA4CO,SAAS,+BAAiD;AAAA,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAYA,wBAAAA;AAAAA,EAAA,eACZC;AACF,GAEE;AACA,QAAM,MAAM,mBAAmB,EAAE,SAAS,UAAU;AACpD,QAAM,kBAAkBC,wBAAAA,4BAA4B;AAAA,IAClD;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACD,QAAM,UAAqC;AAAA,IACzC;AAAA,IACA,GAAI,SAAS,EAAE,OAAO,KAAA;AAAA,IACtB,SAAS,OAAO,QAAgC;AAC9C,YAAM,UAAU,YAAY,GAAG;AAC/B,qDAAgB;AAChB,aAAOC,cAAAA;AAAAA,QACLC,qCAA4B,MAAM,QAAQ,GAAG,CAAC;AAAA,QAC9CH;AAAAA,MAAA;AAAA,IAEJ;AAAA,IACA,GAAI,gBAAgB,EAAE,aAAA;AAAA;AAAA;AAAA,IAGtB,GAAI,gBAAgB,aAAa;AAAA,MAC/B,WACE,gBAAgB;AAAA,IAAA;AAAA,IAEpB,GAAI,gBAAgB,SAAS;AAAA,MAC3B,OACE,gBAAgB;AAAA,IAAA;AAAA,EACpB;AAEF,SAAOI,sCAA2B,SAAS,EAAE,KAAK;AACpD;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"deep-agent-transport-recipe.js","sources":["../../../../../agent-runtime/src/deep-agent-transport-recipe.ts"],"sourcesContent":["import type { ResourceElement } from \"@assistant-ui/tap\";\nimport type { Statewire, StatewireClient } from \"statewire\";\nimport {\n type DeepAgentMessageWindowDeclaration,\n withDeclaredMessageWindow,\n} from \"./message-window\";\nimport { withDeclaredProtocolVersion } from \"./protocol\";\nimport {\n attachRetryGate,\n type AttachRetryGate,\n createAttachInstrumentation,\n type DeepAgentAttachTimingEvent,\n type DeepAgentWakeRefusal,\n parseRetryAfterHeaderMs,\n} from \"./statewire-attach-observer\";\nimport {\n statewireTransport,\n type StatewireTransportOptions,\n} from \"./statewire-transport\";\n\n/** Auth-header resolver for the transport; `staleAuth` is true right after an\n * auth rejection, so resolvers that can force-renew should honor it. */\nexport type DeepAgentHeadersResolver = (ctx: {\n staleAuth: boolean;\n}) => Record<string, string> | Promise<Record<string, string>>;\n\n/** The statewire attach URL for a thread: the host's `…/v2/threads` mount plus the id. */\nexport function deepAgentThreadUrl({\n baseUrl,\n threadId,\n}: {\n baseUrl: string;\n threadId: string;\n}): string {\n return `${baseUrl.replace(/\\/+$/, \"\")}/${encodeURIComponent(threadId)}`;\n}\n\nexport interface DeepAgentAttachTransportOptions {\n /** Base URL of the deep-agent statewire host (the `…/v2/threads` mount). */\n baseUrl: string;\n threadId: string;\n headers: DeepAgentHeadersResolver;\n /** SSE when true, WebSocket otherwise. */\n sse?: boolean;\n /** A never-started chat: the transport makes no network calls until the first command send. */\n isNew?: boolean;\n /** Host session store (mobile); browser hosts mount `storage.session` on the thread instead. */\n sessionStore?: StatewireClient.SessionStorage | undefined;\n /** Observes every headers resolve (the diagnostics recorder's stale-auth seam). */\n onAuthResolve?: ((ctx: { staleAuth: boolean }) => void) | undefined;\n /** Attach timing + retry hints (see `statewire-attach-observer`). */\n onAttachTiming?: ((event: DeepAgentAttachTimingEvent) => void) | undefined;\n /** Dial floors from server retry hints; the process-wide gate by default. */\n retryGate?: AttachRetryGate;\n /**\n * The head window declared on the attach (`x-athena-deep-agent-window`,\n * see `message-window.ts`): the hello of a client the host does not\n * remember carries the newest `size` messages. Part of the recipe so a\n * pooled dial asks for the same shape as the thread's own attach — a pooled\n * hello then seeds the very state the attach will reconcile. Omitted, the\n * shared default is declared.\n */\n messageWindow?: DeepAgentMessageWindowDeclaration | undefined;\n}\n\n/**\n * The ONE recipe for a deep-agent thread transport — shared by the mounted\n * thread (`useDeepAgentThread`) and the prewarm pool, so a pre-dialed\n * connection is byte-for-byte the connection the thread would have opened\n * itself: same URL, same auth + wire-contract headers, same session lane, same\n * instrumentation.\n *\n * Every attach declares this build's wire-contract version alongside the\n * host's auth headers (first WS frame / SSE request headers) — the server\n * half of version negotiation reads it in `authorize`. The headers resolve\n * also waits out any server `Retry-After` floor for the thread: both statewire\n * attach paths resolve headers before they dial, so this is the seam where a\n * hint can hold the next dial without touching the library's backoff.\n */\nexport function createDeepAgentAttachTransport<TState = unknown>({\n baseUrl,\n threadId,\n headers,\n sse = false,\n isNew = false,\n sessionStore,\n onAuthResolve,\n onAttachTiming,\n retryGate = attachRetryGate,\n messageWindow,\n}: DeepAgentAttachTransportOptions): ResourceElement<\n Statewire<TState | undefined>\n> {\n const url = deepAgentThreadUrl({ baseUrl, threadId });\n const instrumentation = createAttachInstrumentation({\n url,\n sse,\n onEvent: onAttachTiming,\n retryGate,\n });\n const options: StatewireTransportOptions = {\n url,\n ...(isNew && { isNew: true }),\n headers: async (ctx: { staleAuth: boolean }) => {\n await retryGate.waitForDial(url);\n onAuthResolve?.(ctx);\n return withDeclaredMessageWindow(\n withDeclaredProtocolVersion(await headers(ctx)),\n messageWindow,\n );\n },\n ...(sessionStore && { sessionStore }),\n // Structural types: this package compiles without the DOM lib, while\n // statewire's option types name the browser `WebSocket`/`fetch`.\n ...(instrumentation.webSocket && {\n webSocket:\n instrumentation.webSocket as unknown as StatewireTransportOptions[\"webSocket\"],\n }),\n ...(instrumentation.fetch && {\n fetch:\n instrumentation.fetch as unknown as StatewireTransportOptions[\"fetch\"],\n }),\n };\n return statewireTransport<TState>(options, { sse });\n}\n\n/** What `POST …/prewarm` answered. */\nexport interface DeepAgentWakeResult {\n status: number;\n /** The host activated the thread and its history is resident. */\n warm: boolean;\n /**\n * `'warm'` when the history was already memoized, `'fresh_load'` when this\n * call loaded it; `null` when the host did not say (a refusal, or a host\n * without the route).\n */\n history: \"fresh_load\" | \"warm\" | null;\n /**\n * The dial floor recorded for the thread off a 503/429 `Retry-After` — the\n * header's value, except a 503 `\"worker\"` refusal, capped at\n * {@link WAKE_WORKER_REFUSAL_FLOOR_MS}.\n */\n retryAfterMs: number | null;\n /** Who refused a 503/429 (see {@link DeepAgentWakeRefusal}); `null` for any other answer. */\n refusal: DeepAgentWakeRefusal;\n durationMs: number;\n}\n\n/**\n * Dial floor for a 503 the pinned worker produced about one instance's\n * lifecycle. Pinned answers every retriable 503 with a blanket\n * `Retry-After: 1`, but what those name — an instance record mid-teardown\n * (\"instance closing; retry\": fence release + placement release, ≈100 ms), a\n * full activation queue — clears far sooner than that, and the thread's own\n * attach is the request that carries the next activation. Holding it a full\n * second traded a ≈100 ms gap for a ≈1 s stall on every refused wake. A 429\n * is rate limiting and keeps its hint whatever the body says.\n */\nexport const WAKE_WORKER_REFUSAL_FLOOR_MS = 250;\n\n/**\n * The pinboard's own placement-refusal vocabulary — the `error` strings of\n * `@assistant-ui/pinboard` (`src/refusals.ts` plus the two inline refusals in\n * `src/server.ts`). Mirrors `PINBOARD_PLACEMENT_REFUSAL_ERRORS` in\n * `iris/scaler/src/v2AccessLog.ts`, whose test pins it to the installed\n * library; keep the two lists identical. A body with any other `error`\n * string is `\"unknown\"`, never `\"placement\"`.\n */\nexport const PINBOARD_PLACEMENT_REFUSAL_ERRORS: ReadonlySet<string> = new Set([\n \"no live backends for namespace\",\n \"registry cold start, retry\",\n \"not the serving fleet\",\n \"no live workers for hostname\",\n \"hostname binding displaced, retry\",\n \"hostname bindings inconsistent, retry\",\n \"takeover in progress, retry\",\n \"placement held by another hostname\",\n \"worker inflight cap exceeded, retry\",\n \"worker refused activation, retry\",\n]);\n\n/**\n * Pinned's instance-lifecycle refusals (`pinned/hosting/directory.py`,\n * `pinned/hosting/_records.py`): each names one instance on the worker the\n * thread is already placed on and clears in the time a teardown or an\n * activation slot takes. Anything else the worker answers 503 with — \"Thread\n * history load failed\", \"The regional conversation store is temporarily\n * unavailable\", a lease that expired — is not about one instance and keeps\n * the full hint.\n */\nexport const PINNED_TRANSIENT_WORKER_REFUSALS: ReadonlySet<string> = new Set([\n \"instance closing; retry\",\n \"instance aborted\",\n \"activation queue full; retry\",\n \"host draining; retry\",\n \"instance capacity reached; retry\",\n \"instance past its wind-down deadline; retry\",\n]);\n\n/**\n * Classify a 503/429 body (see {@link DeepAgentWakeRefusal}): a known pinboard\n * `error` is `\"placement\"`, a known pinned `detail` is `\"worker\"`, everything\n * else — including unfamiliar strings in either field — is `\"unknown\"`.\n */\nexport function classifyWakeRefusal(\n body: unknown,\n): Exclude<DeepAgentWakeRefusal, null> {\n if (typeof body === \"object\" && body !== null) {\n const record = body as Record<string, unknown>;\n if (\n typeof record.error === \"string\" &&\n PINBOARD_PLACEMENT_REFUSAL_ERRORS.has(record.error)\n ) {\n return \"placement\";\n }\n if (\n typeof record.detail === \"string\" &&\n PINNED_TRANSIENT_WORKER_REFUSALS.has(record.detail)\n ) {\n return \"worker\";\n }\n }\n return \"unknown\";\n}\n\n/** Structural `fetch` for the wake call — this package compiles without the DOM lib. */\nexport type DeepAgentWakeFetch = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n keepalive?: boolean;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n headers: { get(name: string): string | null };\n json(): Promise<unknown>;\n}>;\n\nexport interface DeepAgentWakeOptions {\n /** Base URL of the deep-agent statewire host (the `…/v2/threads` mount). */\n baseUrl: string;\n threadId: string;\n headers: DeepAgentHeadersResolver;\n fetch?: DeepAgentWakeFetch | undefined;\n /** Dial floors from server retry hints; the process-wide gate by default. */\n retryGate?: AttachRetryGate;\n onAttachTiming?: ((event: DeepAgentAttachTimingEvent) => void) | undefined;\n now?: () => number;\n}\n\n/**\n * Wake a thread's server instance ahead of its attach. `POST …/prewarm` runs\n * the host's `authorize` and history load — the fence adopt, graph compile and\n * checkpoint read that make a cold attach's hello take ≈1 s — and answers with\n * no thread content, only whether the history was already resident. Fire it\n * on hover intent, before a pooled attach or the click's own dial, so the\n * activation overlaps the handshake and the attach that follows finds the\n * instance warm.\n *\n * Fire-and-forget by contract: every failure resolves `null`, and a host\n * without the route (404) is reported like any other non-OK answer. A 503/429\n * `Retry-After` is recorded as a dial floor for the thread, so the attach that\n * follows waits it out instead of bouncing — in full, except a 503 that names\n * one of pinned's instance-lifecycle refusals (one instance mid-teardown on\n * the worker the thread already has; see {@link DeepAgentWakeRefusal}), whose\n * floor is capped at {@link WAKE_WORKER_REFUSAL_FLOOR_MS}.\n */\nexport async function wakeDeepAgentThread({\n baseUrl,\n threadId,\n headers,\n fetch: fetchOverride,\n retryGate = attachRetryGate,\n onAttachTiming,\n now = () => Date.now(),\n}: DeepAgentWakeOptions): Promise<DeepAgentWakeResult | null> {\n const fetchImpl =\n fetchOverride ?? (globalThis as { fetch?: DeepAgentWakeFetch }).fetch;\n if (typeof fetchImpl !== \"function\") return null;\n const threadUrl = deepAgentThreadUrl({ baseUrl, threadId });\n const emit = (event: DeepAgentAttachTimingEvent): void => {\n try {\n onAttachTiming?.(event);\n } catch {\n // Observers are best-effort.\n }\n };\n const startedAt = now();\n try {\n const resolved = withDeclaredProtocolVersion(\n await headers({ staleAuth: false }),\n );\n const response = await fetchImpl(`${threadUrl}/prewarm`, {\n method: \"POST\",\n headers: resolved,\n keepalive: true,\n });\n const durationMs = now() - startedAt;\n let retryAfterMs: number | null = null;\n let refusal: DeepAgentWakeRefusal = null;\n if (response.status === 503 || response.status === 429) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n // A refusal without a readable body is classified by nothing.\n }\n refusal = classifyWakeRefusal(body);\n const hinted = parseRetryAfterHeaderMs(\n response.headers.get(\"retry-after\"),\n now,\n );\n if (hinted !== null) {\n retryAfterMs =\n response.status === 503 && refusal === \"worker\"\n ? Math.min(hinted, WAKE_WORKER_REFUSAL_FLOOR_MS)\n : hinted;\n if (retryAfterMs > 0) {\n retryGate.noteRetryAfter(threadUrl, retryAfterMs);\n emit({\n type: \"retry-after\",\n url: threadUrl,\n delayMs: retryAfterMs,\n source: \"http-header\",\n });\n }\n }\n }\n let warm = false;\n let history: DeepAgentWakeResult[\"history\"] = null;\n if (response.ok) {\n try {\n const body = (await response.json()) as {\n warm?: unknown;\n history?: unknown;\n } | null;\n warm = body?.warm === true;\n history =\n body?.history === \"warm\" || body?.history === \"fresh_load\"\n ? body.history\n : null;\n } catch {\n // A 200 without a JSON body still means the instance is up.\n warm = true;\n }\n }\n emit({\n type: \"wake\",\n url: threadUrl,\n status: response.status,\n durationMs,\n history,\n refusal,\n });\n return {\n status: response.status,\n warm,\n history,\n retryAfterMs,\n refusal,\n durationMs,\n };\n } catch {\n return null;\n }\n}\n"],"names":[],"mappings":";;;;AA2BO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGW;AACT,SAAO,GAAG,QAAQ,QAAQ,QAAQ,EAAE,CAAC,IAAI,mBAAmB,QAAQ,CAAC;AACvE;AA4CO,SAAS,+BAAiD;AAAA,EAC/D;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAEE;AACA,QAAM,MAAM,mBAAmB,EAAE,SAAS,UAAU;AACpD,QAAM,kBAAkB,4BAA4B;AAAA,IAClD;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACD,QAAM,UAAqC;AAAA,IACzC;AAAA,IACA,GAAI,SAAS,EAAE,OAAO,KAAA;AAAA,IACtB,SAAS,OAAO,QAAgC;AAC9C,YAAM,UAAU,YAAY,GAAG;AAC/B,qDAAgB;AAChB,aAAO;AAAA,QACL,4BAA4B,MAAM,QAAQ,GAAG,CAAC;AAAA,QAC9C;AAAA,MAAA;AAAA,IAEJ;AAAA,IACA,GAAI,gBAAgB,EAAE,aAAA;AAAA;AAAA;AAAA,IAGtB,GAAI,gBAAgB,aAAa;AAAA,MAC/B,WACE,gBAAgB;AAAA,IAAA;AAAA,IAEpB,GAAI,gBAAgB,SAAS;AAAA,MAC3B,OACE,gBAAgB;AAAA,IAAA;AAAA,EACpB;AAEF,SAAO,mBAA2B,SAAS,EAAE,KAAK;AACpD;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"edit-command.cjs","sources":["../../../../../agent-runtime/src/edit-command.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { DEEP_AGENT_INTEREST_PROTOCOL } from './generated/runconfig-contract';\nimport {\n type DeepAgentMessageWindow,\n pageInterestRequests,\n readDeepAgentMessageWindow,\n} from './message-window';\nimport { hasStatewireThreadExtras } from './thread-extras';\n\n/**\n * Wire-accurate `run/edit` construction for deep-agent retry/edit, shared by\n * every surface that resubmits a user message.\n *\n * assistant-ui builds the command's `runAnchorMessageId` from the CONVERTED\n * thread messages (`resolveEditCommand`: `messages[index - 1].id`), but the\n * deep-agent host validates that anchor against the edited message's parent in\n * its RAW message list (`harness_sdk.linear_thread`: the entry before it in\n * `state[\"messages\"]`). Those two lists do not line up:\n *\n * - the converter chunks an assistant turn and its tool messages into ONE\n * thread message carrying only the FIRST wire id, so the anchor names the\n * head of the previous turn instead of its last tool message;\n * - `sanitizeLangChainMessages` drops hidden-from-frontend and unconvertible\n * entries, so a dropped tail message is invisible to the anchor too.\n *\n * Either way the host rejects the command with `wrong-anchor`\n * (`runAnchorMessageId … is not the source's parent`) AFTER the composer's\n * synchronous `send()` returned, so the retry/edit silently does nothing.\n *\n * Building the command here off the replicated raw state makes the anchor the\n * same value the host computes.\n *\n * The raw list is the loaded interest window, so the first loaded message's\n * parent is not in it while older messages exist — an edit there must not\n * anchor on `null` (the root, rejected `wrong-anchor`); a message below the\n * window has no parent on this client at all. {@link resolveDeepAgentEditCommand}\n * states older page interests until the parent is loaded.\n */\n\n/**\n * Host metadata frozen at submission, settling once the async capture does.\n * The message-metadata preparation surface — `prepareMessageMetadata`,\n * `discardPreparedMessageMetadata`, `sendPreparedEdit` and `sendCommand`'s\n * `preparedMetadata` option — is what the monorepo patches into\n * `@assistant-ui/react-statewire@0.29.1` (`patches/`). It is declared\n * structurally here rather than as `StatewireThread.Extras[...]` so that the\n * published sources also compile against the unpatched upstream typings, where\n * those members are absent and the edit path's optional calls never run.\n */\nexport type DeepAgentPreparedMessageMetadata = Promise<Record<string, unknown> | undefined>;\n\n/** The pieces of the statewire thread extras the edit path reads. */\nexport interface DeepAgentStatewireExtras {\n /** The library's dispatch; it resolves with the host's reply to the command. */\n sendCommand: (\n command: StatewireThread.Command,\n options?: { preparedMetadata?: DeepAgentPreparedMessageMetadata | undefined },\n ) => Promise<unknown> | void;\n /** Optional on older hosts; capture before loading an edit's history anchor. */\n prepareMessageMetadata?: (() => DeepAgentPreparedMessageMetadata) | undefined;\n discardPreparedMessageMetadata?:\n | ((prepared: DeepAgentPreparedMessageMetadata) => void)\n | undefined;\n sendPreparedEdit?:\n | ((sourceId: string, prepared: DeepAgentPreparedMessageMetadata) => void)\n | undefined;\n isPreparingSubmission?: boolean;\n /** The composed thread state — its `messages` are the raw wire messages of the loaded window. */\n state: { messages?: readonly { id?: unknown }[] } | null;\n /** The mounted documents (the interest window), absent on a wire without documents. */\n documents?: StatewireThread.Extras['documents'];\n invoke?: StatewireThread.Extras['invoke'];\n /** The live run, when one is in flight; `epoch` advances on each dispatch. */\n runs: { runId?: string | null; epoch?: number };\n /** The host's replicated run status; absent on a surface that predates it. */\n status?: StatewireThread.ThreadStatus;\n}\n\n/**\n * The statewire extras of the nearest aui thread, or `null` when the thread is\n * not a statewire thread (e.g. a read-only subagent transcript runtime).\n * Gated on {@link hasStatewireThreadExtras} for the same reason the approval\n * card is: the library's own hooks throw on a non-statewire thread.\n */\nexport function readDeepAgentStatewireExtras(extras: unknown): DeepAgentStatewireExtras | null {\n if (!hasStatewireThreadExtras(extras)) return null;\n return extras as DeepAgentStatewireExtras;\n}\n\n/**\n * The host's parent of `sourceId`: the raw wire message directly before it\n * (`null` when it is the first of the whole transcript). Returns `undefined`\n * when `sourceId` is not in the raw list at all — an optimistic bubble the\n * host has not settled yet, a message below the loaded window, or a surface\n * whose state this module cannot read — or when it is the first loaded\n * message while older ones exist (`hasOlder`), so the caller can load more or\n * fall back to the library's own path instead of asserting a wrong anchor.\n */\nexport function resolveWireAnchorMessageId({\n wireMessages,\n sourceId,\n hasOlder = false,\n}: {\n wireMessages: readonly { id?: unknown }[] | undefined;\n sourceId: string;\n hasOlder?: boolean;\n}): string | null | undefined {\n if (!wireMessages) return undefined;\n const index = wireMessages.findIndex((message) => message?.id === sourceId);\n if (index < 0) return undefined;\n if (index === 0) return hasOlder ? undefined : null;\n const parentId = wireMessages[index - 1]?.id;\n return typeof parentId === 'string' ? parentId : undefined;\n}\n\n/**\n * Whether `sourceId`'s parent lies below the loaded window: the message is\n * absent from the raw list, or its first entry, while older messages exist.\n * The caller loads older history before an edit can be anchored.\n */\nexport function isSourceBelowLoadedWindow({\n wireMessages,\n sourceId,\n window,\n}: {\n wireMessages: readonly { id?: unknown }[] | undefined;\n sourceId: string;\n window: DeepAgentMessageWindow | null;\n}): boolean {\n if (!wireMessages || window === null || !window.hasOlder) return false;\n const index = wireMessages.findIndex((message) => message?.id === sourceId);\n return index <= 0;\n}\n\nconst readWindow = (extras: DeepAgentStatewireExtras) =>\n readDeepAgentMessageWindow(extras.documents?.list());\n\n/**\n * A fresh command / message id (uniqueness, not secrecy). `crypto.randomUUID`\n * where the host runtime has it — every browser, Bun, Node and Hermes with\n * webcrypto — and a v4-shaped fallback elsewhere, so the shared path never\n * throws on a runtime without full webcrypto.\n */\nfunction mintCommandId(): string {\n const webcrypto = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (typeof webcrypto?.randomUUID === 'function') return webcrypto.randomUUID();\n const hex = (): string => Math.floor(Math.random() * 0x10000).toString(16).padStart(4, '0');\n return `${hex()}${hex()}-${hex()}-4${hex().slice(1)}-${hex()}-${hex()}${hex()}${hex()}`;\n}\n\n/**\n * The `run/edit` command for resubmitting `sourceId` with `text`, anchored on\n * the host's own parent. `null` when the anchor cannot be resolved from the\n * replicated state.\n */\nexport function buildDeepAgentEditCommand({\n extras,\n sourceId,\n text,\n}: {\n extras: DeepAgentStatewireExtras;\n sourceId: string;\n text: string;\n}): StatewireThread.RunEditCommand | null {\n const runAnchorMessageId = resolveWireAnchorMessageId({\n wireMessages: extras.state?.messages,\n sourceId,\n hasOlder: readWindow(extras)?.hasOlder ?? false,\n });\n if (runAnchorMessageId === undefined) return null;\n return {\n type: 'run/edit',\n runId: extras.runs.runId ?? mintCommandId(),\n message: { id: mintCommandId(), role: 'user', parts: [{ type: 'text', text }] },\n sourceId,\n runAnchorMessageId,\n };\n}\n\n/**\n * Pages of older history stated while looking for an edited message's host\n * parent. Each page is up to a window's worth of messages, so an edit reaches\n * this many windows below what was loaded before it gives up.\n */\nexport const MAX_EDIT_ANCHOR_HISTORY_LOADS = 5;\n\n/**\n * The anchored `run/edit` for `sourceId`, stating older page interests one at\n * a time when the message's parent lies below the loaded window. Every page\n * is read back from the live documents (`readExtras` — the host mounts them\n * before it answers the statement), so the anchor is always the host's own\n * parent id.\n *\n * Resolves `null` — the caller's cue for the library's composer path, whose\n * failure it reports — when the thread is not a statewire thread, the host has\n * not settled the message, the history is exhausted without it, or the page\n * budget ({@link MAX_EDIT_ANCHOR_HISTORY_LOADS}) is spent. A synchronous\n * resolution never awaits: the first read either anchors at once or decides\n * that pages are needed.\n */\nexport async function resolveDeepAgentEditCommand({\n readExtras,\n sourceId,\n text,\n pageSize,\n maxHistoryLoads = MAX_EDIT_ANCHOR_HISTORY_LOADS,\n}: {\n /** The live thread extras — re-read per page so each anchor attempt sees the widened window. */\n readExtras: () => DeepAgentStatewireExtras | null;\n sourceId: string;\n text: string;\n pageSize?: number;\n maxHistoryLoads?: number;\n}): Promise<StatewireThread.RunEditCommand | null> {\n for (let page = 0; page <= maxHistoryLoads; page += 1) {\n const extras = readExtras();\n if (!extras) return null;\n const command = buildDeepAgentEditCommand({ extras, sourceId, text });\n if (command) return command;\n if (page === maxHistoryLoads || !extras.invoke) return null;\n const window = readWindow(extras);\n if (!isSourceBelowLoadedWindow({ wireMessages: extras.state?.messages, sourceId, window })) {\n return null;\n }\n const requests = window === null ? null : pageInterestRequests(window, pageSize);\n if (requests === null) return null;\n await extras.invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [requests]);\n }\n return null;\n}\n"],"names":["hasStatewireThreadExtras","readDeepAgentMessageWindow","pageInterestRequests","DEEP_AGENT_INTEREST_PROTOCOL"],"mappings":";;;;;AAoFO,SAAS,6BAA6B,QAAkD;AAC7F,MAAI,CAACA,aAAAA,yBAAyB,MAAM,EAAG,QAAO;AAC9C,SAAO;AACT;AAWO,SAAS,2BAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAI8B;;AAC5B,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QAAQ,aAAa,UAAU,CAAC,aAAY,mCAAS,QAAO,QAAQ;AAC1E,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,UAAU,EAAG,QAAO,WAAW,SAAY;AAC/C,QAAM,YAAW,kBAAa,QAAQ,CAAC,MAAtB,mBAAyB;AAC1C,SAAO,OAAO,aAAa,WAAW,WAAW;AACnD;AAOO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,GAIY;AACV,MAAI,CAAC,gBAAgB,WAAW,QAAQ,CAAC,OAAO,SAAU,QAAO;AACjE,QAAM,QAAQ,aAAa,UAAU,CAAC,aAAY,mCAAS,QAAO,QAAQ;AAC1E,SAAO,SAAS;AAClB;AAEA,MAAM,aAAa,CAAC,WAAA;;AAClBC,uBAAAA,4BAA2B,YAAO,cAAP,mBAAkB,MAAM;AAAA;AAQrD,SAAS,gBAAwB;AAC/B,QAAM,YAAa,WAA0D;AAC7E,MAAI,QAAO,uCAAW,gBAAe,WAAY,QAAO,UAAU,WAAA;AAClE,QAAM,MAAM,MAAc,KAAK,MAAM,KAAK,OAAA,IAAW,KAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC1F,SAAO,GAAG,IAAA,CAAK,GAAG,IAAA,CAAK,IAAI,IAAA,CAAK,KAAK,MAAM,MAAM,CAAC,CAAC,IAAI,IAAA,CAAK,IAAI,KAAK,GAAG,IAAA,CAAK,GAAG,IAAA,CAAK;AACvF;AAOO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,GAI0C;;AACxC,QAAM,qBAAqB,2BAA2B;AAAA,IACpD,eAAc,YAAO,UAAP,mBAAc;AAAA,IAC5B;AAAA,IACA,YAAU,gBAAW,MAAM,MAAjB,mBAAoB,aAAY;AAAA,EAAA,CAC3C;AACD,MAAI,uBAAuB,OAAW,QAAO;AAC7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,OAAO,KAAK,SAAS,cAAA;AAAA,IAC5B,SAAS,EAAE,IAAI,cAAA,GAAiB,MAAM,QAAQ,OAAO,CAAC,EAAE,MAAM,QAAQ,KAAA,CAAM,EAAA;AAAA,IAC5E;AAAA,IACA;AAAA,EAAA;AAEJ;AAOO,MAAM,gCAAgC;AAgB7C,eAAsB,4BAA4B;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AACpB,GAOmD;;AACjD,WAAS,OAAO,GAAG,QAAQ,iBAAiB,QAAQ,GAAG;AACrD,UAAM,SAAS,WAAA;AACf,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,UAAU,0BAA0B,EAAE,QAAQ,UAAU,MAAM;AACpE,QAAI,QAAS,QAAO;AACpB,QAAI,SAAS,mBAAmB,CAAC,OAAO,OAAQ,QAAO;AACvD,UAAM,SAAS,WAAW,MAAM;AAChC,QAAI,CAAC,0BAA0B,EAAE,eAAc,YAAO,UAAP,mBAAc,UAAU,UAAU,OAAA,CAAQ,GAAG;AAC1F,aAAO;AAAA,IACT;AACA,UAAM,WAAW,WAAW,OAAO,OAAOC,cAAAA,qBAAqB,QAAQ,QAAQ;AAC/E,QAAI,aAAa,KAAM,QAAO;AAC9B,UAAM,OAAO,OAAOC,kBAAAA,8BAA8B,OAAO,CAAC,QAAQ,CAAC;AAAA,EACrE;AACA,SAAO;AACT;;;;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"edit-command.js","sources":["../../../../../agent-runtime/src/edit-command.ts"],"sourcesContent":["import type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { DEEP_AGENT_INTEREST_PROTOCOL } from './generated/runconfig-contract';\nimport {\n type DeepAgentMessageWindow,\n pageInterestRequests,\n readDeepAgentMessageWindow,\n} from './message-window';\nimport { hasStatewireThreadExtras } from './thread-extras';\n\n/**\n * Wire-accurate `run/edit` construction for deep-agent retry/edit, shared by\n * every surface that resubmits a user message.\n *\n * assistant-ui builds the command's `runAnchorMessageId` from the CONVERTED\n * thread messages (`resolveEditCommand`: `messages[index - 1].id`), but the\n * deep-agent host validates that anchor against the edited message's parent in\n * its RAW message list (`harness_sdk.linear_thread`: the entry before it in\n * `state[\"messages\"]`). Those two lists do not line up:\n *\n * - the converter chunks an assistant turn and its tool messages into ONE\n * thread message carrying only the FIRST wire id, so the anchor names the\n * head of the previous turn instead of its last tool message;\n * - `sanitizeLangChainMessages` drops hidden-from-frontend and unconvertible\n * entries, so a dropped tail message is invisible to the anchor too.\n *\n * Either way the host rejects the command with `wrong-anchor`\n * (`runAnchorMessageId … is not the source's parent`) AFTER the composer's\n * synchronous `send()` returned, so the retry/edit silently does nothing.\n *\n * Building the command here off the replicated raw state makes the anchor the\n * same value the host computes.\n *\n * The raw list is the loaded interest window, so the first loaded message's\n * parent is not in it while older messages exist — an edit there must not\n * anchor on `null` (the root, rejected `wrong-anchor`); a message below the\n * window has no parent on this client at all. {@link resolveDeepAgentEditCommand}\n * states older page interests until the parent is loaded.\n */\n\n/**\n * Host metadata frozen at submission, settling once the async capture does.\n * The message-metadata preparation surface — `prepareMessageMetadata`,\n * `discardPreparedMessageMetadata`, `sendPreparedEdit` and `sendCommand`'s\n * `preparedMetadata` option — is what the monorepo patches into\n * `@assistant-ui/react-statewire@0.29.1` (`patches/`). It is declared\n * structurally here rather than as `StatewireThread.Extras[...]` so that the\n * published sources also compile against the unpatched upstream typings, where\n * those members are absent and the edit path's optional calls never run.\n */\nexport type DeepAgentPreparedMessageMetadata = Promise<Record<string, unknown> | undefined>;\n\n/** The pieces of the statewire thread extras the edit path reads. */\nexport interface DeepAgentStatewireExtras {\n /** The library's dispatch; it resolves with the host's reply to the command. */\n sendCommand: (\n command: StatewireThread.Command,\n options?: { preparedMetadata?: DeepAgentPreparedMessageMetadata | undefined },\n ) => Promise<unknown> | void;\n /** Optional on older hosts; capture before loading an edit's history anchor. */\n prepareMessageMetadata?: (() => DeepAgentPreparedMessageMetadata) | undefined;\n discardPreparedMessageMetadata?:\n | ((prepared: DeepAgentPreparedMessageMetadata) => void)\n | undefined;\n sendPreparedEdit?:\n | ((sourceId: string, prepared: DeepAgentPreparedMessageMetadata) => void)\n | undefined;\n isPreparingSubmission?: boolean;\n /** The composed thread state — its `messages` are the raw wire messages of the loaded window. */\n state: { messages?: readonly { id?: unknown }[] } | null;\n /** The mounted documents (the interest window), absent on a wire without documents. */\n documents?: StatewireThread.Extras['documents'];\n invoke?: StatewireThread.Extras['invoke'];\n /** The live run, when one is in flight; `epoch` advances on each dispatch. */\n runs: { runId?: string | null; epoch?: number };\n /** The host's replicated run status; absent on a surface that predates it. */\n status?: StatewireThread.ThreadStatus;\n}\n\n/**\n * The statewire extras of the nearest aui thread, or `null` when the thread is\n * not a statewire thread (e.g. a read-only subagent transcript runtime).\n * Gated on {@link hasStatewireThreadExtras} for the same reason the approval\n * card is: the library's own hooks throw on a non-statewire thread.\n */\nexport function readDeepAgentStatewireExtras(extras: unknown): DeepAgentStatewireExtras | null {\n if (!hasStatewireThreadExtras(extras)) return null;\n return extras as DeepAgentStatewireExtras;\n}\n\n/**\n * The host's parent of `sourceId`: the raw wire message directly before it\n * (`null` when it is the first of the whole transcript). Returns `undefined`\n * when `sourceId` is not in the raw list at all — an optimistic bubble the\n * host has not settled yet, a message below the loaded window, or a surface\n * whose state this module cannot read — or when it is the first loaded\n * message while older ones exist (`hasOlder`), so the caller can load more or\n * fall back to the library's own path instead of asserting a wrong anchor.\n */\nexport function resolveWireAnchorMessageId({\n wireMessages,\n sourceId,\n hasOlder = false,\n}: {\n wireMessages: readonly { id?: unknown }[] | undefined;\n sourceId: string;\n hasOlder?: boolean;\n}): string | null | undefined {\n if (!wireMessages) return undefined;\n const index = wireMessages.findIndex((message) => message?.id === sourceId);\n if (index < 0) return undefined;\n if (index === 0) return hasOlder ? undefined : null;\n const parentId = wireMessages[index - 1]?.id;\n return typeof parentId === 'string' ? parentId : undefined;\n}\n\n/**\n * Whether `sourceId`'s parent lies below the loaded window: the message is\n * absent from the raw list, or its first entry, while older messages exist.\n * The caller loads older history before an edit can be anchored.\n */\nexport function isSourceBelowLoadedWindow({\n wireMessages,\n sourceId,\n window,\n}: {\n wireMessages: readonly { id?: unknown }[] | undefined;\n sourceId: string;\n window: DeepAgentMessageWindow | null;\n}): boolean {\n if (!wireMessages || window === null || !window.hasOlder) return false;\n const index = wireMessages.findIndex((message) => message?.id === sourceId);\n return index <= 0;\n}\n\nconst readWindow = (extras: DeepAgentStatewireExtras) =>\n readDeepAgentMessageWindow(extras.documents?.list());\n\n/**\n * A fresh command / message id (uniqueness, not secrecy). `crypto.randomUUID`\n * where the host runtime has it — every browser, Bun, Node and Hermes with\n * webcrypto — and a v4-shaped fallback elsewhere, so the shared path never\n * throws on a runtime without full webcrypto.\n */\nfunction mintCommandId(): string {\n const webcrypto = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (typeof webcrypto?.randomUUID === 'function') return webcrypto.randomUUID();\n const hex = (): string => Math.floor(Math.random() * 0x10000).toString(16).padStart(4, '0');\n return `${hex()}${hex()}-${hex()}-4${hex().slice(1)}-${hex()}-${hex()}${hex()}${hex()}`;\n}\n\n/**\n * The `run/edit` command for resubmitting `sourceId` with `text`, anchored on\n * the host's own parent. `null` when the anchor cannot be resolved from the\n * replicated state.\n */\nexport function buildDeepAgentEditCommand({\n extras,\n sourceId,\n text,\n}: {\n extras: DeepAgentStatewireExtras;\n sourceId: string;\n text: string;\n}): StatewireThread.RunEditCommand | null {\n const runAnchorMessageId = resolveWireAnchorMessageId({\n wireMessages: extras.state?.messages,\n sourceId,\n hasOlder: readWindow(extras)?.hasOlder ?? false,\n });\n if (runAnchorMessageId === undefined) return null;\n return {\n type: 'run/edit',\n runId: extras.runs.runId ?? mintCommandId(),\n message: { id: mintCommandId(), role: 'user', parts: [{ type: 'text', text }] },\n sourceId,\n runAnchorMessageId,\n };\n}\n\n/**\n * Pages of older history stated while looking for an edited message's host\n * parent. Each page is up to a window's worth of messages, so an edit reaches\n * this many windows below what was loaded before it gives up.\n */\nexport const MAX_EDIT_ANCHOR_HISTORY_LOADS = 5;\n\n/**\n * The anchored `run/edit` for `sourceId`, stating older page interests one at\n * a time when the message's parent lies below the loaded window. Every page\n * is read back from the live documents (`readExtras` — the host mounts them\n * before it answers the statement), so the anchor is always the host's own\n * parent id.\n *\n * Resolves `null` — the caller's cue for the library's composer path, whose\n * failure it reports — when the thread is not a statewire thread, the host has\n * not settled the message, the history is exhausted without it, or the page\n * budget ({@link MAX_EDIT_ANCHOR_HISTORY_LOADS}) is spent. A synchronous\n * resolution never awaits: the first read either anchors at once or decides\n * that pages are needed.\n */\nexport async function resolveDeepAgentEditCommand({\n readExtras,\n sourceId,\n text,\n pageSize,\n maxHistoryLoads = MAX_EDIT_ANCHOR_HISTORY_LOADS,\n}: {\n /** The live thread extras — re-read per page so each anchor attempt sees the widened window. */\n readExtras: () => DeepAgentStatewireExtras | null;\n sourceId: string;\n text: string;\n pageSize?: number;\n maxHistoryLoads?: number;\n}): Promise<StatewireThread.RunEditCommand | null> {\n for (let page = 0; page <= maxHistoryLoads; page += 1) {\n const extras = readExtras();\n if (!extras) return null;\n const command = buildDeepAgentEditCommand({ extras, sourceId, text });\n if (command) return command;\n if (page === maxHistoryLoads || !extras.invoke) return null;\n const window = readWindow(extras);\n if (!isSourceBelowLoadedWindow({ wireMessages: extras.state?.messages, sourceId, window })) {\n return null;\n }\n const requests = window === null ? null : pageInterestRequests(window, pageSize);\n if (requests === null) return null;\n await extras.invoke(DEEP_AGENT_INTEREST_PROTOCOL, 'set', [requests]);\n }\n return null;\n}\n"],"names":[],"mappings":";;;AAoFO,SAAS,6BAA6B,QAAkD;AAC7F,MAAI,CAAC,yBAAyB,MAAM,EAAG,QAAO;AAC9C,SAAO;AACT;AAWO,SAAS,2BAA2B;AAAA,EACzC;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAI8B;;AAC5B,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,QAAQ,aAAa,UAAU,CAAC,aAAY,mCAAS,QAAO,QAAQ;AAC1E,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,UAAU,EAAG,QAAO,WAAW,SAAY;AAC/C,QAAM,YAAW,kBAAa,QAAQ,CAAC,MAAtB,mBAAyB;AAC1C,SAAO,OAAO,aAAa,WAAW,WAAW;AACnD;AAOO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,GAIY;AACV,MAAI,CAAC,gBAAgB,WAAW,QAAQ,CAAC,OAAO,SAAU,QAAO;AACjE,QAAM,QAAQ,aAAa,UAAU,CAAC,aAAY,mCAAS,QAAO,QAAQ;AAC1E,SAAO,SAAS;AAClB;AAEA,MAAM,aAAa,CAAC,WAAA;;AAClB,qCAA2B,YAAO,cAAP,mBAAkB,MAAM;AAAA;AAQrD,SAAS,gBAAwB;AAC/B,QAAM,YAAa,WAA0D;AAC7E,MAAI,QAAO,uCAAW,gBAAe,WAAY,QAAO,UAAU,WAAA;AAClE,QAAM,MAAM,MAAc,KAAK,MAAM,KAAK,OAAA,IAAW,KAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC1F,SAAO,GAAG,IAAA,CAAK,GAAG,IAAA,CAAK,IAAI,IAAA,CAAK,KAAK,MAAM,MAAM,CAAC,CAAC,IAAI,IAAA,CAAK,IAAI,KAAK,GAAG,IAAA,CAAK,GAAG,IAAA,CAAK;AACvF;AAOO,SAAS,0BAA0B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AACF,GAI0C;;AACxC,QAAM,qBAAqB,2BAA2B;AAAA,IACpD,eAAc,YAAO,UAAP,mBAAc;AAAA,IAC5B;AAAA,IACA,YAAU,gBAAW,MAAM,MAAjB,mBAAoB,aAAY;AAAA,EAAA,CAC3C;AACD,MAAI,uBAAuB,OAAW,QAAO;AAC7C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,OAAO,KAAK,SAAS,cAAA;AAAA,IAC5B,SAAS,EAAE,IAAI,cAAA,GAAiB,MAAM,QAAQ,OAAO,CAAC,EAAE,MAAM,QAAQ,KAAA,CAAM,EAAA;AAAA,IAC5E;AAAA,IACA;AAAA,EAAA;AAEJ;AAOO,MAAM,gCAAgC;AAgB7C,eAAsB,4BAA4B;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AACpB,GAOmD;;AACjD,WAAS,OAAO,GAAG,QAAQ,iBAAiB,QAAQ,GAAG;AACrD,UAAM,SAAS,WAAA;AACf,QAAI,CAAC,OAAQ,QAAO;AACpB,UAAM,UAAU,0BAA0B,EAAE,QAAQ,UAAU,MAAM;AACpE,QAAI,QAAS,QAAO;AACpB,QAAI,SAAS,mBAAmB,CAAC,OAAO,OAAQ,QAAO;AACvD,UAAM,SAAS,WAAW,MAAM;AAChC,QAAI,CAAC,0BAA0B,EAAE,eAAc,YAAO,UAAP,mBAAc,UAAU,UAAU,OAAA,CAAQ,GAAG;AAC1F,aAAO;AAAA,IACT;AACA,UAAM,WAAW,WAAW,OAAO,OAAO,qBAAqB,QAAQ,QAAQ;AAC/E,QAAI,aAAa,KAAM,QAAO;AAC9B,UAAM,OAAO,OAAO,8BAA8B,OAAO,CAAC,QAAQ,CAAC;AAAA,EACrE;AACA,SAAO;AACT;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.cjs","sources":["../../../../../agent-runtime/src/errors.ts"],"sourcesContent":["import type { StatewireClient } from 'statewire';\nimport type { DeepAgentConnectionState } from './types';\n\n/** Structured payload attached to a server-rejected Statewire command. */\nexport interface StatewireRejectionPayload {\n status: number;\n detail: unknown;\n}\n\n/** Return a rejected command's structured `{ status, detail }` payload. */\nexport function statewireRejectionPayload(error: unknown): StatewireRejectionPayload | null {\n const failure = (error as { failure?: { fate?: unknown; payload?: unknown } } | null)?.failure;\n if (failure?.fate !== 'rejected') return null;\n\n const payload = failure.payload as { status?: unknown; detail?: unknown } | undefined;\n if (typeof payload?.status !== 'number') return null;\n return { status: payload.status, detail: payload.detail };\n}\n\n/**\n * The server's human-readable reason for a rejected command — `detail.message`\n * (or a plain-string `detail`) — else `null`. Hosts surface it as send\n * feedback so the user sees the actionable cause instead of the generic\n * client-side `command rejected (<status>)`.\n */\nexport function statewireRejectionDetailMessage(error: unknown): string | null {\n const detail = statewireRejectionPayload(error)?.detail;\n if (typeof detail === 'string') return detail.length > 0 ? detail : null;\n if (detail === null || typeof detail !== 'object' || Array.isArray(detail)) return null;\n const message = (detail as { message?: unknown }).message;\n return typeof message === 'string' && message.length > 0 ? message : null;\n}\n\n/**\n * A structured, retryable refusal the host recorded as the run error\n * (`runs.error`): the dispatch never started — the pod or instance was\n * closing (`pod_draining`) — and the entries stayed in their lanes for the\n * thread's next placement. Distinct from a rejected COMMAND\n * ({@link statewireRejectionPayload}): this is the shape harness-sdk writes\n * when the run executor raises the rejection, so it lives on the replicated\n * run entry, not on a command settle.\n */\nexport interface RetryableRunRefusal {\n /** The structured `detail.code` (`pod_draining`). */\n code: string;\n /** The server's human-readable reason, when it sent one. */\n message: string | null;\n}\n\n/** The `{ code, message, retryable: true }` detail of a structured refusal, or `null`. */\nfunction readRetryableDetail(detail: unknown): { code: string; message: string | null } | null {\n if (detail === null || typeof detail !== 'object' || Array.isArray(detail)) return null;\n const { code, message, retryable } = detail as {\n code?: unknown;\n message?: unknown;\n retryable?: unknown;\n };\n if (retryable !== true || typeof code !== 'string' || code.length === 0) return null;\n return { code, message: typeof message === 'string' && message.length > 0 ? message : null };\n}\n\nexport function readRetryableRunRefusal(error: unknown): RetryableRunRefusal | null {\n if (error === null || typeof error !== 'object' || Array.isArray(error)) return null;\n return readRetryableDetail((error as { detail?: unknown }).detail);\n}\n\n/**\n * A structured, retryable refusal of a COMMAND, read off its rejection\n * ({@link statewireRejectionPayload}): the host's `_structured_reject(...,\n * retryable=True)` envelope — `command_auth_context_expired` when a pod\n * replacement dropped the socket-scoped auth a replayed command needed,\n * `pod_draining` when the host was closing. The command never ran, and the\n * host itself says a resend after the reconnect will; `detail.code` is the\n * wire-frozen contract clients classify on. Same shape as\n * {@link readRetryableRunRefusal}, which reads it off a replicated run entry.\n */\nexport interface RetryableCommandRejection {\n /** The structured `detail.code`. */\n code: string;\n /** The server's human-readable reason, when it sent one. */\n message: string | null;\n}\n\nexport function readRetryableCommandRejection(error: unknown): RetryableCommandRejection | null {\n const payload = statewireRejectionPayload(error);\n return payload === null ? null : readRetryableDetail(payload.detail);\n}\n\n/** The host's `detail.code` for a command replayed onto a pod that could not restore its auth context. */\nexport const STATEWIRE_COMMAND_AUTH_CONTEXT_EXPIRED = 'command_auth_context_expired';\n\n/**\n * Whether a rejected command hit the host's fail-closed auth recovery: the\n * command was replayed onto a replacement pod that could not restore the\n * attach's auth context, so it was refused unrun (503, retryable). The\n * reconnect already under way restores the context; a single resend after it\n * is the recovery, not a failure to report.\n */\nexport function isStatewireCommandAuthContextExpired(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n return (\n payload?.status === 503 &&\n readRetryableDetail(payload.detail)?.code === STATEWIRE_COMMAND_AUTH_CONTEXT_EXPIRED\n );\n}\n\n/**\n * Whether a rejected command hit the legacy-thread dispatch fence: the\n * structured 409 the server answers when an out-of-band run is writing a\n * pre-cutover thread (or its adoption failed). A per-send failure — the\n * snapshot never carries a standing read-only flag.\n */\nexport function isLegacyThreadReadOnlyRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 409) return false;\n\n const detail = payload.detail as { code?: unknown } | undefined;\n return detail?.code === 'legacy_thread_read_only';\n}\n\n/**\n * Whether a rejected command was refused because its rewind anchor no longer\n * matches the host's parent for the edited message (`wrong-anchor`).\n *\n * A retry/edit resubmit is dispatched optimistically, so this rejection lands\n * after the UI already cleared its edit affordances: the turn simply never\n * re-runs. Callers use this to tell the user the resubmit was dropped instead\n * of leaving a console-only failure.\n */\nexport function isStatewireRewindAnchorRejection(error: unknown): boolean {\n const failure = (error as { failure?: { fate?: unknown; payload?: unknown } } | null | undefined)\n ?.failure;\n if (failure?.fate !== 'rejected') return false;\n return (failure.payload as { reason?: unknown } | undefined)?.reason === 'wrong-anchor';\n}\n\n/**\n * Whether a statewire send failure is the host's `expired` verdict\n * (`result-unavailable`): the command's seq was at or below the host's\n * admitted watermark and no retained result was left for it. Either the\n * command ran and settled but the client re-asked for the answer (a resend\n * after the stream came back) only after the host had let the retained result\n * go, or the seq was already admitted and the command was absorbed as a\n * duplicate without ever running — a client whose shared session record fell\n * behind the watermark stays there until a reconnect re-syncs it. The library\n * never reruns the command on replay, so for an idempotent read the answer is\n * simply gone — asking again is the correct reaction, not reporting a failure;\n * a write cannot assume it was applied.\n */\nexport function isStatewireExpiredVerdict(error: unknown): boolean {\n const failure = (error as { failure?: { fate?: unknown; cause?: unknown } | null | undefined })\n ?.failure;\n return failure?.fate === 'expired' && failure.cause === 'server';\n}\n\n/**\n * Whether a statewire send failure is a local channel death rather than a\n * command error.\n *\n * The library's contract for `SendFailure.cause`: `\"server\"` is the server's\n * own answer; every other cause is \"a channel death the client settled\n * locally\" — `unmount`, `stream-gone`, and `session-lost`. A thread switch\n * settles every in-flight command that way, so logging these at error level\n * reports two failures for an ordinary switch and buries the ones that mean\n * something.\n *\n * Channel health is already surfaced where it belongs: the connection\n * indicator renders the connection state. This\n * predicate only decides the log level.\n */\nexport function isStatewireChannelTeardown(error: unknown): boolean {\n const cause = (error as { failure?: { cause?: unknown } } | null | undefined)?.failure?.cause;\n return typeof cause === 'string' && cause !== 'server';\n}\n\n/**\n * The low-cardinality reason a statewire send settled as transport weather\n * rather than a defect, or `null` when the failure is a real error.\n *\n * Three families qualify: a channel death the client settled locally\n * ({@link isStatewireChannelTeardown} — the cause: `unmount`, `stream-gone`,\n * `session-lost`), the host's `expired` verdict\n * ({@link isStatewireExpiredVerdict} — the answer outlived the host's\n * retention before the post-reconnect resend collected it), and a retryable\n * structured rejection ({@link readRetryableCommandRejection} — its code: the\n * host asked for a resend). In every case the sender's own retry or poll is\n * the recovery and the connection indicator already shows the channel, so\n * error reporting counts the outcome instead of paging on it. Handler\n * failures, crashes, and non-retryable rejections stay `null`: those are\n * defects to page on.\n */\nexport function statewireTransportOutcome(error: unknown): string | null {\n const cause = (error as { failure?: { cause?: unknown } } | null | undefined)?.failure?.cause;\n if (typeof cause === 'string' && cause !== 'server') return cause;\n if (isStatewireExpiredVerdict(error)) return 'expired';\n return readRetryableCommandRejection(error)?.code ?? null;\n}\n\nconst sanitizeConnectionMessage = (message: string): string | undefined => {\n const printable = Array.from(message, (character) => {\n const code = character.charCodeAt(0);\n return code < 32 || code === 127 ? ' ' : character;\n })\n .join('')\n .replace(/\\s+/g, ' ')\n .trim();\n return printable ? printable.slice(0, 200) : undefined;\n};\n\n/** Preserve the Statewire lifecycle and sanitize host detail for display. */\nexport function projectDeepAgentConnection(\n connection: StatewireClient.ConnectionState\n): DeepAgentConnectionState {\n const message =\n 'message' in connection && typeof connection.message === 'string'\n ? sanitizeConnectionMessage(connection.message)\n : undefined;\n switch (connection.status) {\n case 'standby':\n return {\n status: connection.status,\n reason: connection.reason,\n degraded: connection.degraded,\n ...(message !== undefined && { message }),\n };\n case 'connecting':\n case 'connected':\n return { status: connection.status, degraded: connection.degraded };\n case 'retrying':\n return {\n status: connection.status,\n degraded: connection.degraded,\n attempt: connection.attempt,\n ...(connection.cause !== undefined && { cause: connection.cause }),\n ...(connection.code !== undefined && { code: connection.code }),\n ...(connection.nextRetryAt !== undefined && { nextRetryAt: connection.nextRetryAt }),\n ...(connection.lastError !== undefined && { lastError: connection.lastError }),\n ...(message !== undefined && { message }),\n };\n case 'stopped':\n return {\n status: connection.status,\n degraded: connection.degraded,\n reason: connection.reason,\n ...(connection.payload !== undefined && { payload: connection.payload }),\n ...(message !== undefined && { message }),\n };\n default:\n return unknownConnectionStatus(connection);\n }\n}\n\n/** Compile-time exhaustiveness: reachable only when the library grows a status this build does not project. */\nfunction unknownConnectionStatus(connection: never): never {\n throw new Error(\n `statewire: unknown connection status ${JSON.stringify(\n (connection as { status?: unknown }).status\n )}`\n );\n}\n"],"names":[],"mappings":"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"}