@flanksource/clicky-ui 0.3.46 → 0.3.47

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 (379) hide show
  1. package/NOTICE.md +11 -0
  2. package/dist/ai.cjs +13 -8
  3. package/dist/ai.cjs.map +1 -1
  4. package/dist/ai.js +7 -2
  5. package/dist/ai.js.map +1 -1
  6. package/dist/components/ErrorWrapper.cjs +2 -3
  7. package/dist/components/ErrorWrapper.cjs.map +1 -1
  8. package/dist/components/ErrorWrapper.d.ts.map +1 -1
  9. package/dist/components/ErrorWrapper.js +3 -4
  10. package/dist/components/ErrorWrapper.js.map +1 -1
  11. package/dist/data/Clicky.cjs +3 -0
  12. package/dist/data/Clicky.cjs.map +1 -1
  13. package/dist/data/Clicky.js +3 -0
  14. package/dist/data/Clicky.js.map +1 -1
  15. package/dist/data/FilterPill.cjs +15 -2
  16. package/dist/data/FilterPill.cjs.map +1 -1
  17. package/dist/data/FilterPill.d.ts +1 -1
  18. package/dist/data/FilterPill.d.ts.map +1 -1
  19. package/dist/data/FilterPill.js +15 -2
  20. package/dist/data/FilterPill.js.map +1 -1
  21. package/dist/data/LogViewer.cjs +27 -8
  22. package/dist/data/LogViewer.cjs.map +1 -1
  23. package/dist/data/LogViewer.d.ts +11 -3
  24. package/dist/data/LogViewer.d.ts.map +1 -1
  25. package/dist/data/LogViewer.js +27 -8
  26. package/dist/data/LogViewer.js.map +1 -1
  27. package/dist/data/Properties.cjs +2 -15
  28. package/dist/data/Properties.cjs.map +1 -1
  29. package/dist/data/Properties.d.ts +4 -0
  30. package/dist/data/Properties.d.ts.map +1 -1
  31. package/dist/data/Properties.js +2 -15
  32. package/dist/data/Properties.js.map +1 -1
  33. package/dist/data/PropertyValue.cjs +37 -0
  34. package/dist/data/PropertyValue.cjs.map +1 -0
  35. package/dist/data/PropertyValue.d.ts +6 -0
  36. package/dist/data/PropertyValue.d.ts.map +1 -0
  37. package/dist/data/PropertyValue.js +37 -0
  38. package/dist/data/PropertyValue.js.map +1 -0
  39. package/dist/data/Tree.cjs +4 -3
  40. package/dist/data/Tree.cjs.map +1 -1
  41. package/dist/data/Tree.d.ts +3 -1
  42. package/dist/data/Tree.d.ts.map +1 -1
  43. package/dist/data/Tree.js +4 -3
  44. package/dist/data/Tree.js.map +1 -1
  45. package/dist/data/ai/ChatFab.cjs +1 -1
  46. package/dist/data/ai/ChatFab.cjs.map +1 -1
  47. package/dist/data/ai/ChatFab.js +1 -1
  48. package/dist/data/ai/ChatFab.js.map +1 -1
  49. package/dist/data/ai/ChatWindow.cjs +106 -171
  50. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  51. package/dist/data/ai/ChatWindow.d.ts +6 -2
  52. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  53. package/dist/data/ai/ChatWindow.header.cjs +104 -0
  54. package/dist/data/ai/ChatWindow.header.cjs.map +1 -0
  55. package/dist/data/ai/ChatWindow.header.d.ts +29 -0
  56. package/dist/data/ai/ChatWindow.header.d.ts.map +1 -0
  57. package/dist/data/ai/ChatWindow.header.js +104 -0
  58. package/dist/data/ai/ChatWindow.header.js.map +1 -0
  59. package/dist/data/ai/ChatWindow.js +107 -172
  60. package/dist/data/ai/ChatWindow.js.map +1 -1
  61. package/dist/data/ai/ChatWindow.placement.cjs +95 -0
  62. package/dist/data/ai/ChatWindow.placement.cjs.map +1 -0
  63. package/dist/data/ai/ChatWindow.placement.d.ts +32 -0
  64. package/dist/data/ai/ChatWindow.placement.d.ts.map +1 -0
  65. package/dist/data/ai/ChatWindow.placement.js +95 -0
  66. package/dist/data/ai/ChatWindow.placement.js.map +1 -0
  67. package/dist/data/ai/SessionChatComposer.cjs +3 -1
  68. package/dist/data/ai/SessionChatComposer.cjs.map +1 -1
  69. package/dist/data/ai/SessionChatComposer.d.ts +4 -1
  70. package/dist/data/ai/SessionChatComposer.d.ts.map +1 -1
  71. package/dist/data/ai/SessionChatComposer.js +3 -1
  72. package/dist/data/ai/SessionChatComposer.js.map +1 -1
  73. package/dist/data/ai/SessionInspector.cjs +68 -72
  74. package/dist/data/ai/SessionInspector.cjs.map +1 -1
  75. package/dist/data/ai/SessionInspector.composer-slot.cjs +8 -0
  76. package/dist/data/ai/SessionInspector.composer-slot.cjs.map +1 -0
  77. package/dist/data/ai/SessionInspector.composer-slot.d.ts +9 -0
  78. package/dist/data/ai/SessionInspector.composer-slot.d.ts.map +1 -0
  79. package/dist/data/ai/SessionInspector.composer-slot.js +8 -0
  80. package/dist/data/ai/SessionInspector.composer-slot.js.map +1 -0
  81. package/dist/data/ai/SessionInspector.d.ts +6 -1
  82. package/dist/data/ai/SessionInspector.d.ts.map +1 -1
  83. package/dist/data/ai/SessionInspector.js +69 -73
  84. package/dist/data/ai/SessionInspector.js.map +1 -1
  85. package/dist/data/ai/SessionInspector.legacy.cjs +41 -42
  86. package/dist/data/ai/SessionInspector.legacy.cjs.map +1 -1
  87. package/dist/data/ai/SessionInspector.legacy.d.ts.map +1 -1
  88. package/dist/data/ai/SessionInspector.legacy.js +41 -42
  89. package/dist/data/ai/SessionInspector.legacy.js.map +1 -1
  90. package/dist/data/ai/SessionInspector.remote-view.cjs +14 -9
  91. package/dist/data/ai/SessionInspector.remote-view.cjs.map +1 -1
  92. package/dist/data/ai/SessionInspector.remote-view.d.ts +6 -2
  93. package/dist/data/ai/SessionInspector.remote-view.d.ts.map +1 -1
  94. package/dist/data/ai/SessionInspector.remote-view.js +14 -9
  95. package/dist/data/ai/SessionInspector.remote-view.js.map +1 -1
  96. package/dist/data/ai/SessionInspector.remote.cjs +84 -16
  97. package/dist/data/ai/SessionInspector.remote.cjs.map +1 -1
  98. package/dist/data/ai/SessionInspector.remote.d.ts +42 -7
  99. package/dist/data/ai/SessionInspector.remote.d.ts.map +1 -1
  100. package/dist/data/ai/SessionInspector.remote.js +86 -18
  101. package/dist/data/ai/SessionInspector.remote.js.map +1 -1
  102. package/dist/data/ai/SessionInspector.transcript.cjs +5 -0
  103. package/dist/data/ai/SessionInspector.transcript.cjs.map +1 -1
  104. package/dist/data/ai/SessionInspector.transcript.d.ts.map +1 -1
  105. package/dist/data/ai/SessionInspector.transcript.js +5 -0
  106. package/dist/data/ai/SessionInspector.transcript.js.map +1 -1
  107. package/dist/data/ai/SessionTail.cjs +63 -0
  108. package/dist/data/ai/SessionTail.cjs.map +1 -0
  109. package/dist/data/ai/SessionTail.d.ts +14 -0
  110. package/dist/data/ai/SessionTail.d.ts.map +1 -0
  111. package/dist/data/ai/SessionTail.js +63 -0
  112. package/dist/data/ai/SessionTail.js.map +1 -0
  113. package/dist/data/ai/SessionViewer.cjs +10 -3
  114. package/dist/data/ai/SessionViewer.cjs.map +1 -1
  115. package/dist/data/ai/SessionViewer.d.ts +7 -1
  116. package/dist/data/ai/SessionViewer.d.ts.map +1 -1
  117. package/dist/data/ai/SessionViewer.js +10 -3
  118. package/dist/data/ai/SessionViewer.js.map +1 -1
  119. package/dist/data/ai/SessionViewer.unified.d.ts +14 -0
  120. package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
  121. package/dist/data/ai/index.d.ts +4 -3
  122. package/dist/data/ai/index.d.ts.map +1 -1
  123. package/dist/data/ai/use-session-scroll.cjs +30 -6
  124. package/dist/data/ai/use-session-scroll.cjs.map +1 -1
  125. package/dist/data/ai/use-session-scroll.d.ts +7 -0
  126. package/dist/data/ai/use-session-scroll.d.ts.map +1 -1
  127. package/dist/data/ai/use-session-scroll.js +30 -6
  128. package/dist/data/ai/use-session-scroll.js.map +1 -1
  129. package/dist/data/chat/Chat.cjs +1 -1
  130. package/dist/data/chat/Chat.cjs.map +1 -1
  131. package/dist/data/chat/Chat.js +1 -1
  132. package/dist/data/chat/Chat.js.map +1 -1
  133. package/dist/data/chat/ChatRuntimeToolbar.cjs +4 -4
  134. package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
  135. package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
  136. package/dist/data/chat/ChatRuntimeToolbar.js +4 -4
  137. package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
  138. package/dist/data/chat/PromptInput.cjs +4 -2
  139. package/dist/data/chat/PromptInput.cjs.map +1 -1
  140. package/dist/data/chat/PromptInput.d.ts +5 -1
  141. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  142. package/dist/data/chat/PromptInput.js +4 -2
  143. package/dist/data/chat/PromptInput.js.map +1 -1
  144. package/dist/data/diagnostics/ErrorDetails.cjs +13 -2
  145. package/dist/data/diagnostics/ErrorDetails.cjs.map +1 -1
  146. package/dist/data/diagnostics/ErrorDetails.d.ts +3 -1
  147. package/dist/data/diagnostics/ErrorDetails.d.ts.map +1 -1
  148. package/dist/data/diagnostics/ErrorDetails.js +14 -3
  149. package/dist/data/diagnostics/ErrorDetails.js.map +1 -1
  150. package/dist/data/diagnostics/FrameSourceWindow.cjs +3 -2
  151. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
  152. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
  153. package/dist/data/diagnostics/FrameSourceWindow.js +3 -2
  154. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
  155. package/dist/data/diagnostics/error-diagnostics.cjs +5 -0
  156. package/dist/data/diagnostics/error-diagnostics.cjs.map +1 -1
  157. package/dist/data/diagnostics/error-diagnostics.d.ts +1 -0
  158. package/dist/data/diagnostics/error-diagnostics.d.ts.map +1 -1
  159. package/dist/data/diagnostics/error-diagnostics.js +6 -1
  160. package/dist/data/diagnostics/error-diagnostics.js.map +1 -1
  161. package/dist/data/har/HarPanel.cjs +3 -1
  162. package/dist/data/har/HarPanel.cjs.map +1 -1
  163. package/dist/data/har/HarPanel.d.ts +4 -1
  164. package/dist/data/har/HarPanel.d.ts.map +1 -1
  165. package/dist/data/har/HarPanel.js +3 -1
  166. package/dist/data/har/HarPanel.js.map +1 -1
  167. package/dist/data/test-runner/Expectation.cjs +57 -0
  168. package/dist/data/test-runner/Expectation.cjs.map +1 -0
  169. package/dist/data/test-runner/Expectation.d.ts +19 -0
  170. package/dist/data/test-runner/Expectation.d.ts.map +1 -0
  171. package/dist/data/test-runner/Expectation.js +57 -0
  172. package/dist/data/test-runner/Expectation.js.map +1 -0
  173. package/dist/data/test-runner/MarkIcons.cjs +38 -0
  174. package/dist/data/test-runner/MarkIcons.cjs.map +1 -0
  175. package/dist/data/test-runner/MarkIcons.d.ts +15 -0
  176. package/dist/data/test-runner/MarkIcons.d.ts.map +1 -0
  177. package/dist/data/test-runner/MarkIcons.js +38 -0
  178. package/dist/data/test-runner/MarkIcons.js.map +1 -0
  179. package/dist/data/test-runner/TestDetailPanel.cjs +74 -42
  180. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  181. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  182. package/dist/data/test-runner/TestDetailPanel.js +76 -44
  183. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  184. package/dist/data/test-runner/TestFailureDetail.cjs +23 -27
  185. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
  186. package/dist/data/test-runner/TestFailureDetail.d.ts +2 -2
  187. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
  188. package/dist/data/test-runner/TestFailureDetail.js +23 -27
  189. package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
  190. package/dist/data/test-runner/TestFilterBar.cjs +87 -93
  191. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -1
  192. package/dist/data/test-runner/TestFilterBar.d.ts +3 -2
  193. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -1
  194. package/dist/data/test-runner/TestFilterBar.js +89 -95
  195. package/dist/data/test-runner/TestFilterBar.js.map +1 -1
  196. package/dist/data/test-runner/TestRunSummary.cjs +35 -21
  197. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
  198. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
  199. package/dist/data/test-runner/TestRunSummary.js +35 -21
  200. package/dist/data/test-runner/TestRunSummary.js.map +1 -1
  201. package/dist/data/test-runner/TestRunner.cjs +52 -30
  202. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  203. package/dist/data/test-runner/TestRunner.d.ts +3 -2
  204. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  205. package/dist/data/test-runner/TestRunner.js +52 -30
  206. package/dist/data/test-runner/TestRunner.js.map +1 -1
  207. package/dist/data/test-runner/TestTree.cjs +5 -2
  208. package/dist/data/test-runner/TestTree.cjs.map +1 -1
  209. package/dist/data/test-runner/TestTree.d.ts +5 -2
  210. package/dist/data/test-runner/TestTree.d.ts.map +1 -1
  211. package/dist/data/test-runner/TestTree.js +5 -2
  212. package/dist/data/test-runner/TestTree.js.map +1 -1
  213. package/dist/data/test-runner/TestTreeNode.cjs +27 -19
  214. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  215. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  216. package/dist/data/test-runner/TestTreeNode.js +28 -20
  217. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  218. package/dist/data/test-runner/adapter.cjs +11 -0
  219. package/dist/data/test-runner/adapter.cjs.map +1 -1
  220. package/dist/data/test-runner/adapter.d.ts +18 -7
  221. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  222. package/dist/data/test-runner/adapter.js +11 -0
  223. package/dist/data/test-runner/adapter.js.map +1 -1
  224. package/dist/data/test-runner/copy.cjs +114 -0
  225. package/dist/data/test-runner/copy.cjs.map +1 -0
  226. package/dist/data/test-runner/copy.d.ts +47 -0
  227. package/dist/data/test-runner/copy.d.ts.map +1 -0
  228. package/dist/data/test-runner/copy.js +114 -0
  229. package/dist/data/test-runner/copy.js.map +1 -0
  230. package/dist/data/test-runner/index.d.ts +5 -2
  231. package/dist/data/test-runner/index.d.ts.map +1 -1
  232. package/dist/data/test-runner/marks.cjs +143 -0
  233. package/dist/data/test-runner/marks.cjs.map +1 -0
  234. package/dist/data/test-runner/marks.d.ts +30 -0
  235. package/dist/data/test-runner/marks.d.ts.map +1 -0
  236. package/dist/data/test-runner/marks.js +143 -0
  237. package/dist/data/test-runner/marks.js.map +1 -0
  238. package/dist/data/test-runner/status.cjs +27 -44
  239. package/dist/data/test-runner/status.cjs.map +1 -1
  240. package/dist/data/test-runner/status.d.ts +12 -3
  241. package/dist/data/test-runner/status.d.ts.map +1 -1
  242. package/dist/data/test-runner/status.js +27 -44
  243. package/dist/data/test-runner/status.js.map +1 -1
  244. package/dist/data/verification/adapterViews.cjs +16 -41
  245. package/dist/data/verification/adapterViews.cjs.map +1 -1
  246. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  247. package/dist/data/verification/adapterViews.js +16 -41
  248. package/dist/data/verification/adapterViews.js.map +1 -1
  249. package/dist/data/verification/adapters.cjs +115 -3
  250. package/dist/data/verification/adapters.cjs.map +1 -1
  251. package/dist/data/verification/adapters.d.ts.map +1 -1
  252. package/dist/data/verification/adapters.js +115 -3
  253. package/dist/data/verification/adapters.js.map +1 -1
  254. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  255. package/dist/data/verification/verifyReportTests.js.map +1 -1
  256. package/dist/data/version-info.cjs +3 -3
  257. package/dist/data/version-info.js +3 -3
  258. package/dist/data.cjs +44 -3
  259. package/dist/data.cjs.map +1 -1
  260. package/dist/data.d.ts +2 -0
  261. package/dist/data.d.ts.map +1 -1
  262. package/dist/data.js +45 -4
  263. package/dist/data.js.map +1 -1
  264. package/dist/icons/components/UiLogoEslint.cjs +10 -0
  265. package/dist/icons/components/UiLogoEslint.cjs.map +1 -0
  266. package/dist/icons/components/UiLogoEslint.d.ts +9 -0
  267. package/dist/icons/components/UiLogoEslint.d.ts.map +1 -0
  268. package/dist/icons/components/UiLogoEslint.js +10 -0
  269. package/dist/icons/components/UiLogoEslint.js.map +1 -0
  270. package/dist/icons/components/UiLogoGo.cjs +10 -0
  271. package/dist/icons/components/UiLogoGo.cjs.map +1 -0
  272. package/dist/icons/components/UiLogoGo.d.ts +9 -0
  273. package/dist/icons/components/UiLogoGo.d.ts.map +1 -0
  274. package/dist/icons/components/UiLogoGo.js +10 -0
  275. package/dist/icons/components/UiLogoGo.js.map +1 -0
  276. package/dist/icons/components/UiLogoJest.cjs +10 -0
  277. package/dist/icons/components/UiLogoJest.cjs.map +1 -0
  278. package/dist/icons/components/UiLogoJest.d.ts +9 -0
  279. package/dist/icons/components/UiLogoJest.d.ts.map +1 -0
  280. package/dist/icons/components/UiLogoJest.js +10 -0
  281. package/dist/icons/components/UiLogoJest.js.map +1 -0
  282. package/dist/icons/components/UiLogoMarkdown.cjs +10 -0
  283. package/dist/icons/components/UiLogoMarkdown.cjs.map +1 -0
  284. package/dist/icons/components/UiLogoMarkdown.d.ts +9 -0
  285. package/dist/icons/components/UiLogoMarkdown.d.ts.map +1 -0
  286. package/dist/icons/components/UiLogoMarkdown.js +10 -0
  287. package/dist/icons/components/UiLogoMarkdown.js.map +1 -0
  288. package/dist/icons/components/UiLogoOxc.cjs +10 -0
  289. package/dist/icons/components/UiLogoOxc.cjs.map +1 -0
  290. package/dist/icons/components/UiLogoOxc.d.ts +9 -0
  291. package/dist/icons/components/UiLogoOxc.d.ts.map +1 -0
  292. package/dist/icons/components/UiLogoOxc.js +10 -0
  293. package/dist/icons/components/UiLogoOxc.js.map +1 -0
  294. package/dist/icons/components/UiLogoPlaywright.cjs +10 -0
  295. package/dist/icons/components/UiLogoPlaywright.cjs.map +1 -0
  296. package/dist/icons/components/UiLogoPlaywright.d.ts +9 -0
  297. package/dist/icons/components/UiLogoPlaywright.d.ts.map +1 -0
  298. package/dist/icons/components/UiLogoPlaywright.js +10 -0
  299. package/dist/icons/components/UiLogoPlaywright.js.map +1 -0
  300. package/dist/icons/components/UiLogoPython.cjs +10 -0
  301. package/dist/icons/components/UiLogoPython.cjs.map +1 -0
  302. package/dist/icons/components/UiLogoPython.d.ts +9 -0
  303. package/dist/icons/components/UiLogoPython.d.ts.map +1 -0
  304. package/dist/icons/components/UiLogoPython.js +10 -0
  305. package/dist/icons/components/UiLogoPython.js.map +1 -0
  306. package/dist/icons/components/UiLogoReact.cjs +10 -0
  307. package/dist/icons/components/UiLogoReact.cjs.map +1 -0
  308. package/dist/icons/components/UiLogoReact.d.ts +9 -0
  309. package/dist/icons/components/UiLogoReact.d.ts.map +1 -0
  310. package/dist/icons/components/UiLogoReact.js +10 -0
  311. package/dist/icons/components/UiLogoReact.js.map +1 -0
  312. package/dist/icons/components/UiLogoRuff.cjs +10 -0
  313. package/dist/icons/components/UiLogoRuff.cjs.map +1 -0
  314. package/dist/icons/components/UiLogoRuff.d.ts +9 -0
  315. package/dist/icons/components/UiLogoRuff.d.ts.map +1 -0
  316. package/dist/icons/components/UiLogoRuff.js +10 -0
  317. package/dist/icons/components/UiLogoRuff.js.map +1 -0
  318. package/dist/icons/components/UiLogoTypescript.cjs +10 -0
  319. package/dist/icons/components/UiLogoTypescript.cjs.map +1 -0
  320. package/dist/icons/components/UiLogoTypescript.d.ts +9 -0
  321. package/dist/icons/components/UiLogoTypescript.d.ts.map +1 -0
  322. package/dist/icons/components/UiLogoTypescript.js +10 -0
  323. package/dist/icons/components/UiLogoTypescript.js.map +1 -0
  324. package/dist/icons/components/UiLogoVitest.cjs +10 -0
  325. package/dist/icons/components/UiLogoVitest.cjs.map +1 -0
  326. package/dist/icons/components/UiLogoVitest.d.ts +9 -0
  327. package/dist/icons/components/UiLogoVitest.d.ts.map +1 -0
  328. package/dist/icons/components/UiLogoVitest.js +10 -0
  329. package/dist/icons/components/UiLogoVitest.js.map +1 -0
  330. package/dist/icons/index.d.ts +11 -0
  331. package/dist/icons/index.d.ts.map +1 -1
  332. package/dist/icons/registry.cjs +22 -0
  333. package/dist/icons/registry.cjs.map +1 -1
  334. package/dist/icons/registry.d.ts.map +1 -1
  335. package/dist/icons/registry.js +22 -0
  336. package/dist/icons/registry.js.map +1 -1
  337. package/dist/icons.cjs +22 -0
  338. package/dist/icons.cjs.map +1 -1
  339. package/dist/icons.js +22 -0
  340. package/dist/icons.js.map +1 -1
  341. package/dist/index.cjs +44 -3
  342. package/dist/index.cjs.map +1 -1
  343. package/dist/index.js +45 -4
  344. package/dist/index.js.map +1 -1
  345. package/dist/overlay/DropdownMenuSubmenu.cjs +4 -4
  346. package/dist/overlay/DropdownMenuSubmenu.cjs.map +1 -1
  347. package/dist/overlay/DropdownMenuSubmenu.js +4 -4
  348. package/dist/overlay/DropdownMenuSubmenu.js.map +1 -1
  349. package/dist/rpc/InlineError.cjs +12 -43
  350. package/dist/rpc/InlineError.cjs.map +1 -1
  351. package/dist/rpc/InlineError.d.ts.map +1 -1
  352. package/dist/rpc/InlineError.js +13 -44
  353. package/dist/rpc/InlineError.js.map +1 -1
  354. package/dist/rpc/OperationScheduleDialog.cjs +63 -10
  355. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  356. package/dist/rpc/OperationScheduleDialog.d.ts +4 -0
  357. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  358. package/dist/rpc/OperationScheduleDialog.js +63 -10
  359. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  360. package/dist/rpc/OperationSchedules.cjs +12 -2
  361. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  362. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  363. package/dist/rpc/OperationSchedules.js +12 -2
  364. package/dist/rpc/OperationSchedules.js.map +1 -1
  365. package/dist/rpc/operation-schedule-model.cjs +2 -0
  366. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  367. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  368. package/dist/rpc/operation-schedule-model.js +2 -0
  369. package/dist/rpc/operation-schedule-model.js.map +1 -1
  370. package/dist/rpc/operation-schedule-types.d.ts +7 -1
  371. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  372. package/dist/styles.css +1 -1
  373. package/package.json +1 -1
  374. package/dist/data/test-runner/frameworkIcon.cjs +0 -27
  375. package/dist/data/test-runner/frameworkIcon.cjs.map +0 -1
  376. package/dist/data/test-runner/frameworkIcon.d.ts +0 -3
  377. package/dist/data/test-runner/frameworkIcon.d.ts.map +0 -1
  378. package/dist/data/test-runner/frameworkIcon.js +0 -27
  379. package/dist/data/test-runner/frameworkIcon.js.map +0 -1
