@flanksource/clicky-ui 0.3.45 → 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 (505) hide show
  1. package/NOTICE.md +11 -0
  2. package/README.md +22 -0
  3. package/dist/ai.cjs +13 -8
  4. package/dist/ai.cjs.map +1 -1
  5. package/dist/ai.js +7 -2
  6. package/dist/ai.js.map +1 -1
  7. package/dist/components/ErrorWrapper.cjs +2 -3
  8. package/dist/components/ErrorWrapper.cjs.map +1 -1
  9. package/dist/components/ErrorWrapper.d.ts.map +1 -1
  10. package/dist/components/ErrorWrapper.js +3 -4
  11. package/dist/components/ErrorWrapper.js.map +1 -1
  12. package/dist/data/AccessMark.cjs +23 -0
  13. package/dist/data/AccessMark.cjs.map +1 -0
  14. package/dist/data/AccessMark.d.ts +11 -0
  15. package/dist/data/AccessMark.d.ts.map +1 -0
  16. package/dist/data/AccessMark.js +23 -0
  17. package/dist/data/AccessMark.js.map +1 -0
  18. package/dist/data/Clicky.cjs +3 -0
  19. package/dist/data/Clicky.cjs.map +1 -1
  20. package/dist/data/Clicky.js +3 -0
  21. package/dist/data/Clicky.js.map +1 -1
  22. package/dist/data/CodeDiff.cjs +116 -23
  23. package/dist/data/CodeDiff.cjs.map +1 -1
  24. package/dist/data/CodeDiff.d.ts +26 -0
  25. package/dist/data/CodeDiff.d.ts.map +1 -1
  26. package/dist/data/CodeDiff.js +116 -23
  27. package/dist/data/CodeDiff.js.map +1 -1
  28. package/dist/data/FilterPill.cjs +15 -2
  29. package/dist/data/FilterPill.cjs.map +1 -1
  30. package/dist/data/FilterPill.d.ts +1 -1
  31. package/dist/data/FilterPill.d.ts.map +1 -1
  32. package/dist/data/FilterPill.js +15 -2
  33. package/dist/data/FilterPill.js.map +1 -1
  34. package/dist/data/GraphDiagram.cjs +65 -13
  35. package/dist/data/GraphDiagram.cjs.map +1 -1
  36. package/dist/data/GraphDiagram.d.ts +9 -2
  37. package/dist/data/GraphDiagram.d.ts.map +1 -1
  38. package/dist/data/GraphDiagram.js +66 -14
  39. package/dist/data/GraphDiagram.js.map +1 -1
  40. package/dist/data/GraphDiagramGroups.cjs +100 -0
  41. package/dist/data/GraphDiagramGroups.cjs.map +1 -0
  42. package/dist/data/GraphDiagramGroups.d.ts +24 -0
  43. package/dist/data/GraphDiagramGroups.d.ts.map +1 -0
  44. package/dist/data/GraphDiagramGroups.js +100 -0
  45. package/dist/data/GraphDiagramGroups.js.map +1 -0
  46. package/dist/data/GraphDiagramNodes.cjs +17 -31
  47. package/dist/data/GraphDiagramNodes.cjs.map +1 -1
  48. package/dist/data/GraphDiagramNodes.d.ts +6 -13
  49. package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
  50. package/dist/data/GraphDiagramNodes.js +17 -31
  51. package/dist/data/GraphDiagramNodes.js.map +1 -1
  52. package/dist/data/LogViewer.cjs +27 -8
  53. package/dist/data/LogViewer.cjs.map +1 -1
  54. package/dist/data/LogViewer.d.ts +11 -3
  55. package/dist/data/LogViewer.d.ts.map +1 -1
  56. package/dist/data/LogViewer.js +27 -8
  57. package/dist/data/LogViewer.js.map +1 -1
  58. package/dist/data/Properties.cjs +2 -15
  59. package/dist/data/Properties.cjs.map +1 -1
  60. package/dist/data/Properties.d.ts +4 -0
  61. package/dist/data/Properties.d.ts.map +1 -1
  62. package/dist/data/Properties.js +2 -15
  63. package/dist/data/Properties.js.map +1 -1
  64. package/dist/data/PropertyValue.cjs +37 -0
  65. package/dist/data/PropertyValue.cjs.map +1 -0
  66. package/dist/data/PropertyValue.d.ts +6 -0
  67. package/dist/data/PropertyValue.d.ts.map +1 -0
  68. package/dist/data/PropertyValue.js +37 -0
  69. package/dist/data/PropertyValue.js.map +1 -0
  70. package/dist/data/Tree.cjs +4 -3
  71. package/dist/data/Tree.cjs.map +1 -1
  72. package/dist/data/Tree.d.ts +3 -1
  73. package/dist/data/Tree.d.ts.map +1 -1
  74. package/dist/data/Tree.js +4 -3
  75. package/dist/data/Tree.js.map +1 -1
  76. package/dist/data/TreeNode.cjs +1 -1
  77. package/dist/data/TreeNode.cjs.map +1 -1
  78. package/dist/data/TreeNode.d.ts.map +1 -1
  79. package/dist/data/TreeNode.js +1 -1
  80. package/dist/data/TreeNode.js.map +1 -1
  81. package/dist/data/ai/ChatFab.cjs +1 -1
  82. package/dist/data/ai/ChatFab.cjs.map +1 -1
  83. package/dist/data/ai/ChatFab.js +1 -1
  84. package/dist/data/ai/ChatFab.js.map +1 -1
  85. package/dist/data/ai/ChatWindow.cjs +106 -171
  86. package/dist/data/ai/ChatWindow.cjs.map +1 -1
  87. package/dist/data/ai/ChatWindow.d.ts +6 -2
  88. package/dist/data/ai/ChatWindow.d.ts.map +1 -1
  89. package/dist/data/ai/ChatWindow.header.cjs +104 -0
  90. package/dist/data/ai/ChatWindow.header.cjs.map +1 -0
  91. package/dist/data/ai/ChatWindow.header.d.ts +29 -0
  92. package/dist/data/ai/ChatWindow.header.d.ts.map +1 -0
  93. package/dist/data/ai/ChatWindow.header.js +104 -0
  94. package/dist/data/ai/ChatWindow.header.js.map +1 -0
  95. package/dist/data/ai/ChatWindow.js +107 -172
  96. package/dist/data/ai/ChatWindow.js.map +1 -1
  97. package/dist/data/ai/ChatWindow.placement.cjs +95 -0
  98. package/dist/data/ai/ChatWindow.placement.cjs.map +1 -0
  99. package/dist/data/ai/ChatWindow.placement.d.ts +32 -0
  100. package/dist/data/ai/ChatWindow.placement.d.ts.map +1 -0
  101. package/dist/data/ai/ChatWindow.placement.js +95 -0
  102. package/dist/data/ai/ChatWindow.placement.js.map +1 -0
  103. package/dist/data/ai/SessionChatComposer.cjs +3 -1
  104. package/dist/data/ai/SessionChatComposer.cjs.map +1 -1
  105. package/dist/data/ai/SessionChatComposer.d.ts +4 -1
  106. package/dist/data/ai/SessionChatComposer.d.ts.map +1 -1
  107. package/dist/data/ai/SessionChatComposer.js +3 -1
  108. package/dist/data/ai/SessionChatComposer.js.map +1 -1
  109. package/dist/data/ai/SessionInspector.cjs +68 -72
  110. package/dist/data/ai/SessionInspector.cjs.map +1 -1
  111. package/dist/data/ai/SessionInspector.composer-slot.cjs +8 -0
  112. package/dist/data/ai/SessionInspector.composer-slot.cjs.map +1 -0
  113. package/dist/data/ai/SessionInspector.composer-slot.d.ts +9 -0
  114. package/dist/data/ai/SessionInspector.composer-slot.d.ts.map +1 -0
  115. package/dist/data/ai/SessionInspector.composer-slot.js +8 -0
  116. package/dist/data/ai/SessionInspector.composer-slot.js.map +1 -0
  117. package/dist/data/ai/SessionInspector.d.ts +6 -1
  118. package/dist/data/ai/SessionInspector.d.ts.map +1 -1
  119. package/dist/data/ai/SessionInspector.js +69 -73
  120. package/dist/data/ai/SessionInspector.js.map +1 -1
  121. package/dist/data/ai/SessionInspector.legacy.cjs +41 -42
  122. package/dist/data/ai/SessionInspector.legacy.cjs.map +1 -1
  123. package/dist/data/ai/SessionInspector.legacy.d.ts.map +1 -1
  124. package/dist/data/ai/SessionInspector.legacy.js +41 -42
  125. package/dist/data/ai/SessionInspector.legacy.js.map +1 -1
  126. package/dist/data/ai/SessionInspector.remote-view.cjs +14 -9
  127. package/dist/data/ai/SessionInspector.remote-view.cjs.map +1 -1
  128. package/dist/data/ai/SessionInspector.remote-view.d.ts +6 -2
  129. package/dist/data/ai/SessionInspector.remote-view.d.ts.map +1 -1
  130. package/dist/data/ai/SessionInspector.remote-view.js +14 -9
  131. package/dist/data/ai/SessionInspector.remote-view.js.map +1 -1
  132. package/dist/data/ai/SessionInspector.remote.cjs +84 -16
  133. package/dist/data/ai/SessionInspector.remote.cjs.map +1 -1
  134. package/dist/data/ai/SessionInspector.remote.d.ts +42 -7
  135. package/dist/data/ai/SessionInspector.remote.d.ts.map +1 -1
  136. package/dist/data/ai/SessionInspector.remote.js +86 -18
  137. package/dist/data/ai/SessionInspector.remote.js.map +1 -1
  138. package/dist/data/ai/SessionInspector.transcript.cjs +5 -0
  139. package/dist/data/ai/SessionInspector.transcript.cjs.map +1 -1
  140. package/dist/data/ai/SessionInspector.transcript.d.ts.map +1 -1
  141. package/dist/data/ai/SessionInspector.transcript.js +5 -0
  142. package/dist/data/ai/SessionInspector.transcript.js.map +1 -1
  143. package/dist/data/ai/SessionTail.cjs +63 -0
  144. package/dist/data/ai/SessionTail.cjs.map +1 -0
  145. package/dist/data/ai/SessionTail.d.ts +14 -0
  146. package/dist/data/ai/SessionTail.d.ts.map +1 -0
  147. package/dist/data/ai/SessionTail.js +63 -0
  148. package/dist/data/ai/SessionTail.js.map +1 -0
  149. package/dist/data/ai/SessionViewer.cjs +10 -3
  150. package/dist/data/ai/SessionViewer.cjs.map +1 -1
  151. package/dist/data/ai/SessionViewer.d.ts +7 -1
  152. package/dist/data/ai/SessionViewer.d.ts.map +1 -1
  153. package/dist/data/ai/SessionViewer.js +10 -3
  154. package/dist/data/ai/SessionViewer.js.map +1 -1
  155. package/dist/data/ai/SessionViewer.unified.d.ts +14 -0
  156. package/dist/data/ai/SessionViewer.unified.d.ts.map +1 -1
  157. package/dist/data/ai/index.d.ts +4 -3
  158. package/dist/data/ai/index.d.ts.map +1 -1
  159. package/dist/data/ai/use-session-scroll.cjs +30 -6
  160. package/dist/data/ai/use-session-scroll.cjs.map +1 -1
  161. package/dist/data/ai/use-session-scroll.d.ts +7 -0
  162. package/dist/data/ai/use-session-scroll.d.ts.map +1 -1
  163. package/dist/data/ai/use-session-scroll.js +30 -6
  164. package/dist/data/ai/use-session-scroll.js.map +1 -1
  165. package/dist/data/call-graph/CallGraph.cjs +50 -31
  166. package/dist/data/call-graph/CallGraph.cjs.map +1 -1
  167. package/dist/data/call-graph/CallGraph.d.ts +10 -1
  168. package/dist/data/call-graph/CallGraph.d.ts.map +1 -1
  169. package/dist/data/call-graph/CallGraph.js +52 -33
  170. package/dist/data/call-graph/CallGraph.js.map +1 -1
  171. package/dist/data/call-graph/CallGraphMembers.cjs +5 -4
  172. package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -1
  173. package/dist/data/call-graph/CallGraphMembers.d.ts +1 -1
  174. package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -1
  175. package/dist/data/call-graph/CallGraphMembers.js +5 -4
  176. package/dist/data/call-graph/CallGraphMembers.js.map +1 -1
  177. package/dist/data/call-graph/CallGraphToolbar.cjs +17 -5
  178. package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -1
  179. package/dist/data/call-graph/CallGraphToolbar.d.ts +10 -0
  180. package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -1
  181. package/dist/data/call-graph/CallGraphToolbar.js +18 -6
  182. package/dist/data/call-graph/CallGraphToolbar.js.map +1 -1
  183. package/dist/data/call-graph/call-graph-model.cjs +169 -18
  184. package/dist/data/call-graph/call-graph-model.cjs.map +1 -1
  185. package/dist/data/call-graph/call-graph-model.d.ts +40 -4
  186. package/dist/data/call-graph/call-graph-model.d.ts.map +1 -1
  187. package/dist/data/call-graph/call-graph-model.js +170 -19
  188. package/dist/data/call-graph/call-graph-model.js.map +1 -1
  189. package/dist/data/call-graph/call-graph-vocabulary.cjs +19 -1
  190. package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -1
  191. package/dist/data/call-graph/call-graph-vocabulary.d.ts +4 -0
  192. package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -1
  193. package/dist/data/call-graph/call-graph-vocabulary.js +19 -1
  194. package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -1
  195. package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -1
  196. package/dist/data/call-graph/index.d.ts +2 -2
  197. package/dist/data/call-graph/index.d.ts.map +1 -1
  198. package/dist/data/call-graph/types.cjs.map +1 -1
  199. package/dist/data/call-graph/types.d.ts +7 -2
  200. package/dist/data/call-graph/types.d.ts.map +1 -1
  201. package/dist/data/call-graph/types.js.map +1 -1
  202. package/dist/data/chat/Chat.cjs +1 -1
  203. package/dist/data/chat/Chat.cjs.map +1 -1
  204. package/dist/data/chat/Chat.js +1 -1
  205. package/dist/data/chat/Chat.js.map +1 -1
  206. package/dist/data/chat/ChatRuntimeToolbar.cjs +4 -4
  207. package/dist/data/chat/ChatRuntimeToolbar.cjs.map +1 -1
  208. package/dist/data/chat/ChatRuntimeToolbar.d.ts.map +1 -1
  209. package/dist/data/chat/ChatRuntimeToolbar.js +4 -4
  210. package/dist/data/chat/ChatRuntimeToolbar.js.map +1 -1
  211. package/dist/data/chat/PromptInput.cjs +4 -2
  212. package/dist/data/chat/PromptInput.cjs.map +1 -1
  213. package/dist/data/chat/PromptInput.d.ts +5 -1
  214. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  215. package/dist/data/chat/PromptInput.js +4 -2
  216. package/dist/data/chat/PromptInput.js.map +1 -1
  217. package/dist/data/data-access.cjs +8 -0
  218. package/dist/data/data-access.cjs.map +1 -0
  219. package/dist/data/data-access.d.ts +5 -0
  220. package/dist/data/data-access.d.ts.map +1 -0
  221. package/dist/data/data-access.js +8 -0
  222. package/dist/data/data-access.js.map +1 -0
  223. package/dist/data/diagnostics/ErrorDetails.cjs +13 -2
  224. package/dist/data/diagnostics/ErrorDetails.cjs.map +1 -1
  225. package/dist/data/diagnostics/ErrorDetails.d.ts +3 -1
  226. package/dist/data/diagnostics/ErrorDetails.d.ts.map +1 -1
  227. package/dist/data/diagnostics/ErrorDetails.js +14 -3
  228. package/dist/data/diagnostics/ErrorDetails.js.map +1 -1
  229. package/dist/data/diagnostics/FrameSourceWindow.cjs +3 -2
  230. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
  231. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
  232. package/dist/data/diagnostics/FrameSourceWindow.js +3 -2
  233. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
  234. package/dist/data/diagnostics/error-diagnostics.cjs +5 -0
  235. package/dist/data/diagnostics/error-diagnostics.cjs.map +1 -1
  236. package/dist/data/diagnostics/error-diagnostics.d.ts +1 -0
  237. package/dist/data/diagnostics/error-diagnostics.d.ts.map +1 -1
  238. package/dist/data/diagnostics/error-diagnostics.js +6 -1
  239. package/dist/data/diagnostics/error-diagnostics.js.map +1 -1
  240. package/dist/data/git/GitChangeList.cjs +6 -2
  241. package/dist/data/git/GitChangeList.cjs.map +1 -1
  242. package/dist/data/git/GitChangeList.d.ts +6 -1
  243. package/dist/data/git/GitChangeList.d.ts.map +1 -1
  244. package/dist/data/git/GitChangeList.js +6 -2
  245. package/dist/data/git/GitChangeList.js.map +1 -1
  246. package/dist/data/graph-columns-layout.cjs +47 -16
  247. package/dist/data/graph-columns-layout.cjs.map +1 -1
  248. package/dist/data/graph-columns-layout.d.ts +34 -0
  249. package/dist/data/graph-columns-layout.d.ts.map +1 -1
  250. package/dist/data/graph-columns-layout.js +47 -16
  251. package/dist/data/graph-columns-layout.js.map +1 -1
  252. package/dist/data/graph-diagram-model.cjs +5 -0
  253. package/dist/data/graph-diagram-model.cjs.map +1 -1
  254. package/dist/data/graph-diagram-model.d.ts +19 -0
  255. package/dist/data/graph-diagram-model.d.ts.map +1 -1
  256. package/dist/data/graph-diagram-model.js +5 -0
  257. package/dist/data/graph-diagram-model.js.map +1 -1
  258. package/dist/data/graph-group-window.cjs +60 -0
  259. package/dist/data/graph-group-window.cjs.map +1 -0
  260. package/dist/data/graph-group-window.d.ts +33 -0
  261. package/dist/data/graph-group-window.d.ts.map +1 -0
  262. package/dist/data/graph-group-window.js +60 -0
  263. package/dist/data/graph-group-window.js.map +1 -0
  264. package/dist/data/har/HarPanel.cjs +3 -1
  265. package/dist/data/har/HarPanel.cjs.map +1 -1
  266. package/dist/data/har/HarPanel.d.ts +4 -1
  267. package/dist/data/har/HarPanel.d.ts.map +1 -1
  268. package/dist/data/har/HarPanel.js +3 -1
  269. package/dist/data/har/HarPanel.js.map +1 -1
  270. package/dist/data/test-runner/Expectation.cjs +57 -0
  271. package/dist/data/test-runner/Expectation.cjs.map +1 -0
  272. package/dist/data/test-runner/Expectation.d.ts +19 -0
  273. package/dist/data/test-runner/Expectation.d.ts.map +1 -0
  274. package/dist/data/test-runner/Expectation.js +57 -0
  275. package/dist/data/test-runner/Expectation.js.map +1 -0
  276. package/dist/data/test-runner/MarkIcons.cjs +38 -0
  277. package/dist/data/test-runner/MarkIcons.cjs.map +1 -0
  278. package/dist/data/test-runner/MarkIcons.d.ts +15 -0
  279. package/dist/data/test-runner/MarkIcons.d.ts.map +1 -0
  280. package/dist/data/test-runner/MarkIcons.js +38 -0
  281. package/dist/data/test-runner/MarkIcons.js.map +1 -0
  282. package/dist/data/test-runner/TestDetailPanel.cjs +74 -42
  283. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
  284. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
  285. package/dist/data/test-runner/TestDetailPanel.js +76 -44
  286. package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
  287. package/dist/data/test-runner/TestFailureDetail.cjs +23 -27
  288. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -1
  289. package/dist/data/test-runner/TestFailureDetail.d.ts +2 -2
  290. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -1
  291. package/dist/data/test-runner/TestFailureDetail.js +23 -27
  292. package/dist/data/test-runner/TestFailureDetail.js.map +1 -1
  293. package/dist/data/test-runner/TestFilterBar.cjs +87 -93
  294. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -1
  295. package/dist/data/test-runner/TestFilterBar.d.ts +3 -2
  296. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -1
  297. package/dist/data/test-runner/TestFilterBar.js +89 -95
  298. package/dist/data/test-runner/TestFilterBar.js.map +1 -1
  299. package/dist/data/test-runner/TestRunSummary.cjs +35 -21
  300. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
  301. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
  302. package/dist/data/test-runner/TestRunSummary.js +35 -21
  303. package/dist/data/test-runner/TestRunSummary.js.map +1 -1
  304. package/dist/data/test-runner/TestRunner.cjs +52 -30
  305. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  306. package/dist/data/test-runner/TestRunner.d.ts +3 -2
  307. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  308. package/dist/data/test-runner/TestRunner.js +52 -30
  309. package/dist/data/test-runner/TestRunner.js.map +1 -1
  310. package/dist/data/test-runner/TestTree.cjs +5 -2
  311. package/dist/data/test-runner/TestTree.cjs.map +1 -1
  312. package/dist/data/test-runner/TestTree.d.ts +5 -2
  313. package/dist/data/test-runner/TestTree.d.ts.map +1 -1
  314. package/dist/data/test-runner/TestTree.js +5 -2
  315. package/dist/data/test-runner/TestTree.js.map +1 -1
  316. package/dist/data/test-runner/TestTreeNode.cjs +27 -19
  317. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
  318. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
  319. package/dist/data/test-runner/TestTreeNode.js +28 -20
  320. package/dist/data/test-runner/TestTreeNode.js.map +1 -1
  321. package/dist/data/test-runner/adapter.cjs +11 -0
  322. package/dist/data/test-runner/adapter.cjs.map +1 -1
  323. package/dist/data/test-runner/adapter.d.ts +18 -7
  324. package/dist/data/test-runner/adapter.d.ts.map +1 -1
  325. package/dist/data/test-runner/adapter.js +11 -0
  326. package/dist/data/test-runner/adapter.js.map +1 -1
  327. package/dist/data/test-runner/copy.cjs +114 -0
  328. package/dist/data/test-runner/copy.cjs.map +1 -0
  329. package/dist/data/test-runner/copy.d.ts +47 -0
  330. package/dist/data/test-runner/copy.d.ts.map +1 -0
  331. package/dist/data/test-runner/copy.js +114 -0
  332. package/dist/data/test-runner/copy.js.map +1 -0
  333. package/dist/data/test-runner/index.d.ts +5 -2
  334. package/dist/data/test-runner/index.d.ts.map +1 -1
  335. package/dist/data/test-runner/marks.cjs +143 -0
  336. package/dist/data/test-runner/marks.cjs.map +1 -0
  337. package/dist/data/test-runner/marks.d.ts +30 -0
  338. package/dist/data/test-runner/marks.d.ts.map +1 -0
  339. package/dist/data/test-runner/marks.js +143 -0
  340. package/dist/data/test-runner/marks.js.map +1 -0
  341. package/dist/data/test-runner/status.cjs +27 -44
  342. package/dist/data/test-runner/status.cjs.map +1 -1
  343. package/dist/data/test-runner/status.d.ts +12 -3
  344. package/dist/data/test-runner/status.d.ts.map +1 -1
  345. package/dist/data/test-runner/status.js +27 -44
  346. package/dist/data/test-runner/status.js.map +1 -1
  347. package/dist/data/use-group-windows.cjs +50 -0
  348. package/dist/data/use-group-windows.cjs.map +1 -0
  349. package/dist/data/use-group-windows.d.ts +27 -0
  350. package/dist/data/use-group-windows.d.ts.map +1 -0
  351. package/dist/data/use-group-windows.js +50 -0
  352. package/dist/data/use-group-windows.js.map +1 -0
  353. package/dist/data/verification/adapterViews.cjs +16 -41
  354. package/dist/data/verification/adapterViews.cjs.map +1 -1
  355. package/dist/data/verification/adapterViews.d.ts.map +1 -1
  356. package/dist/data/verification/adapterViews.js +16 -41
  357. package/dist/data/verification/adapterViews.js.map +1 -1
  358. package/dist/data/verification/adapters.cjs +115 -3
  359. package/dist/data/verification/adapters.cjs.map +1 -1
  360. package/dist/data/verification/adapters.d.ts.map +1 -1
  361. package/dist/data/verification/adapters.js +115 -3
  362. package/dist/data/verification/adapters.js.map +1 -1
  363. package/dist/data/verification/verifyReportTests.cjs.map +1 -1
  364. package/dist/data/verification/verifyReportTests.js.map +1 -1
  365. package/dist/data/version-info.cjs +3 -3
  366. package/dist/data/version-info.js +3 -3
  367. package/dist/data.cjs +54 -3
  368. package/dist/data.cjs.map +1 -1
  369. package/dist/data.d.ts +6 -2
  370. package/dist/data.d.ts.map +1 -1
  371. package/dist/data.js +58 -7
  372. package/dist/data.js.map +1 -1
  373. package/dist/hooks/event-hub.cjs +229 -0
  374. package/dist/hooks/event-hub.cjs.map +1 -0
  375. package/dist/hooks/event-hub.d.ts +26 -0
  376. package/dist/hooks/event-hub.d.ts.map +1 -0
  377. package/dist/hooks/event-hub.js +229 -0
  378. package/dist/hooks/event-hub.js.map +1 -0
  379. package/dist/hooks.cjs +2 -0
  380. package/dist/hooks.cjs.map +1 -1
  381. package/dist/hooks.d.ts +1 -0
  382. package/dist/hooks.d.ts.map +1 -1
  383. package/dist/hooks.js +2 -0
  384. package/dist/hooks.js.map +1 -1
  385. package/dist/icons/components/UiLogoEslint.cjs +10 -0
  386. package/dist/icons/components/UiLogoEslint.cjs.map +1 -0
  387. package/dist/icons/components/UiLogoEslint.d.ts +9 -0
  388. package/dist/icons/components/UiLogoEslint.d.ts.map +1 -0
  389. package/dist/icons/components/UiLogoEslint.js +10 -0
  390. package/dist/icons/components/UiLogoEslint.js.map +1 -0
  391. package/dist/icons/components/UiLogoGo.cjs +10 -0
  392. package/dist/icons/components/UiLogoGo.cjs.map +1 -0
  393. package/dist/icons/components/UiLogoGo.d.ts +9 -0
  394. package/dist/icons/components/UiLogoGo.d.ts.map +1 -0
  395. package/dist/icons/components/UiLogoGo.js +10 -0
  396. package/dist/icons/components/UiLogoGo.js.map +1 -0
  397. package/dist/icons/components/UiLogoJest.cjs +10 -0
  398. package/dist/icons/components/UiLogoJest.cjs.map +1 -0
  399. package/dist/icons/components/UiLogoJest.d.ts +9 -0
  400. package/dist/icons/components/UiLogoJest.d.ts.map +1 -0
  401. package/dist/icons/components/UiLogoJest.js +10 -0
  402. package/dist/icons/components/UiLogoJest.js.map +1 -0
  403. package/dist/icons/components/UiLogoMarkdown.cjs +10 -0
  404. package/dist/icons/components/UiLogoMarkdown.cjs.map +1 -0
  405. package/dist/icons/components/UiLogoMarkdown.d.ts +9 -0
  406. package/dist/icons/components/UiLogoMarkdown.d.ts.map +1 -0
  407. package/dist/icons/components/UiLogoMarkdown.js +10 -0
  408. package/dist/icons/components/UiLogoMarkdown.js.map +1 -0
  409. package/dist/icons/components/UiLogoOxc.cjs +10 -0
  410. package/dist/icons/components/UiLogoOxc.cjs.map +1 -0
  411. package/dist/icons/components/UiLogoOxc.d.ts +9 -0
  412. package/dist/icons/components/UiLogoOxc.d.ts.map +1 -0
  413. package/dist/icons/components/UiLogoOxc.js +10 -0
  414. package/dist/icons/components/UiLogoOxc.js.map +1 -0
  415. package/dist/icons/components/UiLogoPlaywright.cjs +10 -0
  416. package/dist/icons/components/UiLogoPlaywright.cjs.map +1 -0
  417. package/dist/icons/components/UiLogoPlaywright.d.ts +9 -0
  418. package/dist/icons/components/UiLogoPlaywright.d.ts.map +1 -0
  419. package/dist/icons/components/UiLogoPlaywright.js +10 -0
  420. package/dist/icons/components/UiLogoPlaywright.js.map +1 -0
  421. package/dist/icons/components/UiLogoPython.cjs +10 -0
  422. package/dist/icons/components/UiLogoPython.cjs.map +1 -0
  423. package/dist/icons/components/UiLogoPython.d.ts +9 -0
  424. package/dist/icons/components/UiLogoPython.d.ts.map +1 -0
  425. package/dist/icons/components/UiLogoPython.js +10 -0
  426. package/dist/icons/components/UiLogoPython.js.map +1 -0
  427. package/dist/icons/components/UiLogoReact.cjs +10 -0
  428. package/dist/icons/components/UiLogoReact.cjs.map +1 -0
  429. package/dist/icons/components/UiLogoReact.d.ts +9 -0
  430. package/dist/icons/components/UiLogoReact.d.ts.map +1 -0
  431. package/dist/icons/components/UiLogoReact.js +10 -0
  432. package/dist/icons/components/UiLogoReact.js.map +1 -0
  433. package/dist/icons/components/UiLogoRuff.cjs +10 -0
  434. package/dist/icons/components/UiLogoRuff.cjs.map +1 -0
  435. package/dist/icons/components/UiLogoRuff.d.ts +9 -0
  436. package/dist/icons/components/UiLogoRuff.d.ts.map +1 -0
  437. package/dist/icons/components/UiLogoRuff.js +10 -0
  438. package/dist/icons/components/UiLogoRuff.js.map +1 -0
  439. package/dist/icons/components/UiLogoTypescript.cjs +10 -0
  440. package/dist/icons/components/UiLogoTypescript.cjs.map +1 -0
  441. package/dist/icons/components/UiLogoTypescript.d.ts +9 -0
  442. package/dist/icons/components/UiLogoTypescript.d.ts.map +1 -0
  443. package/dist/icons/components/UiLogoTypescript.js +10 -0
  444. package/dist/icons/components/UiLogoTypescript.js.map +1 -0
  445. package/dist/icons/components/UiLogoVitest.cjs +10 -0
  446. package/dist/icons/components/UiLogoVitest.cjs.map +1 -0
  447. package/dist/icons/components/UiLogoVitest.d.ts +9 -0
  448. package/dist/icons/components/UiLogoVitest.d.ts.map +1 -0
  449. package/dist/icons/components/UiLogoVitest.js +10 -0
  450. package/dist/icons/components/UiLogoVitest.js.map +1 -0
  451. package/dist/icons/index.d.ts +11 -0
  452. package/dist/icons/index.d.ts.map +1 -1
  453. package/dist/icons/registry.cjs +22 -0
  454. package/dist/icons/registry.cjs.map +1 -1
  455. package/dist/icons/registry.d.ts.map +1 -1
  456. package/dist/icons/registry.js +22 -0
  457. package/dist/icons/registry.js.map +1 -1
  458. package/dist/icons.cjs +22 -0
  459. package/dist/icons.cjs.map +1 -1
  460. package/dist/icons.js +22 -0
  461. package/dist/icons.js.map +1 -1
  462. package/dist/index.cjs +56 -3
  463. package/dist/index.cjs.map +1 -1
  464. package/dist/index.js +60 -7
  465. package/dist/index.js.map +1 -1
  466. package/dist/overlay/DropdownMenuSubmenu.cjs +4 -4
  467. package/dist/overlay/DropdownMenuSubmenu.cjs.map +1 -1
  468. package/dist/overlay/DropdownMenuSubmenu.js +4 -4
  469. package/dist/overlay/DropdownMenuSubmenu.js.map +1 -1
  470. package/dist/overlay/modalStack.cjs +13 -6
  471. package/dist/overlay/modalStack.cjs.map +1 -1
  472. package/dist/overlay/modalStack.d.ts.map +1 -1
  473. package/dist/overlay/modalStack.js +13 -6
  474. package/dist/overlay/modalStack.js.map +1 -1
  475. package/dist/rpc/InlineError.cjs +12 -43
  476. package/dist/rpc/InlineError.cjs.map +1 -1
  477. package/dist/rpc/InlineError.d.ts.map +1 -1
  478. package/dist/rpc/InlineError.js +13 -44
  479. package/dist/rpc/InlineError.js.map +1 -1
  480. package/dist/rpc/OperationScheduleDialog.cjs +63 -10
  481. package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
  482. package/dist/rpc/OperationScheduleDialog.d.ts +4 -0
  483. package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
  484. package/dist/rpc/OperationScheduleDialog.js +63 -10
  485. package/dist/rpc/OperationScheduleDialog.js.map +1 -1
  486. package/dist/rpc/OperationSchedules.cjs +12 -2
  487. package/dist/rpc/OperationSchedules.cjs.map +1 -1
  488. package/dist/rpc/OperationSchedules.d.ts.map +1 -1
  489. package/dist/rpc/OperationSchedules.js +12 -2
  490. package/dist/rpc/OperationSchedules.js.map +1 -1
  491. package/dist/rpc/operation-schedule-model.cjs +2 -0
  492. package/dist/rpc/operation-schedule-model.cjs.map +1 -1
  493. package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
  494. package/dist/rpc/operation-schedule-model.js +2 -0
  495. package/dist/rpc/operation-schedule-model.js.map +1 -1
  496. package/dist/rpc/operation-schedule-types.d.ts +7 -1
  497. package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
  498. package/dist/styles.css +1 -1
  499. package/package.json +1 -1
  500. package/dist/data/test-runner/frameworkIcon.cjs +0 -27
  501. package/dist/data/test-runner/frameworkIcon.cjs.map +0 -1
  502. package/dist/data/test-runner/frameworkIcon.d.ts +0 -3
  503. package/dist/data/test-runner/frameworkIcon.d.ts.map +0 -1
  504. package/dist/data/test-runner/frameworkIcon.js +0 -27
  505. package/dist/data/test-runner/frameworkIcon.js.map +0 -1
@@ -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":";;;;AAoCO,MAAM,8BAA8B;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,2BAA2B,QAA4B;AACrE,SACE,WAAW,UACV,4BAAkD,SAAS,MAAM;AAEtE;AAGO,MAAM,4BAA4B;AA6ClC,SAAS,WAAW,KAAa,QAAyC;AAC/E,QAAM,SAAS,IAAI,QAAQ,GAAG;AAC9B,QAAM,OAAO,WAAW,KAAK,MAAM,IAAI,MAAM,GAAG,MAAM;AACtD,QAAM,OAAO,WAAW,KAAK,KAAK,IAAI,MAAM,MAAM;AAClD,QAAM,UAAU,KAAK,QAAQ,GAAG;AAChC,QAAM,QAAQ,IAAI,gBAAgB,YAAY,KAAK,KAAK,KAAK,MAAM,UAAU,CAAC,CAAC;AAC/E,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,EAAG,OAAM,IAAI,MAAM,OAAO,KAAK,CAAC;AACjF,SAAO,GAAG,YAAY,KAAK,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,IAAI,KAAK,GAAG,IAAI;AAC1E;AAEA,eAAsB,mBACpB,KACA,QAC8B;AAC9B,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,SAAS,EAAE,QAAQ,mBAAA;AAAA,IACnB,GAAI,SAAS,EAAE,WAAW,CAAA;AAAA,EAAC,CAC5B;AACD,QAAM,OAAO,MAAM,SAAS,KAAA;AAC5B,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,0BAA0B,GAAG,UAAU,SAAS,MAAM,MAAM,YAAY,IAAI,CAAC;AAAA,IAAA;AAAA,EAEjF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,IAAI;AAAA,EAC1B,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,WAAW,GAAG,2BAA2B,OAAO,KAAK,CAAC,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IAAA;AAAA,EAElF;AACA,MAAI,CAAC,SAAS,MAAM,GAAG;AACrB,UAAM,IAAI;AAAA,MACR,WAAW,GAAG,aAAa,MAAM,QAAQ,MAAM,IAAI,aAAa,OAAO,MAAM,CAAC;AAAA,IAAA;AAAA,EAElF;AACA,SAAO;AACT;AAKO,SAAS,qBACd,MACA,UACoB;AACpB,QAAM,OAAO,SAAS,KAAK,CAAC,YAAY,CAAC,QAAQ,WAAW;AAC5D,QAAM,UAAU,QAAQ,CAAA,GAAI,OAAO,CAAC,YAAY,EAAE,QAAQ,QAAQ,YAAY;AAC9E,aAAW,WAAW,UAAU;AAC9B,UAAM,QAAQ,OAAO,UAAU,CAAC,aAAa,SAAS,OAAO,QAAQ,EAAE;AACvE,QAAI,UAAU,GAAI,QAAO,KAAK,OAAO;AAAA,QAChC,QAAO,KAAK,IAAI;AAAA,EACvB;AACA,SAAO;AACT;AAIO,SAAS,kBACd,KACA;AAAA,EACE;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAIqB;AACrB,MAAI,MAAM,WAAW,KAAK,EAAE,QAAQ,GAAG;AACvC,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,IAAI,gBAAA;AACpB,QAAM,+BAAe,IAAA;AACrB,MAAI,SAAS;AACb,MAAI;AACJ,MAAI,QAA4B,EAAE,SAAS,KAAA;AAC3C,MAAI;AACJ,MAAI;AACJ,MAAI,MAAM;AACV,MAAI,aAAa;AAEjB,QAAM,OAAO,CAAC,SAA6B;AACzC,QAAI,OAAQ;AACZ,YAAQ;AACR,aAAS,IAAI;AAAA,EACf;AACA,QAAM,gBAAgB,MAAM;AAC1B,qCAAQ;AACR,aAAS;AAAA,EACX;AACA,QAAM,OAAO,CAAC,YAAoB;AAChC,kBAAA;AACA,SAAK,EAAE,GAAG,OAAO,SAAS,OAAO,OAAO,SAAS;AAAA,EACnD;AAIA,QAAM,UAAU,YAAY;;AAC1B,UAAM,UAAU,EAAE;AAClB,UAAM,YAAY;AAClB,UAAM,UAAU,MAAM,mBAAmB,WAAW,KAAK,EAAE,MAAM,SAAA,CAAU,GAAG,QAAQ,MAAM;AAC5F,QAAI,UAAU,YAAY,WAAY,QAAO;AAC7C,UAAM,aAAa,IAAI,KAAI,aAAQ,aAAR,mBAAkB,IAAI,CAAC,YAAY,QAAQ,GAAG;AACzE,UAAM,OAAO,CAAC,GAAG,SAAS,OAAA,CAAQ,EAC/B,OAAO,CAAC,UAAU,MAAM,MAAM,aAAa,CAAC,WAAW,IAAI,MAAM,QAAQ,EAAE,CAAC,EAC5E,IAAI,CAAC,UAAU,MAAM,OAAO;AAC/B,UAAM,OAAO,sBAAqB,WAAM,YAAN,mBAAe,UAAU,QAAQ,YAAY,EAAE;AACjF,UAAM,UAAU;AAAA,MACd,GAAG;AAAA,MACH,UAAU,qBAAqB,MAAM,IAAI;AAAA,IAAA;AAE3C,QAAI,OAAO,QAAQ,aAAa,UAAU;AACxC,iBAAW,KAAK,IAAI,YAAY,QAAQ,UAAU,QAAQ,QAAQ;AAAA,IACpE;AACA,UAAM,WACJ,MAAM,YAAY,YACb,aAAQ,WAAR,mBAAgB,aAAY,QAAM,aAAQ,aAAR,mBAAkB,WAAU,KAC/D,MAAM;AACZ,SAAK,EAAE,GAAG,OAAO,SAAS,SAAS,OAAO,UAAU,YAAY,OAAO;AACvE,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAqB;;AACrC,UAAM,UAAS,uBAAM,YAAN,mBAAe,aAAf,mBAA0B,OAA1B,mBAA8B;AAC7C,QAAI,UAAU,aAAa,CAAC,MAAM,YAAY,CAAC,OAAQ,QAAO,aAAa,QAAQ,QAAA;AACnF,SAAK,EAAE,GAAG,OAAO,cAAc,MAAM;AACrC,gBAAY,mBAAmB,WAAW,KAAK,EAAE,QAAQ,QAAQ,OAAO,SAAA,CAAU,GAAG,QAAQ,MAAM,EAChG,KAAK,CAAC,SAAS;AACd,YAAM,QAAQ,KAAK,YAAY,CAAA;AAG/B,YAAM,EAAE,OAAO,GAAG,KAAA,IAAS;AAC3B,YAAM,UAAU,MAAM;AACtB,WAAK;AAAA,QACH,GAAG;AAAA,QACH,GAAI,UAAU,UAAa,UAAU,YAAY,EAAE,MAAA,IAAU,CAAA;AAAA,QAC7D,cAAc;AAAA,QACd,UAAU,MAAM,WAAW;AAAA,QAC3B,SAAS,EAAE,GAAG,SAAS,UAAU,qBAAqB,OAAO,QAAQ,YAAY,EAAE,EAAA;AAAA,MAAE,CACtF;AACD,kBAAY;AAAA,IACd,CAAC,EACA,MAAM,CAAC,WAAoB;AAC1B,UAAI,OAAQ;AACZ,kBAAY,aAAa,MAAM;AAC/B,WAAK,EAAE,GAAG,OAAO,cAAc,OAAO,OAAO,WAAW;AAAA,IAC1D,CAAC,EACA,QAAQ,MAAM;AACb,kBAAY;AAAA,IACd,CAAC;AACH,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,CAAC,UAA0B;AAC3C,QAAI;AACF,aAAO,KAAK,MAAO,MAAuB,IAAc;AAAA,IAC1D,SAAS,OAAO;AACd,WAAK,eAAe,MAAM,IAAI,eAAe,GAAG,KAAK,OAAO,KAAK,CAAC,EAAE;AACpE,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UAAU,CAAC,UAAiB;AAChC,UAAM,UAAU,UAAU,KAAK;AAC/B,QAAI,YAAY,OAAW;AAC3B,QAAI,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,YAAY,CAAC,QAAQ,IAAI;AACvE,WAAK,oBAAoB,GAAG,uBAAuB,OAAQ,MAAuB,IAAI,CAAC,EAAE;AACzF;AAAA,IACF;AACA,UAAM,QAAQ;AACd,aAAS,IAAI,QAAQ,IAAI,EAAE,SAAS,OAAO,KAAK,EAAE,KAAK;AACvD,UAAM,UAAU,MAAM;AACtB,SAAK;AAAA,MACH,GAAG;AAAA,MACH,SAAS,EAAE,GAAG,SAAS,UAAU,qBAAqB,QAAQ,UAAU,CAAC,KAAK,CAAC,EAAA;AAAA,IAAE,CAClF;AAAA,EACH;AAEA,QAAM,UAAU,CAAC,UAAiB;AAChC,UAAM,QAAQ,UAAU,KAAK;AAC7B,QAAI,UAAU,OAAW;AACzB,QACE,CAAC,SAAS,KAAK,KACf,OAAO,MAAM,aAAa,YAC1B,OAAO,MAAM,oBAAoB,YACjC,OAAO,MAAM,WAAW,UACxB;AACA,WAAK,oBAAoB,GAAG,6DAA6D,OAAQ,MAAuB,IAAI,CAAC,EAAE;AAC/H;AAAA,IACF;AACA,UAAM,OAAO;AACb,SAAK;AAAA,MACH,GAAG;AAAA,MACH,SAAS;AAAA,QACP,GAAG,MAAM;AAAA,QACT,iBAAiB,KAAK;AAAA,QACtB,GAAI,OAAO,KAAK,kBAAkB,WAAW,EAAE,eAAe,KAAK,kBAAkB,CAAA;AAAA,MAAC;AAAA,IACxF,CACD;AACD,UAAM,oBAAoB,aAAa,UAAa,KAAK,WAAW;AAKpE,UAAM,gBAAgB,WAAW,UAAa,KAAK,WAAW;AAC9D,aAAS,KAAK;AACd,QAAI,8BAA8B,KAAK;AACvC,QAAI,qBAAqB,eAAe;AACtC,cAAA,EAAU,MAAM,CAAC,WAAoB;AACnC,YAAI,CAAC,OAAQ,MAAK,aAAa,MAAM,CAAC;AAAA,MACxC,CAAC;AAAA,IACH;AACA,QAAI,2BAA2B,KAAK,eAAe,EAAG,eAAA;AAAA,EACxD;AAMA,QAAM,iBAAiB,MAAM;;AAC3B,UAAM,UAAS,uBAAM,YAAN,mBAAe,aAAf,mBAAyB,OAAO,CAAC,YAAY,CAAC,QAAQ,aAAa,GAAG,QAAtE,mBAA2E;AAC1F,UAAM,WAAW,KAAK,SAAS,EAAE,QAAQ,GAAG,OAAO,OAAA,IAAW,EAAE,QAAQ,EAAA,CAAG;AAC3E,UAAM,KAAK,mBAAmB,GAAG;AACjC,aAAS;AACT,QAAI,WAAW;AACf,UAAM,UAAU,CAAC,UAAiB;AAChC,YAAM,OAAQ,MAAuB;AACrC,UAAI,OAAO,SAAS,UAAU;AAC5B,cAAM,QAAQ,UAAU,KAAK;AAC7B,YAAI,UAAU,OAAW;AACzB,cAAM,WAAW,SAAS,KAAK,KAAK,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AACpF,aAAK,kBAAkB,GAAG,uBAAuB,QAAQ,EAAE;AAC3D;AAAA,MACF;AAGA,UAAI,YAAY,GAAG,eAAe,EAAG;AACrC;AAAA,QACE,WACI,2BAA2B,GAAG,KAC9B,2CAA2C,GAAG;AAAA,MAAA;AAAA,IAEtD;AACA,OAAG,iBAAiB,SAAS,CAAC,UAAU;AACtC,iBAAW;AACX,cAAQ,KAAK;AAAA,IACf,CAAC;AACD,OAAG,iBAAiB,SAAS,CAAC,UAAU;AACtC,iBAAW;AACX,cAAQ,KAAK;AAAA,IACf,CAAC;AACD,OAAG,iBAAiB,SAAS,OAAO;AAAA,EACtC;AAEA,QAAM,SAAS,UAAU;AAAA,IACvB,CAAC,YAAY;AACX,UAAI,CAAC,QAAS,OAAM,IAAI,MAAM,WAAW,GAAG,8BAA8B;AAC1E,YAAM,YAAY,QAAQ;AAC1B,YAAM,SACJ,WAAW,cAAc,UAAa,CAAC,2BAA2B,SAAS;AAC7E,UAAI,UAAU,CAAC,OAAQ,gBAAA;AACvB,aAAO;AAAA,IACT;AAAA,IACA,CAAC,WAAoB;AACnB,UAAI,CAAC,OAAQ,MAAK,aAAa,MAAM,CAAC;AACtC,YAAM;AAAA,IACR;AAAA,EAAA;AAGF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,MAAM;AACX,eAAS;AACT,cAAQ,MAAA;AACR,oBAAA;AAAA,IACF;AAAA,EAAA;AAEJ;AAEA,MAAM,OAA2B,EAAE,SAAS,MAAA;AAC5C,MAAM,UAA8B,EAAE,SAAS,KAAA;AAUxC,SAAS,iBACd,KACA,EAAE,QAAQ,SAAA,IAAmC,CAAA,GAC1B;AACnB,QAAM,qBAAqBA,YAAAA,sBAAA;AAC3B,QAAM,aAAaC,MAAAA,OAAO,kBAAkB;AAC5C,aAAW,UAAU;AACrB,QAAM,YAAYA,MAAAA,OAA4B,MAAS;AACvD,QAAM,CAAC,UAAU,WAAW,IAAIC,eAAA;AAKhCC,QAAAA,UAAU,MAAM;AACd,QAAI,QAAQ,OAAW,QAAO;AAC9B,gBAAY,EAAE,KAAK,OAAO,QAAA,CAAS;AACnC,UAAM,SAAS,kBAAkB,KAAK;AAAA,MACpC,GAAI,WAAW,SAAY,CAAA,IAAK,EAAE,OAAA;AAAA,MAClC,GAAI,aAAa,SAAY,CAAA,IAAK,EAAE,SAAA;AAAA,MACpC,oBAAoB,WAAW;AAAA,MAC/B,UAAU,CAACC,WAAU,YAAY,EAAE,KAAK,OAAAA,QAAO;AAAA,IAAA,CAChD;AACD,cAAU,UAAU;AAEpB,WAAO,OAAO,MAAM,MAAM,MAAS;AACnC,WAAO,MAAM;AACX,aAAO,MAAA;AACP,UAAI,UAAU,YAAY,OAAQ,WAAU,UAAU;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,KAAK,QAAQ,QAAQ,CAAC;AAE1B,QAAM,YAAYC,kBAAY,MAAA;;AAAM,4BAAU,YAAV,mBAAmB,gBAAe,QAAQ,QAAA;AAAA,KAAW,EAAE;AAC3F,QAAM,QAAQ,QAAQ,SAAY,QAAO,qCAAU,SAAQ,MAAM,SAAS,QAAQ;AAClF,SAAOC,MAAAA,QAAQ,OAAO,EAAE,GAAG,OAAO,cAAc,CAAC,OAAO,SAAS,CAAC;AACpE;AAWO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,cAAcL,MAAAA,OAAO,QAAQ;AACnC,cAAY,UAAU;AACtB,QAAM,EAAE,SAAS,MAAA,IAAU;AAC3BE,QAAAA,UAAU,MAAM;;AACd,QAAI,CAAC,QAAS;AACd,sBAAY,YAAZ;AAAA;AAAA,MACE,UAAU,SACN,EAAE,QAAQ,SAAS,OAAO,IAAI,MAAM,KAAK,MACzC,EAAE,QAAQ,UAAU,YAAY,QAAA;AAAA;AAAA,EAExC,GAAG,CAAC,SAAS,SAAS,KAAK,CAAC;AAC9B;AAWO,SAAS,oBACd,YACA,UAAgC,IACT;AACvB,QAAM,UAAU,yCAAY,SAAS;AAAA,IACnC,CAAC,SAAS,KAAK,OAAO,WAAW;AAAA;AAEnC,QAAM,MAAM,WAAW,CAAC,QAAQ,UAAU,QAAQ,MAAM;AACxD,QAAM,SAAS,iBAAiB,KAAK,OAAO;AAC5C,SAAOG,MAAAA,QAAQ,MAAM;AACnB,QAAI,CAAC,WAAY,QAAO;AACxB,QAAI,QAAQ,UAAa,CAAC,gBAAgB,EAAE,YAAY,SAAS,MAAA;AACjE,UAAM,SAAS;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAA,IAAU,CAAA;AAAA,IAAC;AAEhD,QAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,UAAM,UAAU,OAAO;AACvB,WAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY;AAAA,QACV,GAAG;AAAA,QACH,UAAU,WAAW,SAAS;AAAA,UAAI,CAAC,SACjC,SAAS,UAAU,EAAE,GAAG,MAAM,YAAY;AAAA,QAAA;AAAA,MAC5C;AAAA,IACF;AAAA,EAEJ,GAAG,CAAC,YAAY,SAAS,KAAK,MAAM,CAAC;AACvC;AAEA,SAAS,YAAY,MAAc;AACjC,QAAM,OAAO,KAAK,KAAA;AAClB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,QAAI,SAAS,MAAM,KAAK,OAAO,OAAO,UAAU,iBAAiB,OAAO;AAAA,EAC1E,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAEA,SAAS,aAAa,QAAiB;AACrC,SAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACjE;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;;;;;;;"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"SessionInspector.remote.js","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":[],"mappings":"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"}
1
+ {"version":3,"file":"SessionInspector.remote.js","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":["state"],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
+ const SessionInspector_panelParts = require("./SessionInspector.panel-parts.cjs");
5
6
  const utils = require("../../lib/utils.cjs");
6
7
  const SessionInspector_model = require("./SessionInspector.model.cjs");
7
8
  const SessionViewer = require("./SessionViewer.cjs");
@@ -19,6 +20,7 @@ function SessionTranscript({
19
20
  session,
20
21
  viewerProps
21
22
  }) {
23
+ var _a, _b;
22
24
  const turns = session.turns ?? [];
23
25
  const palette = react.useMemo(
24
26
  () => SessionInspector_model.createTurnPalette(
@@ -37,6 +39,9 @@ function SessionTranscript({
37
39
  );
38
40
  const viewerClassName = utils.cn("min-h-0 flex-1", viewerProps == null ? void 0 : viewerProps.className);
39
41
  const scrollResetKey = session.id ?? (viewerProps == null ? void 0 : viewerProps.scrollResetKey);
42
+ if (session.detailAvailable === false && !((_a = session.messages) == null ? void 0 : _a.length) && !((_b = session.events) == null ? void 0 : _b.length)) {
43
+ return /* @__PURE__ */ jsxRuntime.jsx(SessionInspector_panelParts.EmptyState, { children: "Transcript unavailable" });
44
+ }
40
45
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full min-h-0 flex-col", children: /* @__PURE__ */ jsxRuntime.jsx(
41
46
  SessionViewer.SessionViewer,
42
47
  {
@@ -1 +1 @@
1
- {"version":3,"file":"SessionInspector.transcript.cjs","sources":["../../../src/data/ai/SessionInspector.transcript.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport {\n createTurnPalette,\n shortSessionId,\n type TurnPalette,\n} from \"./SessionInspector.model\";\nimport {\n SessionViewer,\n type SessionEvent,\n type SessionViewerProps,\n} from \"./SessionViewer\";\nimport type { SessionTurn, UnifiedSessionInput } from \"./SessionViewer.unified\";\n\ntype TurnBadge = {\n id: string;\n label: string;\n color: TurnPalette;\n className: string;\n};\n\nconst TURN_COLOR_CLASS: Record<TurnPalette, string> = {\n sky: \"border-sky-300 bg-sky-100 text-sky-800 [[data-theme=dark]_&]:border-sky-700 [[data-theme=dark]_&]:bg-sky-950/60 [[data-theme=dark]_&]:text-sky-300\",\n violet:\n \"border-violet-300 bg-violet-100 text-violet-800 [[data-theme=dark]_&]:border-violet-700 [[data-theme=dark]_&]:bg-violet-950/60 [[data-theme=dark]_&]:text-violet-300\",\n emerald:\n \"border-emerald-300 bg-emerald-100 text-emerald-800 [[data-theme=dark]_&]:border-emerald-700 [[data-theme=dark]_&]:bg-emerald-950/60 [[data-theme=dark]_&]:text-emerald-300\",\n orange:\n \"border-orange-300 bg-orange-100 text-orange-800 [[data-theme=dark]_&]:border-orange-700 [[data-theme=dark]_&]:bg-orange-950/60 [[data-theme=dark]_&]:text-orange-300\",\n rose: \"border-rose-300 bg-rose-100 text-rose-800 [[data-theme=dark]_&]:border-rose-700 [[data-theme=dark]_&]:bg-rose-950/60 [[data-theme=dark]_&]:text-rose-300\",\n indigo:\n \"border-indigo-300 bg-indigo-100 text-indigo-800 [[data-theme=dark]_&]:border-indigo-700 [[data-theme=dark]_&]:bg-indigo-950/60 [[data-theme=dark]_&]:text-indigo-300\",\n teal: \"border-teal-300 bg-teal-100 text-teal-800 [[data-theme=dark]_&]:border-teal-700 [[data-theme=dark]_&]:bg-teal-950/60 [[data-theme=dark]_&]:text-teal-300\",\n fuchsia:\n \"border-fuchsia-300 bg-fuchsia-100 text-fuchsia-800 [[data-theme=dark]_&]:border-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-950/60 [[data-theme=dark]_&]:text-fuchsia-300\",\n};\n\nexport function SessionTranscript({\n session,\n viewerProps,\n}: {\n session: UnifiedSessionInput;\n viewerProps?: Omit<SessionViewerProps, \"session\">;\n}) {\n const turns = session.turns ?? [];\n const palette = useMemo(\n () =>\n createTurnPalette(\n session.id,\n turns.map((turn) => turn.id),\n ),\n [session.id, turns],\n );\n const turnBadges = useMemo(\n () => buildTurnBadges(turns, palette),\n [palette, turns],\n );\n const turnBadgeById = useMemo(\n () => new Map(turnBadges.map((badge) => [badge.id, badge])),\n [turnBadges],\n );\n const viewerClassName = cn(\"min-h-0 flex-1\", viewerProps?.className);\n const scrollResetKey = session.id ?? viewerProps?.scrollResetKey;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <SessionViewer\n session={session}\n scrollable\n {...viewerProps}\n windowSize={viewerProps?.windowSize ?? 10}\n batchSize={viewerProps?.batchSize ?? 10}\n {...(scrollResetKey !== undefined\n ? { scrollResetKey }\n : {})}\n className={viewerClassName}\n showHeader={false}\n showContextMeter={false}\n showRowMetadata={false}\n renderMessageBadge={(event: SessionEvent) => {\n const badge = event.turnId\n ? turnBadgeById.get(event.turnId)\n : undefined;\n return badge ? (\n <TranscriptTurnBadge badge={badge} placement=\"message\" />\n ) : null;\n }}\n headerActions={\n <>\n {viewerProps?.headerActions}\n <TurnBadgeLegend badges={turnBadges} />\n </>\n }\n />\n </div>\n );\n}\n\nfunction buildTurnBadges(\n turns: SessionTurn[],\n palette: Record<string, TurnPalette>,\n) {\n return turns.map((turn): TurnBadge => {\n const color = palette[turn.id] ?? \"sky\";\n return {\n id: turn.id,\n label: shortSessionId(turn.id),\n color,\n className: TURN_COLOR_CLASS[color],\n };\n });\n}\n\nfunction TurnBadgeLegend({ badges }: { badges: TurnBadge[] }) {\n return (\n <div className=\"flex max-w-full flex-wrap gap-1\" aria-label=\"Turn colors\">\n {badges.map((badge) => (\n <TranscriptTurnBadge key={badge.id} badge={badge} placement=\"legend\" />\n ))}\n </div>\n );\n}\n\nfunction TranscriptTurnBadge({\n badge,\n placement,\n}: {\n badge: TurnBadge;\n placement: \"legend\" | \"message\";\n}) {\n return (\n <span\n data-testid={\n placement === \"legend\" ? \"transcript-turn-badge\" : \"message-turn-badge\"\n }\n data-turn-color={badge.color}\n title={`Turn ${badge.id}`}\n className={cn(\n \"inline-flex shrink-0 rounded border px-1 py-px font-mono text-[9px] font-semibold leading-none\",\n placement === \"message\" && \"mt-1\",\n badge.className,\n )}\n >\n {badge.label}\n </span>\n );\n}\n"],"names":["useMemo","createTurnPalette","cn","jsx","SessionViewer","jsxs","Fragment","shortSessionId"],"mappings":";;;;;;;AAqBA,MAAM,mBAAgD;AAAA,EACpD,KAAK;AAAA,EACL,QACE;AAAA,EACF,SACE;AAAA,EACF,QACE;AAAA,EACF,MAAM;AAAA,EACN,QACE;AAAA,EACF,MAAM;AAAA,EACN,SACE;AACJ;AAEO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AACF,GAGG;AACD,QAAM,QAAQ,QAAQ,SAAS,CAAA;AAC/B,QAAM,UAAUA,MAAAA;AAAAA,IACd,MACEC,uBAAAA;AAAAA,MACE,QAAQ;AAAA,MACR,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,IAAA;AAAA,IAE/B,CAAC,QAAQ,IAAI,KAAK;AAAA,EAAA;AAEpB,QAAM,aAAaD,MAAAA;AAAAA,IACjB,MAAM,gBAAgB,OAAO,OAAO;AAAA,IACpC,CAAC,SAAS,KAAK;AAAA,EAAA;AAEjB,QAAM,gBAAgBA,MAAAA;AAAAA,IACpB,MAAM,IAAI,IAAI,WAAW,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AAAA,IAC1D,CAAC,UAAU;AAAA,EAAA;AAEb,QAAM,kBAAkBE,MAAAA,GAAG,kBAAkB,2CAAa,SAAS;AACnE,QAAM,iBAAiB,QAAQ,OAAM,2CAAa;AAElD,SACEC,2BAAAA,IAAC,OAAA,EAAI,WAAU,gCACb,UAAAA,2BAAAA;AAAAA,IAACC,cAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACA,YAAU;AAAA,MACT,GAAG;AAAA,MACJ,aAAY,2CAAa,eAAc;AAAA,MACvC,YAAW,2CAAa,cAAa;AAAA,MACpC,GAAI,mBAAmB,SACpB,EAAE,eAAA,IACF,CAAA;AAAA,MACJ,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,oBAAoB,CAAC,UAAwB;AAC3C,cAAM,QAAQ,MAAM,SAChB,cAAc,IAAI,MAAM,MAAM,IAC9B;AACJ,eAAO,QACLD,2BAAAA,IAAC,qBAAA,EAAoB,OAAc,WAAU,WAAU,IACrD;AAAA,MACN;AAAA,MACA,eACEE,2BAAAA,KAAAC,qBAAA,EACG,UAAA;AAAA,QAAA,2CAAa;AAAA,QACdH,2BAAAA,IAAC,iBAAA,EAAgB,QAAQ,WAAA,CAAY;AAAA,MAAA,EAAA,CACvC;AAAA,IAAA;AAAA,EAAA,GAGN;AAEJ;AAEA,SAAS,gBACP,OACA,SACA;AACA,SAAO,MAAM,IAAI,CAAC,SAAoB;AACpC,UAAM,QAAQ,QAAQ,KAAK,EAAE,KAAK;AAClC,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,OAAOI,uBAAAA,eAAe,KAAK,EAAE;AAAA,MAC7B;AAAA,MACA,WAAW,iBAAiB,KAAK;AAAA,IAAA;AAAA,EAErC,CAAC;AACH;AAEA,SAAS,gBAAgB,EAAE,UAAmC;AAC5D,wCACG,OAAA,EAAI,WAAU,mCAAkC,cAAW,eACzD,iBAAO,IAAI,CAAC,UACXJ,2BAAAA,IAAC,uBAAmC,OAAc,WAAU,YAAlC,MAAM,EAAqC,CACtE,GACH;AAEJ;AAEA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AACF,GAGG;AACD,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,eACE,cAAc,WAAW,0BAA0B;AAAA,MAErD,mBAAiB,MAAM;AAAA,MACvB,OAAO,QAAQ,MAAM,EAAE;AAAA,MACvB,WAAWD,MAAAA;AAAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B,MAAM;AAAA,MAAA;AAAA,MAGP,UAAA,MAAM;AAAA,IAAA;AAAA,EAAA;AAGb;;"}
1
+ {"version":3,"file":"SessionInspector.transcript.cjs","sources":["../../../src/data/ai/SessionInspector.transcript.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { EmptyState } from \"./SessionInspector.panel-parts\";\nimport { cn } from \"../../lib/utils\";\nimport {\n createTurnPalette,\n shortSessionId,\n type TurnPalette,\n} from \"./SessionInspector.model\";\nimport {\n SessionViewer,\n type SessionEvent,\n type SessionViewerProps,\n} from \"./SessionViewer\";\nimport type { SessionTurn, UnifiedSessionInput } from \"./SessionViewer.unified\";\n\ntype TurnBadge = {\n id: string;\n label: string;\n color: TurnPalette;\n className: string;\n};\n\nconst TURN_COLOR_CLASS: Record<TurnPalette, string> = {\n sky: \"border-sky-300 bg-sky-100 text-sky-800 [[data-theme=dark]_&]:border-sky-700 [[data-theme=dark]_&]:bg-sky-950/60 [[data-theme=dark]_&]:text-sky-300\",\n violet:\n \"border-violet-300 bg-violet-100 text-violet-800 [[data-theme=dark]_&]:border-violet-700 [[data-theme=dark]_&]:bg-violet-950/60 [[data-theme=dark]_&]:text-violet-300\",\n emerald:\n \"border-emerald-300 bg-emerald-100 text-emerald-800 [[data-theme=dark]_&]:border-emerald-700 [[data-theme=dark]_&]:bg-emerald-950/60 [[data-theme=dark]_&]:text-emerald-300\",\n orange:\n \"border-orange-300 bg-orange-100 text-orange-800 [[data-theme=dark]_&]:border-orange-700 [[data-theme=dark]_&]:bg-orange-950/60 [[data-theme=dark]_&]:text-orange-300\",\n rose: \"border-rose-300 bg-rose-100 text-rose-800 [[data-theme=dark]_&]:border-rose-700 [[data-theme=dark]_&]:bg-rose-950/60 [[data-theme=dark]_&]:text-rose-300\",\n indigo:\n \"border-indigo-300 bg-indigo-100 text-indigo-800 [[data-theme=dark]_&]:border-indigo-700 [[data-theme=dark]_&]:bg-indigo-950/60 [[data-theme=dark]_&]:text-indigo-300\",\n teal: \"border-teal-300 bg-teal-100 text-teal-800 [[data-theme=dark]_&]:border-teal-700 [[data-theme=dark]_&]:bg-teal-950/60 [[data-theme=dark]_&]:text-teal-300\",\n fuchsia:\n \"border-fuchsia-300 bg-fuchsia-100 text-fuchsia-800 [[data-theme=dark]_&]:border-fuchsia-700 [[data-theme=dark]_&]:bg-fuchsia-950/60 [[data-theme=dark]_&]:text-fuchsia-300\",\n};\n\nexport function SessionTranscript({\n session,\n viewerProps,\n}: {\n session: UnifiedSessionInput;\n viewerProps?: Omit<SessionViewerProps, \"session\">;\n}) {\n const turns = session.turns ?? [];\n const palette = useMemo(\n () =>\n createTurnPalette(\n session.id,\n turns.map((turn) => turn.id)\n ),\n [session.id, turns]\n );\n const turnBadges = useMemo(\n () => buildTurnBadges(turns, palette),\n [palette, turns]\n );\n const turnBadgeById = useMemo(\n () => new Map(turnBadges.map((badge) => [badge.id, badge])),\n [turnBadges]\n );\n const viewerClassName = cn(\"min-h-0 flex-1\", viewerProps?.className);\n const scrollResetKey = session.id ?? viewerProps?.scrollResetKey;\n\n if (\n session.detailAvailable === false &&\n !session.messages?.length &&\n !session.events?.length\n ) {\n return <EmptyState>Transcript unavailable</EmptyState>;\n }\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <SessionViewer\n session={session}\n scrollable\n {...viewerProps}\n windowSize={viewerProps?.windowSize ?? 10}\n batchSize={viewerProps?.batchSize ?? 10}\n {...(scrollResetKey !== undefined ? { scrollResetKey } : {})}\n className={viewerClassName}\n showHeader={false}\n showContextMeter={false}\n showRowMetadata={false}\n renderMessageBadge={(event: SessionEvent) => {\n const badge = event.turnId\n ? turnBadgeById.get(event.turnId)\n : undefined;\n return badge ? (\n <TranscriptTurnBadge badge={badge} placement=\"message\" />\n ) : null;\n }}\n headerActions={\n <>\n {viewerProps?.headerActions}\n <TurnBadgeLegend badges={turnBadges} />\n </>\n }\n />\n </div>\n );\n}\n\nfunction buildTurnBadges(\n turns: SessionTurn[],\n palette: Record<string, TurnPalette>\n) {\n return turns.map((turn): TurnBadge => {\n const color = palette[turn.id] ?? \"sky\";\n return {\n id: turn.id,\n label: shortSessionId(turn.id),\n color,\n className: TURN_COLOR_CLASS[color],\n };\n });\n}\n\nfunction TurnBadgeLegend({ badges }: { badges: TurnBadge[] }) {\n return (\n <div className=\"flex max-w-full flex-wrap gap-1\" aria-label=\"Turn colors\">\n {badges.map((badge) => (\n <TranscriptTurnBadge key={badge.id} badge={badge} placement=\"legend\" />\n ))}\n </div>\n );\n}\n\nfunction TranscriptTurnBadge({\n badge,\n placement,\n}: {\n badge: TurnBadge;\n placement: \"legend\" | \"message\";\n}) {\n return (\n <span\n data-testid={\n placement === \"legend\" ? \"transcript-turn-badge\" : \"message-turn-badge\"\n }\n data-turn-color={badge.color}\n title={`Turn ${badge.id}`}\n className={cn(\n \"inline-flex shrink-0 rounded border px-1 py-px font-mono text-[9px] font-semibold leading-none\",\n placement === \"message\" && \"mt-1\",\n badge.className\n )}\n >\n {badge.label}\n </span>\n );\n}\n"],"names":["useMemo","createTurnPalette","cn","jsx","EmptyState","SessionViewer","jsxs","Fragment","shortSessionId"],"mappings":";;;;;;;;AAsBA,MAAM,mBAAgD;AAAA,EACpD,KAAK;AAAA,EACL,QACE;AAAA,EACF,SACE;AAAA,EACF,QACE;AAAA,EACF,MAAM;AAAA,EACN,QACE;AAAA,EACF,MAAM;AAAA,EACN,SACE;AACJ;AAEO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AACF,GAGG;;AACD,QAAM,QAAQ,QAAQ,SAAS,CAAA;AAC/B,QAAM,UAAUA,MAAAA;AAAAA,IACd,MACEC,uBAAAA;AAAAA,MACE,QAAQ;AAAA,MACR,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE;AAAA,IAAA;AAAA,IAE/B,CAAC,QAAQ,IAAI,KAAK;AAAA,EAAA;AAEpB,QAAM,aAAaD,MAAAA;AAAAA,IACjB,MAAM,gBAAgB,OAAO,OAAO;AAAA,IACpC,CAAC,SAAS,KAAK;AAAA,EAAA;AAEjB,QAAM,gBAAgBA,MAAAA;AAAAA,IACpB,MAAM,IAAI,IAAI,WAAW,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;AAAA,IAC1D,CAAC,UAAU;AAAA,EAAA;AAEb,QAAM,kBAAkBE,MAAAA,GAAG,kBAAkB,2CAAa,SAAS;AACnE,QAAM,iBAAiB,QAAQ,OAAM,2CAAa;AAElD,MACE,QAAQ,oBAAoB,SAC5B,GAAC,aAAQ,aAAR,mBAAkB,WACnB,GAAC,aAAQ,WAAR,mBAAgB,SACjB;AACA,WAAOC,2BAAAA,IAACC,4BAAAA,cAAW,UAAA,yBAAA,CAAsB;AAAA,EAC3C;AAEA,SACED,2BAAAA,IAAC,OAAA,EAAI,WAAU,gCACb,UAAAA,2BAAAA;AAAAA,IAACE,cAAAA;AAAAA,IAAA;AAAA,MACC;AAAA,MACA,YAAU;AAAA,MACT,GAAG;AAAA,MACJ,aAAY,2CAAa,eAAc;AAAA,MACvC,YAAW,2CAAa,cAAa;AAAA,MACpC,GAAI,mBAAmB,SAAY,EAAE,eAAA,IAAmB,CAAA;AAAA,MACzD,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,oBAAoB,CAAC,UAAwB;AAC3C,cAAM,QAAQ,MAAM,SAChB,cAAc,IAAI,MAAM,MAAM,IAC9B;AACJ,eAAO,QACLF,2BAAAA,IAAC,qBAAA,EAAoB,OAAc,WAAU,WAAU,IACrD;AAAA,MACN;AAAA,MACA,eACEG,2BAAAA,KAAAC,qBAAA,EACG,UAAA;AAAA,QAAA,2CAAa;AAAA,QACdJ,2BAAAA,IAAC,iBAAA,EAAgB,QAAQ,WAAA,CAAY;AAAA,MAAA,EAAA,CACvC;AAAA,IAAA;AAAA,EAAA,GAGN;AAEJ;AAEA,SAAS,gBACP,OACA,SACA;AACA,SAAO,MAAM,IAAI,CAAC,SAAoB;AACpC,UAAM,QAAQ,QAAQ,KAAK,EAAE,KAAK;AAClC,WAAO;AAAA,MACL,IAAI,KAAK;AAAA,MACT,OAAOK,uBAAAA,eAAe,KAAK,EAAE;AAAA,MAC7B;AAAA,MACA,WAAW,iBAAiB,KAAK;AAAA,IAAA;AAAA,EAErC,CAAC;AACH;AAEA,SAAS,gBAAgB,EAAE,UAAmC;AAC5D,wCACG,OAAA,EAAI,WAAU,mCAAkC,cAAW,eACzD,iBAAO,IAAI,CAAC,UACXL,2BAAAA,IAAC,uBAAmC,OAAc,WAAU,YAAlC,MAAM,EAAqC,CACtE,GACH;AAEJ;AAEA,SAAS,oBAAoB;AAAA,EAC3B;AAAA,EACA;AACF,GAGG;AACD,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,eACE,cAAc,WAAW,0BAA0B;AAAA,MAErD,mBAAiB,MAAM;AAAA,MACvB,OAAO,QAAQ,MAAM,EAAE;AAAA,MACvB,WAAWD,MAAAA;AAAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B,MAAM;AAAA,MAAA;AAAA,MAGP,UAAA,MAAM;AAAA,IAAA;AAAA,EAAA;AAGb;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"SessionInspector.transcript.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionInspector.transcript.tsx"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAyBhF,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;CACnD,+BAqDA"}
1
+ {"version":3,"file":"SessionInspector.transcript.d.ts","sourceRoot":"","sources":["../../../src/data/ai/SessionInspector.transcript.tsx"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAe,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAyBhF,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;CACnD,+BA2DA"}