@@ -13,12 +13,15 @@ const TERMINAL_SESSION_LIFECYCLES = [
13
13
  function isTerminalSessionLifecycle(status) {
14
14
  return status !== void 0 && TERMINAL_SESSION_LIFECYCLES.includes(status);
15
15
  }
16
- function sessionFollowUrl(src) {
16
+ const DEFAULT_SESSION_PAGE_SIZE = 100;
17
+ function sessionUrl(src, params) {
17
18
  const hashAt = src.indexOf("#");
18
19
  const base = hashAt === -1 ? src : src.slice(0, hashAt);
19
20
  const hash = hashAt === -1 ? "" : src.slice(hashAt);
20
- const separator = !base.includes("?") ? "?" : base.endsWith("?") || base.endsWith("&") ? "" : "&";
21
- return `${base}${separator}follow=1${hash}`;
21
+ const queryAt = base.indexOf("?");
22
+ const query = new URLSearchParams(queryAt === -1 ? "" : base.slice(queryAt + 1));
23
+ for (const [name, value] of Object.entries(params)) query.set(name, String(value));
24
+ return `${queryAt === -1 ? base : base.slice(0, queryAt)}?${query}${hash}`;
22
25
  }
23
26
  async function fetchRemoteSession(src, signal) {
24
27
  const response = await fetch(src, {
@@ -47,7 +50,8 @@ async function fetchRemoteSession(src, signal) {
47
50
  return parsed;
48
51
  }
49
52
  function mergeSessionMessages(base, incoming) {
50
- const merged = [...base ?? []];
53
+ const real = incoming.some((message) => !message.placeholder);
54
+ const merged = (base ?? []).filter((message) => !(real && message.placeholder));
51
55
  for (const message of incoming) {
52
56
  const index = merged.findIndex((existing) => existing.id === message.id);
53
57
  if (index === -1) merged.push(message);
@@ -57,10 +61,13 @@ function mergeSessionMessages(base, incoming) {
57
61
  }
58
62
  function openRemoteSession(src, {
59
63
  follow,
64
+ pageSize = DEFAULT_SESSION_PAGE_SIZE,
60
65
  eventSourceFactory,
61
66
  onChange
62
67
  }) {
63
- const url = sessionFollowUrl(src);
68
+ let url = sessionUrl(src, { follow: 1 });
69
+ let olderPage;
70
+ let pageError;
64
71
  const aborter = new AbortController();
65
72
  const streamed = /* @__PURE__ */ new Map();
66
73
  let closed = false;
@@ -84,23 +91,51 @@ function openRemoteSession(src, {
84
91
  emit({ ...state, loading: false, error: message });
85
92
  };
86
93
  const refresh = async () => {
87
- var _a;
94
+ var _a, _b, _c, _d;
88
95
  const current = ++generation;
89
96
  const startedAt = seq;
90
- const fetched = await fetchRemoteSession(src, aborter.signal);
97
+ const fetched = await fetchRemoteSession(sessionUrl(src, { tail: pageSize }), aborter.signal);
91
98
  if (closed || current !== generation) return void 0;
92
99
  const fetchedIds = new Set((_a = fetched.messages) == null ? void 0 : _a.map((message) => message.id));
93
100
  const keep = [...streamed.values()].filter((entry) => entry.seq > startedAt || !fetchedIds.has(entry.message.id)).map((entry) => entry.message);
101
+ const held = mergeSessionMessages((_b = state.session) == null ? void 0 : _b.messages, fetched.messages ?? []);
94
102
  const session = {
95
103
  ...fetched,
96
- messages: mergeSessionMessages(fetched.messages, keep)
104
+ messages: mergeSessionMessages(held, keep)
97
105
  };
98
106
  if (typeof fetched.revision === "number") {
99
107
  revision = Math.max(revision ?? fetched.revision, fetched.revision);
100
108
  }
101
- emit({ ...state, session, loading: false });
109
+ const hasOlder = state.session === void 0 ? (((_c = fetched.window) == null ? void 0 : _c.messages) ?? 0) > (((_d = fetched.messages) == null ? void 0 : _d.length) ?? 0) : state.hasOlder;
110
+ emit({ ...state, session, loading: false, hasOlder: hasOlder ?? false });
102
111
  return session;
103
112
  };
113
+ const loadOlder = () => {
114
+ var _a, _b, _c;
115
+ const oldest = (_c = (_b = (_a = state.session) == null ? void 0 : _a.messages) == null ? void 0 : _b[0]) == null ? void 0 : _c.id;
116
+ if (closed || olderPage || !state.hasOlder || !oldest) return olderPage ?? Promise.resolve();
117
+ emit({ ...state, loadingOlder: true });
118
+ olderPage = fetchRemoteSession(sessionUrl(src, { before: oldest, limit: pageSize }), aborter.signal).then((page) => {
119
+ const older = page.messages ?? [];
120
+ const { error, ...rest } = state;
121
+ const session = state.session;
122
+ emit({
123
+ ...rest,
124
+ ...error !== void 0 && error !== pageError ? { error } : {},
125
+ loadingOlder: false,
126
+ hasOlder: older.length === pageSize,
127
+ session: { ...session, messages: mergeSessionMessages(older, session.messages ?? []) }
128
+ });
129
+ pageError = void 0;
130
+ }).catch((reason) => {
131
+ if (closed) return;
132
+ pageError = errorMessage(reason);
133
+ emit({ ...state, loadingOlder: false, error: pageError });
134
+ }).finally(() => {
135
+ olderPage = void 0;
136
+ });
137
+ return olderPage;
138
+ };
104
139
  const readFrame = (event) => {
105
140
  try {
106
141
  return JSON.parse(event.data);
@@ -152,6 +187,9 @@ function openRemoteSession(src, {
152
187
  if (isTerminalSessionLifecycle(next.lifecycleStatus)) stopFollowing();
153
188
  };
154
189
  const startFollowing = () => {
190
+ var _a, _b, _c;
191
+ const newest = (_c = (_b = (_a = state.session) == null ? void 0 : _a.messages) == null ? void 0 : _b.filter((message) => !message.placeholder).at(-1)) == null ? void 0 : _c.id;
192
+ url = sessionUrl(src, newest ? { follow: 1, after: newest } : { follow: 1 });
155
193
  const es = eventSourceFactory(url);
156
194
  source = es;
157
195
  let received = false;
@@ -194,6 +232,7 @@ function openRemoteSession(src, {
194
232
  );
195
233
  return {
196
234
  loaded,
235
+ loadOlder,
197
236
  close: () => {
198
237
  closed = true;
199
238
  aborter.abort();
@@ -203,24 +242,51 @@ function openRemoteSession(src, {
203
242
  }
204
243
  const IDLE = { loading: false };
205
244
  const LOADING = { loading: true };
206
- function useRemoteSession(src, { follow } = {}) {
245
+ function useRemoteSession(src, { follow, pageSize } = {}) {
207
246
  const eventSourceFactory = eventSource.useEventSourceFactory();
208
247
  const factoryRef = react.useRef(eventSourceFactory);
209
248
  factoryRef.current = eventSourceFactory;
249
+ const handleRef = react.useRef(void 0);
210
250
  const [snapshot, setSnapshot] = react.useState();
211
251
  react.useEffect(() => {
212
252
  if (src === void 0) return void 0;
213
253
  setSnapshot({ src, state: LOADING });
214
254
  const handle = openRemoteSession(src, {
215
255
  ...follow === void 0 ? {} : { follow },
256
+ ...pageSize === void 0 ? {} : { pageSize },
216
257
  eventSourceFactory: factoryRef.current,
217
- onChange: (state) => setSnapshot({ src, state })
258
+ onChange: (state2) => setSnapshot({ src, state: state2 })
218
259
  });
260
+ handleRef.current = handle;
219
261
  handle.loaded.catch(() => void 0);
220
- return handle.close;
221
- }, [src, follow]);
222
- if (src === void 0) return IDLE;
223
- return (snapshot == null ? void 0 : snapshot.src) === src ? snapshot.state : LOADING;
262
+ return () => {
263
+ handle.close();
264
+ if (handleRef.current === handle) handleRef.current = void 0;
265
+ };
266
+ }, [src, follow, pageSize]);
267
+ const loadOlder = react.useCallback(() => {
268
+ var _a;
269
+ return ((_a = handleRef.current) == null ? void 0 : _a.loadOlder()) ?? Promise.resolve();
270
+ }, []);
271
+ const state = src === void 0 ? IDLE : (snapshot == null ? void 0 : snapshot.src) === src ? snapshot.state : LOADING;
272
+ return react.useMemo(() => ({ ...state, loadOlder }), [state, loadOlder]);
273
+ }
274
+ function useReportLoadState({
275
+ state,
276
+ enabled,
277
+ onChange
278
+ }) {
279
+ const onChangeRef = react.useRef(onChange);
280
+ onChangeRef.current = onChange;
281
+ const { loading, error } = state;
282
+ react.useEffect(() => {
283
+ var _a;
284
+ if (!enabled) return;
285
+ (_a = onChangeRef.current) == null ? void 0 : _a.call(
286
+ onChangeRef,
287
+ error !== void 0 ? { status: "error", error: new Error(error) } : { status: loading ? "loading" : "ready" }
288
+ );
289
+ }, [enabled, loading, error]);
224
290
  }
225
291
  function useRemoteCollection(collection, options = {}) {
226
292
  const current = collection == null ? void 0 : collection.sessions.find(
@@ -264,12 +330,14 @@ function errorMessage(reason) {
264
330
  function isRecord(value) {
265
331
  return typeof value === "object" && value !== null && !Array.isArray(value);
266
332
  }
333
+ exports.DEFAULT_SESSION_PAGE_SIZE = DEFAULT_SESSION_PAGE_SIZE;
267
334
  exports.TERMINAL_SESSION_LIFECYCLES = TERMINAL_SESSION_LIFECYCLES;
268
335
  exports.fetchRemoteSession = fetchRemoteSession;
269
336
  exports.isTerminalSessionLifecycle = isTerminalSessionLifecycle;
270
337
  exports.mergeSessionMessages = mergeSessionMessages;
271
338
  exports.openRemoteSession = openRemoteSession;
272
- exports.sessionFollowUrl = sessionFollowUrl;
339
+ exports.sessionUrl = sessionUrl;
273
340
  exports.useRemoteCollection = useRemoteCollection;
274
341
  exports.useRemoteSession = useRemoteSession;
342
+ exports.useReportLoadState = useReportLoadState;
275
343
  //# sourceMappingURL=SessionInspector.remote.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionInspector.remote.cjs","sources":["../../../src/data/ai/SessionInspector.remote.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n useEventSourceFactory,\n type EventSourceFactory,\n type EventSourceLike,\n} from \"../../hooks/event-source\";\nimport type { SessionCollectionInput } from \"./SessionInspector.collection-types\";\nimport type {\n SessionUIMessage,\n UnifiedSessionInput,\n} from \"./SessionViewer.unified\";\n\n// SessionInspector.remote loads a session from a URL and follows it live.\n// The contract (captain `SessionHandler`):\n// GET {src} Accept: application/json → one UnifiedSessionInput; a non-2xx\n// body is `{error, …}` JSON or plain text, surfaced verbatim.\n// GET {src}?follow=1 (SSE) → `entry` frames (one SessionUIMessage, upserted\n// by id), `state` frames ({revision, lifecycleStatus,\n// activityState, facets}), `error` frames ({error}) and `: ping`\n// comments. The server closes the stream once the session is\n// terminal.\n// A `state` frame with a higher revision means aggregate facets (plan,\n// approvals, files, …) changed, so the aggregate is refetched; entries\n// streamed while it was in flight stay merged on top. `facets` is an opaque\n// fingerprint of the non-message session facets (plan, todos, files, approval\n// requests): the first frame's value is the baseline (no refetch from it\n// alone), and any later frame whose `facets` differs from the last one seen\n// also triggers a refetch, independently of `revision`.\n\nexport const TERMINAL_SESSION_LIFECYCLES = [\n \"succeeded\",\n \"partial\",\n \"failed\",\n \"cancelled\",\n \"interrupted\",\n \"completed\",\n] as const;\n\nexport function isTerminalSessionLifecycle(status: string | undefined) {\n return (\n status !== undefined &&\n (TERMINAL_SESSION_LIFECYCLES as readonly string[]).includes(status)\n );\n}\n\nexport interface RemoteSessionOptions {\n /** Force (`true`) or suppress (`false`) the live stream. By default a\n * session follows only while it reports a non-terminal `lifecycleStatus`;\n * a session that reports none is not followed. */\n follow?: boolean;\n}\n\nexport interface RemoteSessionState {\n session?: UnifiedSessionInput;\n error?: string;\n loading: boolean;\n}\n\n/** The payload of a follow stream's `state` frame. */\nexport interface SessionFollowState {\n revision: number;\n lifecycleStatus: string;\n activityState: string;\n /** Opaque fingerprint of non-message session facets (plan, todos, files,\n * approval requests). Always sent by captain — see the module doc above\n * for how it's used to decide whether to refetch. */\n facets: string;\n}\n\nexport interface RemoteSessionHandle {\n /** Settles with the first fetched session, or rejects with the load error\n * (which `onChange` has already reported as `error`). */\n loaded: Promise<UnifiedSessionInput>;\n close: () => void;\n}\n\n/** `src` with `follow=1` appended to its query, keeping any fragment last. */\nexport function sessionFollowUrl(src: string) {\n const hashAt = src.indexOf(\"#\");\n const base = hashAt === -1 ? src : src.slice(0, hashAt);\n const hash = hashAt === -1 ? \"\" : src.slice(hashAt);\n const separator = !base.includes(\"?\")\n ? \"?\"\n : base.endsWith(\"?\") || base.endsWith(\"&\")\n ? \"\"\n : \"&\";\n return `${base}${separator}follow=1${hash}`;\n}\n\nexport async function fetchRemoteSession(\n src: string,\n signal?: AbortSignal,\n): Promise<UnifiedSessionInput> {\n const response = await fetch(src, {\n headers: { Accept: \"application/json\" },\n ...(signal ? { signal } : {}),\n });\n const body = await response.text();\n if (!response.ok) {\n throw new Error(\n `Failed to load session ${src} (HTTP ${response.status}): ${errorDetail(body)}`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (cause) {\n throw new Error(\n `Session ${src} returned invalid JSON (${String(cause)}): ${body.slice(0, 200)}`,\n );\n }\n if (!isRecord(parsed)) {\n throw new Error(\n `Session ${src} returned ${Array.isArray(parsed) ? \"an array\" : String(parsed)}, expected a session object`,\n );\n }\n return parsed as UnifiedSessionInput;\n}\n\n/** Upserts messages by id: a known id is replaced in place, a new one appended. */\nexport function mergeSessionMessages(\n base: readonly SessionUIMessage[] | undefined,\n incoming: readonly SessionUIMessage[],\n): SessionUIMessage[] {\n const merged = [...(base ?? [])];\n for (const message of incoming) {\n const index = merged.findIndex((existing) => existing.id === message.id);\n if (index === -1) merged.push(message);\n else merged[index] = message;\n }\n return merged;\n}\n\n/** Fetches `src`, then follows it when wanted, reporting every change through\n * `onChange`. Nothing is reported after `close()`. */\nexport function openRemoteSession(\n src: string,\n {\n follow,\n eventSourceFactory,\n onChange,\n }: RemoteSessionOptions & {\n eventSourceFactory: EventSourceFactory;\n onChange: (state: RemoteSessionState) => void;\n },\n): RemoteSessionHandle {\n const url = sessionFollowUrl(src);\n const aborter = new AbortController();\n const streamed = new Map<string, { message: SessionUIMessage; seq: number }>();\n let closed = false;\n let source: EventSourceLike | undefined;\n let state: RemoteSessionState = { loading: true };\n let revision: number | undefined;\n let facets: string | undefined;\n let seq = 0;\n let generation = 0;\n\n const emit = (next: RemoteSessionState) => {\n if (closed) return;\n state = next;\n onChange(next);\n };\n const stopFollowing = () => {\n source?.close();\n source = undefined;\n };\n const fail = (message: string) => {\n stopFollowing();\n emit({ ...state, loading: false, error: message });\n };\n\n const refresh = async () => {\n const current = ++generation;\n const startedAt = seq;\n const fetched = await fetchRemoteSession(src, aborter.signal);\n if (closed || current !== generation) return undefined;\n const fetchedIds = new Set(fetched.messages?.map((message) => message.id));\n const keep = [...streamed.values()]\n .filter((entry) => entry.seq > startedAt || !fetchedIds.has(entry.message.id))\n .map((entry) => entry.message);\n const session = {\n ...fetched,\n messages: mergeSessionMessages(fetched.messages, keep),\n };\n if (typeof fetched.revision === \"number\") {\n revision = Math.max(revision ?? fetched.revision, fetched.revision);\n }\n emit({ ...state, session, loading: false });\n return session;\n };\n\n const readFrame = (event: Event): unknown => {\n try {\n return JSON.parse((event as MessageEvent).data as string);\n } catch (cause) {\n fail(`Unparseable ${event.type} frame from ${url}: ${String(cause)}`);\n return undefined;\n }\n };\n\n const onEntry = (event: Event) => {\n const message = readFrame(event);\n if (message === undefined) return;\n if (!isRecord(message) || typeof message.id !== \"string\" || !message.id) {\n fail(`Entry frame from ${url} has no message id: ${String((event as MessageEvent).data)}`);\n return;\n }\n const entry = message as unknown as SessionUIMessage;\n streamed.set(message.id, { message: entry, seq: ++seq });\n const session = state.session!;\n emit({\n ...state,\n session: { ...session, messages: mergeSessionMessages(session.messages, [entry]) },\n });\n };\n\n const onState = (event: Event) => {\n const frame = readFrame(event);\n if (frame === undefined) return;\n if (\n !isRecord(frame) ||\n typeof frame.revision !== \"number\" ||\n typeof frame.lifecycleStatus !== \"string\" ||\n typeof frame.facets !== \"string\"\n ) {\n fail(`State frame from ${url} lacks a numeric revision, a lifecycleStatus, and facets: ${String((event as MessageEvent).data)}`);\n return;\n }\n const next = frame as unknown as SessionFollowState;\n emit({\n ...state,\n session: {\n ...state.session,\n lifecycleStatus: next.lifecycleStatus,\n ...(typeof next.activityState === \"string\" ? { activityState: next.activityState } : {}),\n },\n });\n const revisionIncreased = revision === undefined || next.revision > revision;\n // The first state frame's facets is the baseline: it never triggers a\n // refetch by itself, only a higher revision does (as before facets\n // existed). Any later frame whose facets differ from the last one seen\n // does trigger a refetch, independently of revision.\n const facetsChanged = facets !== undefined && next.facets !== facets;\n facets = next.facets;\n if (revisionIncreased) revision = next.revision;\n if (revisionIncreased || facetsChanged) {\n refresh().catch((reason: unknown) => {\n if (!closed) fail(errorMessage(reason));\n });\n }\n if (isTerminalSessionLifecycle(next.lifecycleStatus)) stopFollowing();\n };\n\n const startFollowing = () => {\n const es = eventSourceFactory(url);\n source = es;\n let received = false;\n const onError = (event: Event) => {\n const data = (event as MessageEvent).data;\n if (typeof data === \"string\") {\n const frame = readFrame(event);\n if (frame === undefined) return;\n const reported = isRecord(frame) && typeof frame.error === \"string\" ? frame.error : data;\n fail(`Session stream ${url} reported an error: ${reported}`);\n return;\n }\n // After data has arrived, a dropped connection reconnects on its own and\n // the server replays; entries upsert by id, so the replay is idempotent.\n if (received && es.readyState !== 2) return;\n fail(\n received\n ? `Lost the session stream ${url}`\n : `Could not connect to the session stream ${url}`,\n );\n };\n es.addEventListener(\"entry\", (event) => {\n received = true;\n onEntry(event);\n });\n es.addEventListener(\"state\", (event) => {\n received = true;\n onState(event);\n });\n es.addEventListener(\"error\", onError);\n };\n\n const loaded = refresh().then(\n (session) => {\n if (!session) throw new Error(`Session ${src} was closed before it loaded`);\n const lifecycle = session.lifecycleStatus;\n const wanted =\n follow ?? (lifecycle !== undefined && !isTerminalSessionLifecycle(lifecycle));\n if (wanted && !closed) startFollowing();\n return session;\n },\n (reason: unknown) => {\n if (!closed) fail(errorMessage(reason));\n throw reason;\n },\n );\n\n return {\n loaded,\n close: () => {\n closed = true;\n aborter.abort();\n stopFollowing();\n },\n };\n}\n\nconst IDLE: RemoteSessionState = { loading: false };\nconst LOADING: RemoteSessionState = { loading: true };\n\n/** Loads `src` (undefined loads nothing) and follows it on demand; see\n * `RemoteSessionOptions.follow`. Changing `src` or `follow`, or unmounting,\n * closes the previous stream. */\nexport function useRemoteSession(\n src: string | undefined,\n { follow }: RemoteSessionOptions = {},\n): RemoteSessionState {\n const eventSourceFactory = useEventSourceFactory();\n const factoryRef = useRef(eventSourceFactory);\n factoryRef.current = eventSourceFactory;\n const [snapshot, setSnapshot] = useState<{\n src: string;\n state: RemoteSessionState;\n }>();\n\n useEffect(() => {\n if (src === undefined) return undefined;\n setSnapshot({ src, state: LOADING });\n const handle = openRemoteSession(src, {\n ...(follow === undefined ? {} : { follow }),\n eventSourceFactory: factoryRef.current,\n onChange: (state) => setSnapshot({ src, state }),\n });\n // A load failure already reached the state through onChange as `error`.\n handle.loaded.catch(() => undefined);\n return handle.close;\n }, [src, follow]);\n\n if (src === undefined) return IDLE;\n return snapshot?.src === src ? snapshot.state : LOADING;\n}\n\nexport interface RemoteCollectionState {\n /** The collection with its current item's session loaded, once it is. */\n collection?: SessionCollectionInput;\n error?: string;\n loading: boolean;\n}\n\n/** Loads the current item of a collection when it carries only `src`, so the\n * hierarchy (which requires a loaded current session) can render. */\nexport function useRemoteCollection(\n collection: SessionCollectionInput | undefined,\n options: RemoteSessionOptions = {},\n): RemoteCollectionState {\n const current = collection?.sessions.find(\n (item) => item.id === collection.currentSessionId,\n );\n const src = current && !current.session ? current.src : undefined;\n const remote = useRemoteSession(src, options);\n return useMemo(() => {\n if (!collection) return IDLE;\n if (src === undefined || !current) return { collection, loading: false };\n const status = {\n loading: remote.loading,\n ...(remote.error ? { error: remote.error } : {}),\n };\n if (!remote.session) return status;\n const session = remote.session;\n return {\n ...status,\n collection: {\n ...collection,\n sessions: collection.sessions.map((item) =>\n item === current ? { ...item, session } : item,\n ),\n },\n };\n }, [collection, current, src, remote]);\n}\n\nfunction errorDetail(body: string) {\n const text = body.trim();\n if (!text) return \"empty response body\";\n try {\n const parsed: unknown = JSON.parse(text);\n if (isRecord(parsed) && typeof parsed.error === \"string\") return parsed.error;\n } catch {\n // Not JSON: the text itself is the detail.\n }\n return text;\n}\n\nfunction errorMessage(reason: unknown) {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"names":["useEventSourceFactory","useRef","useState","useEffect","useMemo"],"mappings":"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"}
1
+ {"version":3,"file":"SessionInspector.remote.cjs","sources":["../../../src/data/ai/SessionInspector.remote.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n useEventSourceFactory,\n type EventSourceFactory,\n type EventSourceLike,\n} from \"../../hooks/event-source\";\nimport type { SessionCollectionInput } from \"./SessionInspector.collection-types\";\nimport type {\n SessionUIMessage,\n UnifiedSessionInput,\n} from \"./SessionViewer.unified\";\n\n// SessionInspector.remote loads a session from a URL and follows it live,\n// asking only for the transcript's tail. The contract (captain\n// `SessionHandler`):\n// GET {src}?tail=N Accept: application/json → one UnifiedSessionInput with\n// the newest N messages, and `window` (the full counts) when older\n// ones were left out; a non-2xx body is `{error, …}` JSON or plain\n// text, surfaced verbatim.\n// GET {src}?before=<id>&limit=N → the N messages ahead of <id>: one page of\n// history, loaded on demand (`loadOlder`) and prepended.\n// GET {src}?follow=1&after=<id> (SSE) → `entry` frames for the messages\n// after <id> and every later change (one SessionUIMessage, upserted\n// by id), `state` frames ({revision, lifecycleStatus,\n// activityState, facets}), `error` frames ({error}) and `: ping`\n// comments. The server closes the stream once the session is\n// terminal.\n// A `state` frame with a higher revision means aggregate facets (plan,\n// approvals, files, …) changed, so the tail is refetched and merged into the\n// messages already held; entries streamed while it was in flight stay merged\n// on top. `facets` is an opaque\n// fingerprint of the non-message session facets (plan, todos, files, approval\n// requests): the first frame's value is the baseline (no refetch from it\n// alone), and any later frame whose `facets` differs from the last one seen\n// also triggers a refetch, independently of `revision`.\n\nexport const TERMINAL_SESSION_LIFECYCLES = [\n \"succeeded\",\n \"partial\",\n \"failed\",\n \"cancelled\",\n \"interrupted\",\n \"completed\",\n] as const;\n\nexport function isTerminalSessionLifecycle(status: string | undefined) {\n return (\n status !== undefined &&\n (TERMINAL_SESSION_LIFECYCLES as readonly string[]).includes(status)\n );\n}\n\n/** Messages asked for by the first read and by each page of history. */\nexport const DEFAULT_SESSION_PAGE_SIZE = 100;\n\nexport interface RemoteSessionOptions {\n /** Force (`true`) or suppress (`false`) the live stream. By default a\n * session follows only while it reports a non-terminal `lifecycleStatus`;\n * a session that reports none is not followed. */\n follow?: boolean;\n /** Messages per read: the first read's `tail` and each history page's\n * `limit`. Defaults to DEFAULT_SESSION_PAGE_SIZE. */\n pageSize?: number;\n}\n\nexport interface RemoteSessionState {\n session?: UnifiedSessionInput;\n error?: string;\n loading: boolean;\n /** Older messages exist that `loadOlder` has not fetched yet. */\n hasOlder?: boolean;\n /** A `loadOlder` page is in flight. */\n loadingOlder?: boolean;\n}\n\n/** The payload of a follow stream's `state` frame. */\nexport interface SessionFollowState {\n revision: number;\n lifecycleStatus: string;\n activityState: string;\n /** Opaque fingerprint of non-message session facets (plan, todos, files,\n * approval requests). Always sent by captain — see the module doc above\n * for how it's used to decide whether to refetch. */\n facets: string;\n}\n\nexport interface RemoteSessionHandle {\n /** Settles with the first fetched session, or rejects with the load error\n * (which `onChange` has already reported as `error`). */\n loaded: Promise<UnifiedSessionInput>;\n /** Fetches the page of history before the oldest message held and\n * prepends it; a no-op while one is in flight or nothing older remains. */\n loadOlder: () => Promise<void>;\n close: () => void;\n}\n\n/** `src` with `params` set in its query (replacing any of the same name),\n * keeping any fragment last. */\nexport function sessionUrl(src: string, params: Record<string, string | number>) {\n const hashAt = src.indexOf(\"#\");\n const base = hashAt === -1 ? src : src.slice(0, hashAt);\n const hash = hashAt === -1 ? \"\" : src.slice(hashAt);\n const queryAt = base.indexOf(\"?\");\n const query = new URLSearchParams(queryAt === -1 ? \"\" : base.slice(queryAt + 1));\n for (const [name, value] of Object.entries(params)) query.set(name, String(value));\n return `${queryAt === -1 ? base : base.slice(0, queryAt)}?${query}${hash}`;\n}\n\nexport async function fetchRemoteSession(\n src: string,\n signal?: AbortSignal,\n): Promise<UnifiedSessionInput> {\n const response = await fetch(src, {\n headers: { Accept: \"application/json\" },\n ...(signal ? { signal } : {}),\n });\n const body = await response.text();\n if (!response.ok) {\n throw new Error(\n `Failed to load session ${src} (HTTP ${response.status}): ${errorDetail(body)}`,\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch (cause) {\n throw new Error(\n `Session ${src} returned invalid JSON (${String(cause)}): ${body.slice(0, 200)}`,\n );\n }\n if (!isRecord(parsed)) {\n throw new Error(\n `Session ${src} returned ${Array.isArray(parsed) ? \"an array\" : String(parsed)}, expected a session object`,\n );\n }\n return parsed as UnifiedSessionInput;\n}\n\n/** Upserts messages by id: a known id is replaced in place, a new one appended.\n * Placeholders stand in for a transcript that does not exist yet; the first\n * real message replaces them all, since its ids never match theirs. */\nexport function mergeSessionMessages(\n base: readonly SessionUIMessage[] | undefined,\n incoming: readonly SessionUIMessage[],\n): SessionUIMessage[] {\n const real = incoming.some((message) => !message.placeholder);\n const merged = (base ?? []).filter((message) => !(real && message.placeholder));\n for (const message of incoming) {\n const index = merged.findIndex((existing) => existing.id === message.id);\n if (index === -1) merged.push(message);\n else merged[index] = message;\n }\n return merged;\n}\n\n/** Fetches `src`, then follows it when wanted, reporting every change through\n * `onChange`. Nothing is reported after `close()`. */\nexport function openRemoteSession(\n src: string,\n {\n follow,\n pageSize = DEFAULT_SESSION_PAGE_SIZE,\n eventSourceFactory,\n onChange,\n }: RemoteSessionOptions & {\n eventSourceFactory: EventSourceFactory;\n onChange: (state: RemoteSessionState) => void;\n },\n): RemoteSessionHandle {\n let url = sessionUrl(src, { follow: 1 });\n let olderPage: Promise<void> | undefined;\n let pageError: string | undefined;\n const aborter = new AbortController();\n const streamed = new Map<string, { message: SessionUIMessage; seq: number }>();\n let closed = false;\n let source: EventSourceLike | undefined;\n let state: RemoteSessionState = { loading: true };\n let revision: number | undefined;\n let facets: string | undefined;\n let seq = 0;\n let generation = 0;\n\n const emit = (next: RemoteSessionState) => {\n if (closed) return;\n state = next;\n onChange(next);\n };\n const stopFollowing = () => {\n source?.close();\n source = undefined;\n };\n const fail = (message: string) => {\n stopFollowing();\n emit({ ...state, loading: false, error: message });\n };\n\n // Reads the tail and merges it into the messages already held — older pages\n // and streamed entries — so a refetch never drops history the reader loaded.\n const refresh = async () => {\n const current = ++generation;\n const startedAt = seq;\n const fetched = await fetchRemoteSession(sessionUrl(src, { tail: pageSize }), aborter.signal);\n if (closed || current !== generation) return undefined;\n const fetchedIds = new Set(fetched.messages?.map((message) => message.id));\n const keep = [...streamed.values()]\n .filter((entry) => entry.seq > startedAt || !fetchedIds.has(entry.message.id))\n .map((entry) => entry.message);\n const held = mergeSessionMessages(state.session?.messages, fetched.messages ?? []);\n const session = {\n ...fetched,\n messages: mergeSessionMessages(held, keep),\n };\n if (typeof fetched.revision === \"number\") {\n revision = Math.max(revision ?? fetched.revision, fetched.revision);\n }\n const hasOlder =\n state.session === undefined\n ? (fetched.window?.messages ?? 0) > (fetched.messages?.length ?? 0)\n : state.hasOlder;\n emit({ ...state, session, loading: false, hasOlder: hasOlder ?? false });\n return session;\n };\n\n const loadOlder = (): Promise<void> => {\n const oldest = state.session?.messages?.[0]?.id;\n if (closed || olderPage || !state.hasOlder || !oldest) return olderPage ?? Promise.resolve();\n emit({ ...state, loadingOlder: true });\n olderPage = fetchRemoteSession(sessionUrl(src, { before: oldest, limit: pageSize }), aborter.signal)\n .then((page) => {\n const older = page.messages ?? [];\n // A page that loads clears the alert a failed page raised, never a\n // stream's terminal error.\n const { error, ...rest } = state;\n const session = state.session!;\n emit({\n ...rest,\n ...(error !== undefined && error !== pageError ? { error } : {}),\n loadingOlder: false,\n hasOlder: older.length === pageSize,\n session: { ...session, messages: mergeSessionMessages(older, session.messages ?? []) },\n });\n pageError = undefined;\n })\n .catch((reason: unknown) => {\n if (closed) return;\n pageError = errorMessage(reason);\n emit({ ...state, loadingOlder: false, error: pageError });\n })\n .finally(() => {\n olderPage = undefined;\n });\n return olderPage;\n };\n\n const readFrame = (event: Event): unknown => {\n try {\n return JSON.parse((event as MessageEvent).data as string);\n } catch (cause) {\n fail(`Unparseable ${event.type} frame from ${url}: ${String(cause)}`);\n return undefined;\n }\n };\n\n const onEntry = (event: Event) => {\n const message = readFrame(event);\n if (message === undefined) return;\n if (!isRecord(message) || typeof message.id !== \"string\" || !message.id) {\n fail(`Entry frame from ${url} has no message id: ${String((event as MessageEvent).data)}`);\n return;\n }\n const entry = message as unknown as SessionUIMessage;\n streamed.set(message.id, { message: entry, seq: ++seq });\n const session = state.session!;\n emit({\n ...state,\n session: { ...session, messages: mergeSessionMessages(session.messages, [entry]) },\n });\n };\n\n const onState = (event: Event) => {\n const frame = readFrame(event);\n if (frame === undefined) return;\n if (\n !isRecord(frame) ||\n typeof frame.revision !== \"number\" ||\n typeof frame.lifecycleStatus !== \"string\" ||\n typeof frame.facets !== \"string\"\n ) {\n fail(`State frame from ${url} lacks a numeric revision, a lifecycleStatus, and facets: ${String((event as MessageEvent).data)}`);\n return;\n }\n const next = frame as unknown as SessionFollowState;\n emit({\n ...state,\n session: {\n ...state.session,\n lifecycleStatus: next.lifecycleStatus,\n ...(typeof next.activityState === \"string\" ? { activityState: next.activityState } : {}),\n },\n });\n const revisionIncreased = revision === undefined || next.revision > revision;\n // The first state frame's facets is the baseline: it never triggers a\n // refetch by itself, only a higher revision does (as before facets\n // existed). Any later frame whose facets differ from the last one seen\n // does trigger a refetch, independently of revision.\n const facetsChanged = facets !== undefined && next.facets !== facets;\n facets = next.facets;\n if (revisionIncreased) revision = next.revision;\n if (revisionIncreased || facetsChanged) {\n refresh().catch((reason: unknown) => {\n if (!closed) fail(errorMessage(reason));\n });\n }\n if (isTerminalSessionLifecycle(next.lifecycleStatus)) stopFollowing();\n };\n\n // Resumes after the newest real message held, so the server replays only\n // what came later; a placeholder is no position in the transcript, so a\n // reader holding only placeholders follows from the start. A reconnect\n // reuses this URL; replayed entries upsert by id.\n const startFollowing = () => {\n const newest = state.session?.messages?.filter((message) => !message.placeholder).at(-1)?.id;\n url = sessionUrl(src, newest ? { follow: 1, after: newest } : { follow: 1 });\n const es = eventSourceFactory(url);\n source = es;\n let received = false;\n const onError = (event: Event) => {\n const data = (event as MessageEvent).data;\n if (typeof data === \"string\") {\n const frame = readFrame(event);\n if (frame === undefined) return;\n const reported = isRecord(frame) && typeof frame.error === \"string\" ? frame.error : data;\n fail(`Session stream ${url} reported an error: ${reported}`);\n return;\n }\n // After data has arrived, a dropped connection reconnects on its own and\n // the server replays; entries upsert by id, so the replay is idempotent.\n if (received && es.readyState !== 2) return;\n fail(\n received\n ? `Lost the session stream ${url}`\n : `Could not connect to the session stream ${url}`,\n );\n };\n es.addEventListener(\"entry\", (event) => {\n received = true;\n onEntry(event);\n });\n es.addEventListener(\"state\", (event) => {\n received = true;\n onState(event);\n });\n es.addEventListener(\"error\", onError);\n };\n\n const loaded = refresh().then(\n (session) => {\n if (!session) throw new Error(`Session ${src} was closed before it loaded`);\n const lifecycle = session.lifecycleStatus;\n const wanted =\n follow ?? (lifecycle !== undefined && !isTerminalSessionLifecycle(lifecycle));\n if (wanted && !closed) startFollowing();\n return session;\n },\n (reason: unknown) => {\n if (!closed) fail(errorMessage(reason));\n throw reason;\n },\n );\n\n return {\n loaded,\n loadOlder,\n close: () => {\n closed = true;\n aborter.abort();\n stopFollowing();\n },\n };\n}\n\nconst IDLE: RemoteSessionState = { loading: false };\nconst LOADING: RemoteSessionState = { loading: true };\n\nexport interface RemoteSessionView extends RemoteSessionState {\n /** See RemoteSessionHandle.loadOlder; a no-op before the session loads. */\n loadOlder: () => Promise<void>;\n}\n\n/** Loads `src` (undefined loads nothing) and follows it on demand; see\n * `RemoteSessionOptions`. Changing `src`, `follow` or `pageSize`, or\n * unmounting, closes the previous stream. */\nexport function useRemoteSession(\n src: string | undefined,\n { follow, pageSize }: RemoteSessionOptions = {},\n): RemoteSessionView {\n const eventSourceFactory = useEventSourceFactory();\n const factoryRef = useRef(eventSourceFactory);\n factoryRef.current = eventSourceFactory;\n const handleRef = useRef<RemoteSessionHandle>(undefined);\n const [snapshot, setSnapshot] = useState<{\n src: string;\n state: RemoteSessionState;\n }>();\n\n useEffect(() => {\n if (src === undefined) return undefined;\n setSnapshot({ src, state: LOADING });\n const handle = openRemoteSession(src, {\n ...(follow === undefined ? {} : { follow }),\n ...(pageSize === undefined ? {} : { pageSize }),\n eventSourceFactory: factoryRef.current,\n onChange: (state) => setSnapshot({ src, state }),\n });\n handleRef.current = handle;\n // A load failure already reached the state through onChange as `error`.\n handle.loaded.catch(() => undefined);\n return () => {\n handle.close();\n if (handleRef.current === handle) handleRef.current = undefined;\n };\n }, [src, follow, pageSize]);\n\n const loadOlder = useCallback(() => handleRef.current?.loadOlder() ?? Promise.resolve(), []);\n const state = src === undefined ? IDLE : snapshot?.src === src ? snapshot.state : LOADING;\n return useMemo(() => ({ ...state, loadOlder }), [state, loadOlder]);\n}\n\n/** What a host learns about a `src`-backed inspector's load. `error` carries\n * the server's message (a 404 body's `error` field or plain text) or the\n * follow stream's terminal error frame. */\nexport type SessionInspectorLoadState =\n | { status: \"loading\" | \"ready\" }\n | { status: \"error\"; error: Error };\n\n/** Reports `state` to `onChange` whenever its status or error changes; never\n * when `enabled` is false (in-memory sessions have no load to report). */\nexport function useReportLoadState({\n state,\n enabled,\n onChange,\n}: {\n state: RemoteSessionState;\n enabled: boolean;\n onChange: ((state: SessionInspectorLoadState) => void) | undefined;\n}) {\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n const { loading, error } = state;\n useEffect(() => {\n if (!enabled) return;\n onChangeRef.current?.(\n error !== undefined\n ? { status: \"error\", error: new Error(error) }\n : { status: loading ? \"loading\" : \"ready\" },\n );\n }, [enabled, loading, error]);\n}\n\nexport interface RemoteCollectionState {\n /** The collection with its current item's session loaded, once it is. */\n collection?: SessionCollectionInput;\n error?: string;\n loading: boolean;\n}\n\n/** Loads the current item of a collection when it carries only `src`, so the\n * hierarchy (which requires a loaded current session) can render. */\nexport function useRemoteCollection(\n collection: SessionCollectionInput | undefined,\n options: RemoteSessionOptions = {},\n): RemoteCollectionState {\n const current = collection?.sessions.find(\n (item) => item.id === collection.currentSessionId,\n );\n const src = current && !current.session ? current.src : undefined;\n const remote = useRemoteSession(src, options);\n return useMemo(() => {\n if (!collection) return IDLE;\n if (src === undefined || !current) return { collection, loading: false };\n const status = {\n loading: remote.loading,\n ...(remote.error ? { error: remote.error } : {}),\n };\n if (!remote.session) return status;\n const session = remote.session;\n return {\n ...status,\n collection: {\n ...collection,\n sessions: collection.sessions.map((item) =>\n item === current ? { ...item, session } : item,\n ),\n },\n };\n }, [collection, current, src, remote]);\n}\n\nfunction errorDetail(body: string) {\n const text = body.trim();\n if (!text) return \"empty response body\";\n try {\n const parsed: unknown = JSON.parse(text);\n if (isRecord(parsed) && typeof parsed.error === \"string\") return parsed.error;\n } catch {\n // Not JSON: the text itself is the detail.\n }\n return text;\n}\n\nfunction errorMessage(reason: unknown) {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"names":["useEventSourceFactory","useRef","useState","useEffect","state","useCallback","useMemo"],"mappings":"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"}
@@ -3,16 +3,25 @@ import { SessionCollectionInput } from './SessionInspector.collection-types.js';
3
3
  import { SessionUIMessage, UnifiedSessionInput } from './SessionViewer.unified.js';
4
4
  export declare const TERMINAL_SESSION_LIFECYCLES: readonly ["succeeded", "partial", "failed", "cancelled", "interrupted", "completed"];
5
5
  export declare function isTerminalSessionLifecycle(status: string | undefined): boolean;
6
+ /** Messages asked for by the first read and by each page of history. */
7
+ export declare const DEFAULT_SESSION_PAGE_SIZE = 100;
6
8
  export interface RemoteSessionOptions {
7
9
  /** Force (`true`) or suppress (`false`) the live stream. By default a
8
10
  * session follows only while it reports a non-terminal `lifecycleStatus`;
9
11
  * a session that reports none is not followed. */
10
12
  follow?: boolean;
13
+ /** Messages per read: the first read's `tail` and each history page's
14
+ * `limit`. Defaults to DEFAULT_SESSION_PAGE_SIZE. */
15
+ pageSize?: number;
11
16
  }
12
17
  export interface RemoteSessionState {
13
18
  session?: UnifiedSessionInput;
14
19
  error?: string;
15
20
  loading: boolean;
21
+ /** Older messages exist that `loadOlder` has not fetched yet. */
22
+ hasOlder?: boolean;
23
+ /** A `loadOlder` page is in flight. */
24
+ loadingOlder?: boolean;
16
25
  }
17
26
  /** The payload of a follow stream's `state` frame. */
18
27
  export interface SessionFollowState {
@@ -28,23 +37,49 @@ export interface RemoteSessionHandle {
28
37
  /** Settles with the first fetched session, or rejects with the load error
29
38
  * (which `onChange` has already reported as `error`). */
30
39
  loaded: Promise<UnifiedSessionInput>;
40
+ /** Fetches the page of history before the oldest message held and
41
+ * prepends it; a no-op while one is in flight or nothing older remains. */
42
+ loadOlder: () => Promise<void>;
31
43
  close: () => void;
32
44
  }
33
- /** `src` with `follow=1` appended to its query, keeping any fragment last. */
34
- export declare function sessionFollowUrl(src: string): string;
45
+ /** `src` with `params` set in its query (replacing any of the same name),
46
+ * keeping any fragment last. */
47
+ export declare function sessionUrl(src: string, params: Record<string, string | number>): string;
35
48
  export declare function fetchRemoteSession(src: string, signal?: AbortSignal): Promise<UnifiedSessionInput>;
36
- /** Upserts messages by id: a known id is replaced in place, a new one appended. */
49
+ /** Upserts messages by id: a known id is replaced in place, a new one appended.
50
+ * Placeholders stand in for a transcript that does not exist yet; the first
51
+ * real message replaces them all, since its ids never match theirs. */
37
52
  export declare function mergeSessionMessages(base: readonly SessionUIMessage[] | undefined, incoming: readonly SessionUIMessage[]): SessionUIMessage[];
38
53
  /** Fetches `src`, then follows it when wanted, reporting every change through
39
54
  * `onChange`. Nothing is reported after `close()`. */
40
- export declare function openRemoteSession(src: string, { follow, eventSourceFactory, onChange, }: RemoteSessionOptions & {
55
+ export declare function openRemoteSession(src: string, { follow, pageSize, eventSourceFactory, onChange, }: RemoteSessionOptions & {
41
56
  eventSourceFactory: EventSourceFactory;
42
57
  onChange: (state: RemoteSessionState) => void;
43
58
  }): RemoteSessionHandle;
59
+ export interface RemoteSessionView extends RemoteSessionState {
60
+ /** See RemoteSessionHandle.loadOlder; a no-op before the session loads. */
61
+ loadOlder: () => Promise<void>;
62
+ }
44
63
  /** Loads `src` (undefined loads nothing) and follows it on demand; see
45
- * `RemoteSessionOptions.follow`. Changing `src` or `follow`, or unmounting,
46
- * closes the previous stream. */
47
- export declare function useRemoteSession(src: string | undefined, { follow }?: RemoteSessionOptions): RemoteSessionState;
64
+ * `RemoteSessionOptions`. Changing `src`, `follow` or `pageSize`, or
65
+ * unmounting, closes the previous stream. */
66
+ export declare function useRemoteSession(src: string | undefined, { follow, pageSize }?: RemoteSessionOptions): RemoteSessionView;
67
+ /** What a host learns about a `src`-backed inspector's load. `error` carries
68
+ * the server's message (a 404 body's `error` field or plain text) or the
69
+ * follow stream's terminal error frame. */
70
+ export type SessionInspectorLoadState = {
71
+ status: "loading" | "ready";
72
+ } | {
73
+ status: "error";
74
+ error: Error;
75
+ };
76
+ /** Reports `state` to `onChange` whenever its status or error changes; never
77
+ * when `enabled` is false (in-memory sessions have no load to report). */
78
+ export declare function useReportLoadState({ state, enabled, onChange, }: {
79
+ state: RemoteSessionState;
80
+ enabled: boolean;
81
+ onChange: ((state: SessionInspectorLoadState) => void) | undefined;
82
+ }): void;
48
83
  export interface RemoteCollectionState {
49
84
  /** The collection with its current item's session loaded, once it is. */
50
85
  collection?: SessionCollectionInput;
@@ -1 +1 @@
1
- {"version":3,"file":"SessionInspector.remote.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionInspector.remote.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAmBjC,eAAO,MAAM,2BAA2B,sFAO9B,CAAC;AAEX,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,WAKpE;AAED,MAAM,WAAW,oBAAoB;IACnC;;uDAEmD;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB;;0DAEsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC;8DAC0D;IAC1D,MAAM,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,UAU3C;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAyB9B;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,EAC7C,QAAQ,EAAE,SAAS,gBAAgB,EAAE,GACpC,gBAAgB,EAAE,CAQpB;AAED;uDACuD;AACvD,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,EACE,MAAM,EACN,kBAAkB,EAClB,QAAQ,GACT,EAAE,oBAAoB,GAAG;IACxB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC/C,GACA,mBAAmB,CAoKrB;AAKD;;kCAEkC;AAClC,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,EAAE,MAAM,EAAE,GAAE,oBAAyB,GACpC,kBAAkB,CAwBpB;AAED,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,UAAU,CAAC,EAAE,sBAAsB,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;sEACsE;AACtE,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,sBAAsB,GAAG,SAAS,EAC9C,OAAO,GAAE,oBAAyB,GACjC,qBAAqB,CAyBvB"}
1
+ {"version":3,"file":"SessionInspector.remote.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionInspector.remote.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { useRef, useState, useEffect, useMemo } from "react";
1
+ import { useRef, useState, useEffect, useCallback, useMemo } from "react";
2
2
  import { useEventSourceFactory } from "../../hooks/event-source.js";
3
3
  const TERMINAL_SESSION_LIFECYCLES = [
4
4
  "succeeded",
@@ -11,12 +11,15 @@ const TERMINAL_SESSION_LIFECYCLES = [
11
11
  function isTerminalSessionLifecycle(status) {
12
12
  return status !== void 0 && TERMINAL_SESSION_LIFECYCLES.includes(status);
13
13
  }
14
- function sessionFollowUrl(src) {
14
+ const DEFAULT_SESSION_PAGE_SIZE = 100;
15
+ function sessionUrl(src, params) {
15
16
  const hashAt = src.indexOf("#");
16
17
  const base = hashAt === -1 ? src : src.slice(0, hashAt);
17
18
  const hash = hashAt === -1 ? "" : src.slice(hashAt);
18
- const separator = !base.includes("?") ? "?" : base.endsWith("?") || base.endsWith("&") ? "" : "&";
19
- return `${base}${separator}follow=1${hash}`;
19
+ const queryAt = base.indexOf("?");
20
+ const query = new URLSearchParams(queryAt === -1 ? "" : base.slice(queryAt + 1));
21
+ for (const [name, value] of Object.entries(params)) query.set(name, String(value));
22
+ return `${queryAt === -1 ? base : base.slice(0, queryAt)}?${query}${hash}`;
20
23
  }
21
24
  async function fetchRemoteSession(src, signal) {
22
25
  const response = await fetch(src, {
@@ -45,7 +48,8 @@ async function fetchRemoteSession(src, signal) {
45
48
  return parsed;
46
49
  }
47
50
  function mergeSessionMessages(base, incoming) {
48
- const merged = [...base ?? []];
51
+ const real = incoming.some((message) => !message.placeholder);
52
+ const merged = (base ?? []).filter((message) => !(real && message.placeholder));
49
53
  for (const message of incoming) {
50
54
  const index = merged.findIndex((existing) => existing.id === message.id);
51
55
  if (index === -1) merged.push(message);
@@ -55,10 +59,13 @@ function mergeSessionMessages(base, incoming) {
55
59
  }
56
60
  function openRemoteSession(src, {
57
61
  follow,
62
+ pageSize = DEFAULT_SESSION_PAGE_SIZE,
58
63
  eventSourceFactory,
59
64
  onChange
60
65
  }) {
61
- const url = sessionFollowUrl(src);
66
+ let url = sessionUrl(src, { follow: 1 });
67
+ let olderPage;
68
+ let pageError;
62
69
  const aborter = new AbortController();
63
70
  const streamed = /* @__PURE__ */ new Map();
64
71
  let closed = false;
@@ -82,23 +89,51 @@ function openRemoteSession(src, {
82
89
  emit({ ...state, loading: false, error: message });
83
90
  };
84
91
  const refresh = async () => {
85
- var _a;
92
+ var _a, _b, _c, _d;
86
93
  const current = ++generation;
87
94
  const startedAt = seq;
88
- const fetched = await fetchRemoteSession(src, aborter.signal);
95
+ const fetched = await fetchRemoteSession(sessionUrl(src, { tail: pageSize }), aborter.signal);
89
96
  if (closed || current !== generation) return void 0;
90
97
  const fetchedIds = new Set((_a = fetched.messages) == null ? void 0 : _a.map((message) => message.id));
91
98
  const keep = [...streamed.values()].filter((entry) => entry.seq > startedAt || !fetchedIds.has(entry.message.id)).map((entry) => entry.message);
99
+ const held = mergeSessionMessages((_b = state.session) == null ? void 0 : _b.messages, fetched.messages ?? []);
92
100
  const session = {
93
101
  ...fetched,
94
- messages: mergeSessionMessages(fetched.messages, keep)
102
+ messages: mergeSessionMessages(held, keep)
95
103
  };
96
104
  if (typeof fetched.revision === "number") {
97
105
  revision = Math.max(revision ?? fetched.revision, fetched.revision);
98
106
  }
99
- emit({ ...state, session, loading: false });
107
+ const hasOlder = state.session === void 0 ? (((_c = fetched.window) == null ? void 0 : _c.messages) ?? 0) > (((_d = fetched.messages) == null ? void 0 : _d.length) ?? 0) : state.hasOlder;
108
+ emit({ ...state, session, loading: false, hasOlder: hasOlder ?? false });
100
109
  return session;
101
110
  };
111
+ const loadOlder = () => {
112
+ var _a, _b, _c;
113
+ const oldest = (_c = (_b = (_a = state.session) == null ? void 0 : _a.messages) == null ? void 0 : _b[0]) == null ? void 0 : _c.id;
114
+ if (closed || olderPage || !state.hasOlder || !oldest) return olderPage ?? Promise.resolve();
115
+ emit({ ...state, loadingOlder: true });
116
+ olderPage = fetchRemoteSession(sessionUrl(src, { before: oldest, limit: pageSize }), aborter.signal).then((page) => {
117
+ const older = page.messages ?? [];
118
+ const { error, ...rest } = state;
119
+ const session = state.session;
120
+ emit({
121
+ ...rest,
122
+ ...error !== void 0 && error !== pageError ? { error } : {},
123
+ loadingOlder: false,
124
+ hasOlder: older.length === pageSize,
125
+ session: { ...session, messages: mergeSessionMessages(older, session.messages ?? []) }
126
+ });
127
+ pageError = void 0;
128
+ }).catch((reason) => {
129
+ if (closed) return;
130
+ pageError = errorMessage(reason);
131
+ emit({ ...state, loadingOlder: false, error: pageError });
132
+ }).finally(() => {
133
+ olderPage = void 0;
134
+ });
135
+ return olderPage;
136
+ };
102
137
  const readFrame = (event) => {
103
138
  try {
104
139
  return JSON.parse(event.data);
@@ -150,6 +185,9 @@ function openRemoteSession(src, {
150
185
  if (isTerminalSessionLifecycle(next.lifecycleStatus)) stopFollowing();
151
186
  };
152
187
  const startFollowing = () => {
188
+ var _a, _b, _c;
189
+ const newest = (_c = (_b = (_a = state.session) == null ? void 0 : _a.messages) == null ? void 0 : _b.filter((message) => !message.placeholder).at(-1)) == null ? void 0 : _c.id;
190
+ url = sessionUrl(src, newest ? { follow: 1, after: newest } : { follow: 1 });
153
191
  const es = eventSourceFactory(url);
154
192
  source = es;
155
193
  let received = false;
@@ -192,6 +230,7 @@ function openRemoteSession(src, {
192
230
  );
193
231
  return {
194
232
  loaded,
233
+ loadOlder,
195
234
  close: () => {
196
235
  closed = true;
197
236
  aborter.abort();
@@ -201,24 +240,51 @@ function openRemoteSession(src, {
201
240
  }
202
241
  const IDLE = { loading: false };
203
242
  const LOADING = { loading: true };
204
- function useRemoteSession(src, { follow } = {}) {
243
+ function useRemoteSession(src, { follow, pageSize } = {}) {
205
244
  const eventSourceFactory = useEventSourceFactory();
206
245
  const factoryRef = useRef(eventSourceFactory);
207
246
  factoryRef.current = eventSourceFactory;
247
+ const handleRef = useRef(void 0);
208
248
  const [snapshot, setSnapshot] = useState();
209
249
  useEffect(() => {
210
250
  if (src === void 0) return void 0;
211
251
  setSnapshot({ src, state: LOADING });
212
252
  const handle = openRemoteSession(src, {
213
253
  ...follow === void 0 ? {} : { follow },
254
+ ...pageSize === void 0 ? {} : { pageSize },
214
255
  eventSourceFactory: factoryRef.current,
215
- onChange: (state) => setSnapshot({ src, state })
256
+ onChange: (state2) => setSnapshot({ src, state: state2 })
216
257
  });
258
+ handleRef.current = handle;
217
259
  handle.loaded.catch(() => void 0);
218
- return handle.close;
219
- }, [src, follow]);
220
- if (src === void 0) return IDLE;
221
- return (snapshot == null ? void 0 : snapshot.src) === src ? snapshot.state : LOADING;
260
+ return () => {
261
+ handle.close();
262
+ if (handleRef.current === handle) handleRef.current = void 0;
263
+ };
264
+ }, [src, follow, pageSize]);
265
+ const loadOlder = useCallback(() => {
266
+ var _a;
267
+ return ((_a = handleRef.current) == null ? void 0 : _a.loadOlder()) ?? Promise.resolve();
268
+ }, []);
269
+ const state = src === void 0 ? IDLE : (snapshot == null ? void 0 : snapshot.src) === src ? snapshot.state : LOADING;
270
+ return useMemo(() => ({ ...state, loadOlder }), [state, loadOlder]);
271
+ }
272
+ function useReportLoadState({
273
+ state,
274
+ enabled,
275
+ onChange
276
+ }) {
277
+ const onChangeRef = useRef(onChange);
278
+ onChangeRef.current = onChange;
279
+ const { loading, error } = state;
280
+ useEffect(() => {
281
+ var _a;
282
+ if (!enabled) return;
283
+ (_a = onChangeRef.current) == null ? void 0 : _a.call(
284
+ onChangeRef,
285
+ error !== void 0 ? { status: "error", error: new Error(error) } : { status: loading ? "loading" : "ready" }
286
+ );
287
+ }, [enabled, loading, error]);
222
288
  }
223
289
  function useRemoteCollection(collection, options = {}) {
224
290
  const current = collection == null ? void 0 : collection.sessions.find(
@@ -263,13 +329,15 @@ function isRecord(value) {
263
329
  return typeof value === "object" && value !== null && !Array.isArray(value);
264
330
  }
265
331
  export {
332
+ DEFAULT_SESSION_PAGE_SIZE,
266
333
  TERMINAL_SESSION_LIFECYCLES,
267
334
  fetchRemoteSession,
268
335
  isTerminalSessionLifecycle,
269
336
  mergeSessionMessages,
270
337
  openRemoteSession,
271
- sessionFollowUrl,
338
+ sessionUrl,
272
339
  useRemoteCollection,
273
- useRemoteSession
340
+ useRemoteSession,
341
+ useReportLoadState
274
342
  };
275
343
  //# sourceMappingURL=SessionInspector.remote.js.map