@recombine-ai/bosun 0.10.1 → 0.11.2

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 (423) hide show
  1. package/README.md +12 -0
  2. package/bin/bosun.js +3 -44
  3. package/build/client/_app/immutable/assets/0.CH-WU4JN.css +1 -0
  4. package/build/client/_app/immutable/assets/0.CH-WU4JN.css.br +0 -0
  5. package/build/client/_app/immutable/assets/0.CH-WU4JN.css.gz +0 -0
  6. package/build/client/_app/immutable/assets/{10.BX46PfEJ.css → 10.DIVePEYf.css} +1 -1
  7. package/build/client/_app/immutable/assets/10.DIVePEYf.css.br +0 -0
  8. package/build/client/_app/immutable/assets/10.DIVePEYf.css.gz +0 -0
  9. package/build/client/_app/immutable/chunks/{Dp760K13.js → 07xNIa59.js} +2 -2
  10. package/build/client/_app/immutable/chunks/07xNIa59.js.br +0 -0
  11. package/build/client/_app/immutable/chunks/07xNIa59.js.gz +0 -0
  12. package/build/client/_app/immutable/chunks/BDl2QuyN.js +1 -0
  13. package/build/client/_app/immutable/chunks/BDl2QuyN.js.br +0 -0
  14. package/build/client/_app/immutable/chunks/BDl2QuyN.js.gz +0 -0
  15. package/build/client/_app/immutable/chunks/{CqxCTxvJ.js → BI42X8h_.js} +1 -1
  16. package/build/client/_app/immutable/chunks/BI42X8h_.js.br +0 -0
  17. package/build/client/_app/immutable/chunks/BI42X8h_.js.gz +0 -0
  18. package/build/client/_app/immutable/chunks/BJTwdUYr.js +1 -0
  19. package/build/client/_app/immutable/chunks/BJTwdUYr.js.br +0 -0
  20. package/build/client/_app/immutable/chunks/BJTwdUYr.js.gz +0 -0
  21. package/build/client/_app/immutable/chunks/BLu86SbA.js +1 -0
  22. package/build/client/_app/immutable/chunks/BLu86SbA.js.br +0 -0
  23. package/build/client/_app/immutable/chunks/BLu86SbA.js.gz +0 -0
  24. package/build/client/_app/immutable/chunks/BNifnXkO.js +1 -0
  25. package/build/client/_app/immutable/chunks/BNifnXkO.js.br +2 -0
  26. package/build/client/_app/immutable/chunks/BNifnXkO.js.gz +0 -0
  27. package/build/client/_app/immutable/chunks/{BM0jKynj.js → BPNEONy5.js} +1 -1
  28. package/build/client/_app/immutable/chunks/BPNEONy5.js.br +0 -0
  29. package/build/client/_app/immutable/chunks/BPNEONy5.js.gz +0 -0
  30. package/build/client/_app/immutable/chunks/B_K7D6rX.js +2 -0
  31. package/build/client/_app/immutable/chunks/B_K7D6rX.js.br +0 -0
  32. package/build/client/_app/immutable/chunks/B_K7D6rX.js.gz +0 -0
  33. package/build/client/_app/immutable/chunks/Bj8jfBTi.js +1 -0
  34. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.br +0 -0
  35. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.gz +0 -0
  36. package/build/client/_app/immutable/chunks/BoPSJr4b.js +1 -0
  37. package/build/client/_app/immutable/chunks/BoPSJr4b.js.br +0 -0
  38. package/build/client/_app/immutable/chunks/BoPSJr4b.js.gz +0 -0
  39. package/build/client/_app/immutable/chunks/BsDZe4N6.js +1 -0
  40. package/build/client/_app/immutable/chunks/BsDZe4N6.js.br +0 -0
  41. package/build/client/_app/immutable/chunks/BsDZe4N6.js.gz +0 -0
  42. package/build/client/_app/immutable/chunks/{CXo2BVs0.js → Buo-57fF.js} +1 -1
  43. package/build/client/_app/immutable/chunks/Buo-57fF.js.br +0 -0
  44. package/build/client/_app/immutable/chunks/Buo-57fF.js.gz +0 -0
  45. package/build/client/_app/immutable/chunks/{C-whEtKd.js → C39ai1md.js} +1 -1
  46. package/build/client/_app/immutable/chunks/C39ai1md.js.br +0 -0
  47. package/build/client/_app/immutable/chunks/C39ai1md.js.gz +0 -0
  48. package/build/client/_app/immutable/chunks/C7he4XPB.js +1 -0
  49. package/build/client/_app/immutable/chunks/C7he4XPB.js.br +0 -0
  50. package/build/client/_app/immutable/chunks/C7he4XPB.js.gz +0 -0
  51. package/build/client/_app/immutable/chunks/{DYhTBLDc.js → CAjPRvNs.js} +2 -2
  52. package/build/client/_app/immutable/chunks/CAjPRvNs.js.br +0 -0
  53. package/build/client/_app/immutable/chunks/CAjPRvNs.js.gz +0 -0
  54. package/build/client/_app/immutable/chunks/CJYOmGlC.js +1 -0
  55. package/build/client/_app/immutable/chunks/CJYOmGlC.js.br +0 -0
  56. package/build/client/_app/immutable/chunks/CJYOmGlC.js.gz +0 -0
  57. package/build/client/_app/immutable/chunks/{CNLU4B3p.js → CMQRtcOV.js} +1 -1
  58. package/build/client/_app/immutable/chunks/CMQRtcOV.js.br +0 -0
  59. package/build/client/_app/immutable/chunks/CMQRtcOV.js.gz +0 -0
  60. package/build/client/_app/immutable/chunks/{HJHYLHDn.js → CPXdAKdv.js} +1 -1
  61. package/build/client/_app/immutable/chunks/CPXdAKdv.js.br +0 -0
  62. package/build/client/_app/immutable/chunks/CPXdAKdv.js.gz +0 -0
  63. package/build/client/_app/immutable/chunks/CVlsAO8k.js +11 -0
  64. package/build/client/_app/immutable/chunks/CVlsAO8k.js.br +0 -0
  65. package/build/client/_app/immutable/chunks/CVlsAO8k.js.gz +0 -0
  66. package/build/client/_app/immutable/chunks/{DNLcpWO_.js → CX0Ekq8t.js} +1 -1
  67. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.br +0 -0
  68. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.gz +0 -0
  69. package/build/client/_app/immutable/chunks/Ca197UDq.js +1 -0
  70. package/build/client/_app/immutable/chunks/Ca197UDq.js.br +0 -0
  71. package/build/client/_app/immutable/chunks/Ca197UDq.js.gz +0 -0
  72. package/build/client/_app/immutable/chunks/CaHl3dME.js +1 -0
  73. package/build/client/_app/immutable/chunks/CaHl3dME.js.br +0 -0
  74. package/build/client/_app/immutable/chunks/CaHl3dME.js.gz +0 -0
  75. package/build/client/_app/immutable/chunks/Cql_e8Of.js +1 -0
  76. package/build/client/_app/immutable/chunks/Cql_e8Of.js.br +2 -0
  77. package/build/client/_app/immutable/chunks/Cql_e8Of.js.gz +0 -0
  78. package/build/client/_app/immutable/chunks/{Dv8m1cTI.js → CxLomzuE.js} +1 -1
  79. package/build/client/_app/immutable/chunks/CxLomzuE.js.br +0 -0
  80. package/build/client/_app/immutable/chunks/{Dv8m1cTI.js.gz → CxLomzuE.js.gz} +0 -0
  81. package/build/client/_app/immutable/chunks/Cyai3SOj.js +1 -0
  82. package/build/client/_app/immutable/chunks/Cyai3SOj.js.br +0 -0
  83. package/build/client/_app/immutable/chunks/Cyai3SOj.js.gz +0 -0
  84. package/build/client/_app/immutable/chunks/{DTXUDKH5.js → CyvQWZoe.js} +4 -4
  85. package/build/client/_app/immutable/chunks/CyvQWZoe.js.br +0 -0
  86. package/build/client/_app/immutable/chunks/CyvQWZoe.js.gz +0 -0
  87. package/build/client/_app/immutable/chunks/CzXNWL4m.js +1 -0
  88. package/build/client/_app/immutable/chunks/CzXNWL4m.js.br +1 -0
  89. package/build/client/_app/immutable/chunks/CzXNWL4m.js.gz +0 -0
  90. package/build/client/_app/immutable/chunks/D-B5X35l.js +1 -0
  91. package/build/client/_app/immutable/chunks/D-B5X35l.js.br +0 -0
  92. package/build/client/_app/immutable/chunks/D-B5X35l.js.gz +0 -0
  93. package/build/client/_app/immutable/chunks/DJriQKD9.js +2 -0
  94. package/build/client/_app/immutable/chunks/DJriQKD9.js.br +0 -0
  95. package/build/client/_app/immutable/chunks/DJriQKD9.js.gz +0 -0
  96. package/build/client/_app/immutable/chunks/DXUXXCDA.js +1 -0
  97. package/build/client/_app/immutable/chunks/DXUXXCDA.js.br +0 -0
  98. package/build/client/_app/immutable/chunks/DXUXXCDA.js.gz +0 -0
  99. package/build/client/_app/immutable/chunks/{CZLNSMhF.js → Dslf9OwW.js} +10 -10
  100. package/build/client/_app/immutable/chunks/Dslf9OwW.js.br +0 -0
  101. package/build/client/_app/immutable/chunks/Dslf9OwW.js.gz +0 -0
  102. package/build/client/_app/immutable/chunks/QpaC6J6s.js +1 -0
  103. package/build/client/_app/immutable/chunks/QpaC6J6s.js.br +0 -0
  104. package/build/client/_app/immutable/chunks/QpaC6J6s.js.gz +0 -0
  105. package/build/client/_app/immutable/chunks/{HXxIsSOO.js → iAhzu4tv.js} +1 -1
  106. package/build/client/_app/immutable/chunks/iAhzu4tv.js.br +0 -0
  107. package/build/client/_app/immutable/chunks/iAhzu4tv.js.gz +0 -0
  108. package/build/client/_app/immutable/chunks/i_5fJYKS.js +3 -0
  109. package/build/client/_app/immutable/chunks/i_5fJYKS.js.br +0 -0
  110. package/build/client/_app/immutable/chunks/i_5fJYKS.js.gz +0 -0
  111. package/build/client/_app/immutable/chunks/{CF66fWJN.js → nYD9wsyN.js} +1 -1
  112. package/build/client/_app/immutable/chunks/nYD9wsyN.js.br +0 -0
  113. package/build/client/_app/immutable/chunks/nYD9wsyN.js.gz +0 -0
  114. package/build/client/_app/immutable/chunks/{BIpSIzUr.js → uLGP0py1.js} +1 -1
  115. package/build/client/_app/immutable/chunks/uLGP0py1.js.br +0 -0
  116. package/build/client/_app/immutable/chunks/uLGP0py1.js.gz +0 -0
  117. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js +2 -0
  118. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js.br +0 -0
  119. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js.gz +0 -0
  120. package/build/client/_app/immutable/entry/start.CDNd0Bml.js +1 -0
  121. package/build/client/_app/immutable/entry/start.CDNd0Bml.js.br +2 -0
  122. package/build/client/_app/immutable/entry/start.CDNd0Bml.js.gz +0 -0
  123. package/build/client/_app/immutable/nodes/{0.BCmKHiYo.js → 0.AyqKXI2h.js} +2 -2
  124. package/build/client/_app/immutable/nodes/0.AyqKXI2h.js.br +0 -0
  125. package/build/client/_app/immutable/nodes/0.AyqKXI2h.js.gz +0 -0
  126. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js +1 -0
  127. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js.br +0 -0
  128. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js.gz +0 -0
  129. package/build/client/_app/immutable/nodes/10.D03jjkr4.js +7 -0
  130. package/build/client/_app/immutable/nodes/10.D03jjkr4.js.br +0 -0
  131. package/build/client/_app/immutable/nodes/10.D03jjkr4.js.gz +0 -0
  132. package/build/client/_app/immutable/nodes/{15.Wmhj7gOy.js → 15.BFy4oAT6.js} +1 -1
  133. package/build/client/_app/immutable/nodes/15.BFy4oAT6.js.br +0 -0
  134. package/build/client/_app/immutable/nodes/15.BFy4oAT6.js.gz +0 -0
  135. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js +3 -0
  136. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js.br +0 -0
  137. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js.gz +0 -0
  138. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js +11 -0
  139. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js.br +0 -0
  140. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js.gz +0 -0
  141. package/build/client/_app/immutable/nodes/{2.BdQ4KFOK.js → 2.CZEboQjo.js} +20 -20
  142. package/build/client/_app/immutable/nodes/2.CZEboQjo.js.br +0 -0
  143. package/build/client/_app/immutable/nodes/2.CZEboQjo.js.gz +0 -0
  144. package/build/client/_app/immutable/nodes/3.dAV7FviN.js +1 -0
  145. package/build/client/_app/immutable/nodes/3.dAV7FviN.js.br +0 -0
  146. package/build/client/_app/immutable/nodes/3.dAV7FviN.js.gz +0 -0
  147. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js +1 -0
  148. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js.br +0 -0
  149. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js.gz +0 -0
  150. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js +1 -0
  151. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js.br +0 -0
  152. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js.gz +0 -0
  153. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js +1 -0
  154. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js.br +0 -0
  155. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js.gz +0 -0
  156. package/build/client/_app/immutable/nodes/{8.DWNYX1kH.js → 8.Chuqhf8W.js} +1 -1
  157. package/build/client/_app/immutable/nodes/8.Chuqhf8W.js.br +0 -0
  158. package/build/client/_app/immutable/nodes/8.Chuqhf8W.js.gz +0 -0
  159. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js +2 -0
  160. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js.br +0 -0
  161. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js.gz +0 -0
  162. package/build/client/_app/version.json +1 -1
  163. package/build/client/_app/version.json.br +0 -0
  164. package/build/client/_app/version.json.gz +0 -0
  165. package/build/server/chunks/0-TQkJBtAu.js +9 -0
  166. package/build/server/chunks/0-TQkJBtAu.js.map +1 -0
  167. package/build/server/chunks/1-CKxKlw1l.js +9 -0
  168. package/build/server/chunks/{1-C1mGPV-S.js.map → 1-CKxKlw1l.js.map} +1 -1
  169. package/build/server/chunks/10-Bly5Nk7M.js +9 -0
  170. package/build/server/chunks/10-Bly5Nk7M.js.map +1 -0
  171. package/build/server/chunks/{15-CZBxPMAc.js → 15-BMEAVslz.js} +2 -2
  172. package/build/server/chunks/{15-CZBxPMAc.js.map → 15-BMEAVslz.js.map} +1 -1
  173. package/build/server/chunks/17-C8CYQOYP.js +9 -0
  174. package/build/server/chunks/17-C8CYQOYP.js.map +1 -0
  175. package/build/server/chunks/18-CtXVGBbS.js +9 -0
  176. package/build/server/chunks/18-CtXVGBbS.js.map +1 -0
  177. package/build/server/chunks/2-DvD0dAh0.js +9 -0
  178. package/build/server/chunks/2-DvD0dAh0.js.map +1 -0
  179. package/build/server/chunks/3-DBzpRDK8.js +9 -0
  180. package/build/server/chunks/3-DBzpRDK8.js.map +1 -0
  181. package/build/server/chunks/4-BUNytyC2.js +9 -0
  182. package/build/server/chunks/4-BUNytyC2.js.map +1 -0
  183. package/build/server/chunks/5-DNo8_tFS.js +9 -0
  184. package/build/server/chunks/{5-DblV-OAd.js.map → 5-DNo8_tFS.js.map} +1 -1
  185. package/build/server/chunks/6-DUMdIfbf.js +9 -0
  186. package/build/server/chunks/6-DUMdIfbf.js.map +1 -0
  187. package/build/server/chunks/8-DxTJ555u.js +9 -0
  188. package/build/server/chunks/8-DxTJ555u.js.map +1 -0
  189. package/build/server/chunks/9-B4-5k50d.js +9 -0
  190. package/build/server/chunks/9-B4-5k50d.js.map +1 -0
  191. package/build/server/chunks/{AgentSidebar-XdGj1sqT.js → AgentSidebar-DvIlda-E.js} +5 -6
  192. package/build/server/chunks/AgentSidebar-DvIlda-E.js.map +1 -0
  193. package/build/server/chunks/{Button-Dsy3QY7B.js → Button-BLigKkGG.js} +2 -2
  194. package/build/server/chunks/Button-BLigKkGG.js.map +1 -0
  195. package/build/server/chunks/{ChatStepTrace-1q74tVYO.js → ChatStepTrace-CqqIjRLU.js} +47 -11
  196. package/build/server/chunks/ChatStepTrace-CqqIjRLU.js.map +1 -0
  197. package/build/server/chunks/{Input-CHFlfduw.js → Input-OdQKNdS6.js} +1 -2
  198. package/build/server/chunks/Input-OdQKNdS6.js.map +1 -0
  199. package/build/server/chunks/MockGenerator-GEpXTgX0.js +117 -0
  200. package/build/server/chunks/MockGenerator-GEpXTgX0.js.map +1 -0
  201. package/build/server/chunks/Modal-DtVsRmmO.js.map +1 -1
  202. package/build/server/chunks/{_layout.svelte-DJ4kDgDL.js → _layout.svelte-B-CkD4a0.js} +14 -8
  203. package/build/server/chunks/_layout.svelte-B-CkD4a0.js.map +1 -0
  204. package/build/server/chunks/{_layout.svelte-B2lwUacy.js → _layout.svelte-BBFCF-xw.js} +8 -4
  205. package/build/server/chunks/_layout.svelte-BBFCF-xw.js.map +1 -0
  206. package/build/server/chunks/{_layout.svelte-C7JgNCIu.js → _layout.svelte-cPhmW5bZ.js} +3 -1
  207. package/build/server/chunks/_layout.svelte-cPhmW5bZ.js.map +1 -0
  208. package/build/server/chunks/{_page.svelte-FufhMKh4.js → _page.svelte-0J2xaeyC.js} +71 -16
  209. package/build/server/chunks/_page.svelte-0J2xaeyC.js.map +1 -0
  210. package/build/server/chunks/{_page.svelte-BpRksNEG.js → _page.svelte-BgoX8Y6N.js} +2 -2
  211. package/build/server/chunks/{_page.svelte-BpRksNEG.js.map → _page.svelte-BgoX8Y6N.js.map} +1 -1
  212. package/build/server/chunks/{_page.svelte-GaxktsuT.js → _page.svelte-C_S_qUgZ.js} +480 -72
  213. package/build/server/chunks/_page.svelte-C_S_qUgZ.js.map +1 -0
  214. package/build/server/chunks/{_page.svelte-DDpBtd3X.js → _page.svelte-CqGOvPRO.js} +3 -4
  215. package/build/server/chunks/_page.svelte-CqGOvPRO.js.map +1 -0
  216. package/build/server/chunks/{_page.svelte-CA3-vgl2.js → _page.svelte-Cxh9_vUl.js} +151 -100
  217. package/build/server/chunks/_page.svelte-Cxh9_vUl.js.map +1 -0
  218. package/build/server/chunks/_page.svelte-zOI_s_3J.js +2151 -0
  219. package/build/server/chunks/_page.svelte-zOI_s_3J.js.map +1 -0
  220. package/build/server/chunks/agents-context-rV2narXG.js.map +1 -1
  221. package/build/server/chunks/conversation-view-memory-BZguFlTt.js +20 -0
  222. package/build/server/chunks/conversation-view-memory-BZguFlTt.js.map +1 -0
  223. package/build/server/chunks/edit-workspace-context-B1nYQ5hD.js.map +1 -1
  224. package/build/server/chunks/index-CSpGrMM2.js.map +1 -1
  225. package/build/server/chunks/{client-DzvEkq51.js → source-selection-CcN1cgXc.js} +122 -117
  226. package/build/server/chunks/source-selection-CcN1cgXc.js.map +1 -0
  227. package/build/server/index.js +1 -1
  228. package/build/server/index.js.map +1 -1
  229. package/build/server/manifest.js +14 -14
  230. package/build/server/manifest.js.map +1 -1
  231. package/package.json +4 -4
  232. package/src/backend/lib/get-agent-factories.spec.ts +59 -0
  233. package/src/backend/lib/get-agent-factories.ts +31 -1
  234. package/src/backend/lib/voice-agent-lifecycle.test.ts +31 -0
  235. package/src/backend/lib/voice-agent-lifecycle.ts +16 -0
  236. package/src/backend/roleplay/agents.ts +189 -69
  237. package/src/backend/roleplay/message-dispatcher.ts +0 -7
  238. package/src/backend/roleplay/router.ts +14 -17
  239. package/src/backend/roleplay/trace-serialization.spec.ts +35 -0
  240. package/src/backend/roleplay/trace-serialization.ts +57 -0
  241. package/src/backend/roleplay/variants.ts +33 -49
  242. package/src/backend/tests/academy.ts +202 -100
  243. package/src/backend/tests/analyze.ts +0 -3
  244. package/src/backend/tests/data.spec.ts +139 -0
  245. package/src/backend/tests/data.ts +452 -68
  246. package/src/backend/tests/emulate-agents.ts +60 -44
  247. package/src/backend/tests/openai-prompt-cache.ts +68 -0
  248. package/src/backend/tests/promptfoo-runtime.spec.ts +41 -0
  249. package/src/backend/tests/promptfoo-runtime.ts +27 -0
  250. package/src/backend/tests/report-files.spec.ts +42 -0
  251. package/src/backend/tests/report-files.ts +101 -0
  252. package/src/backend/tests/router.ts +28 -2
  253. package/src/backend/tests/suite.ts +13 -4
  254. package/build/client/_app/immutable/assets/0.B4t3dozz.css +0 -1
  255. package/build/client/_app/immutable/assets/0.B4t3dozz.css.br +0 -0
  256. package/build/client/_app/immutable/assets/0.B4t3dozz.css.gz +0 -0
  257. package/build/client/_app/immutable/assets/10.BX46PfEJ.css.br +0 -0
  258. package/build/client/_app/immutable/assets/10.BX46PfEJ.css.gz +0 -0
  259. package/build/client/_app/immutable/chunks/7E8m6dwk.js +0 -1
  260. package/build/client/_app/immutable/chunks/7E8m6dwk.js.br +0 -0
  261. package/build/client/_app/immutable/chunks/7E8m6dwk.js.gz +0 -0
  262. package/build/client/_app/immutable/chunks/7qRqthsa.js +0 -1
  263. package/build/client/_app/immutable/chunks/7qRqthsa.js.br +0 -2
  264. package/build/client/_app/immutable/chunks/7qRqthsa.js.gz +0 -0
  265. package/build/client/_app/immutable/chunks/Ajb1KYIn.js +0 -1
  266. package/build/client/_app/immutable/chunks/Ajb1KYIn.js.br +0 -0
  267. package/build/client/_app/immutable/chunks/Ajb1KYIn.js.gz +0 -0
  268. package/build/client/_app/immutable/chunks/B0iEglSe.js +0 -1
  269. package/build/client/_app/immutable/chunks/B0iEglSe.js.br +0 -0
  270. package/build/client/_app/immutable/chunks/B0iEglSe.js.gz +0 -0
  271. package/build/client/_app/immutable/chunks/BIpSIzUr.js.br +0 -0
  272. package/build/client/_app/immutable/chunks/BIpSIzUr.js.gz +0 -0
  273. package/build/client/_app/immutable/chunks/BM0jKynj.js.br +0 -0
  274. package/build/client/_app/immutable/chunks/BM0jKynj.js.gz +0 -0
  275. package/build/client/_app/immutable/chunks/BTvePR6r.js +0 -1
  276. package/build/client/_app/immutable/chunks/BTvePR6r.js.br +0 -2
  277. package/build/client/_app/immutable/chunks/BTvePR6r.js.gz +0 -0
  278. package/build/client/_app/immutable/chunks/Bcu62lFq.js +0 -1
  279. package/build/client/_app/immutable/chunks/Bcu62lFq.js.br +0 -0
  280. package/build/client/_app/immutable/chunks/Bcu62lFq.js.gz +0 -0
  281. package/build/client/_app/immutable/chunks/BwtfALfL.js +0 -11
  282. package/build/client/_app/immutable/chunks/BwtfALfL.js.br +0 -0
  283. package/build/client/_app/immutable/chunks/BwtfALfL.js.gz +0 -0
  284. package/build/client/_app/immutable/chunks/BxsDwlgp.js +0 -4
  285. package/build/client/_app/immutable/chunks/BxsDwlgp.js.br +0 -0
  286. package/build/client/_app/immutable/chunks/BxsDwlgp.js.gz +0 -0
  287. package/build/client/_app/immutable/chunks/C-whEtKd.js.br +0 -2
  288. package/build/client/_app/immutable/chunks/C-whEtKd.js.gz +0 -0
  289. package/build/client/_app/immutable/chunks/C1WqE8CP.js +0 -1
  290. package/build/client/_app/immutable/chunks/C1WqE8CP.js.br +0 -2
  291. package/build/client/_app/immutable/chunks/C1WqE8CP.js.gz +0 -0
  292. package/build/client/_app/immutable/chunks/C4D-JXnS.js +0 -1
  293. package/build/client/_app/immutable/chunks/C4D-JXnS.js.br +0 -0
  294. package/build/client/_app/immutable/chunks/C4D-JXnS.js.gz +0 -0
  295. package/build/client/_app/immutable/chunks/C4SnSfql.js +0 -1
  296. package/build/client/_app/immutable/chunks/C4SnSfql.js.br +0 -0
  297. package/build/client/_app/immutable/chunks/C4SnSfql.js.gz +0 -0
  298. package/build/client/_app/immutable/chunks/CF66fWJN.js.br +0 -0
  299. package/build/client/_app/immutable/chunks/CF66fWJN.js.gz +0 -0
  300. package/build/client/_app/immutable/chunks/CNLU4B3p.js.br +0 -0
  301. package/build/client/_app/immutable/chunks/CNLU4B3p.js.gz +0 -0
  302. package/build/client/_app/immutable/chunks/CXo2BVs0.js.br +0 -0
  303. package/build/client/_app/immutable/chunks/CXo2BVs0.js.gz +0 -0
  304. package/build/client/_app/immutable/chunks/CZLNSMhF.js.br +0 -0
  305. package/build/client/_app/immutable/chunks/CZLNSMhF.js.gz +0 -0
  306. package/build/client/_app/immutable/chunks/CqO5tR2_.js +0 -1
  307. package/build/client/_app/immutable/chunks/CqO5tR2_.js.br +0 -0
  308. package/build/client/_app/immutable/chunks/CqO5tR2_.js.gz +0 -0
  309. package/build/client/_app/immutable/chunks/CqxCTxvJ.js.br +0 -0
  310. package/build/client/_app/immutable/chunks/CqxCTxvJ.js.gz +0 -0
  311. package/build/client/_app/immutable/chunks/D-wf2QNc.js +0 -1
  312. package/build/client/_app/immutable/chunks/D-wf2QNc.js.br +0 -0
  313. package/build/client/_app/immutable/chunks/D-wf2QNc.js.gz +0 -0
  314. package/build/client/_app/immutable/chunks/D4obUD_s.js +0 -1
  315. package/build/client/_app/immutable/chunks/D4obUD_s.js.br +0 -0
  316. package/build/client/_app/immutable/chunks/D4obUD_s.js.gz +0 -0
  317. package/build/client/_app/immutable/chunks/D8e7SQh9.js +0 -1
  318. package/build/client/_app/immutable/chunks/D8e7SQh9.js.br +0 -0
  319. package/build/client/_app/immutable/chunks/D8e7SQh9.js.gz +0 -0
  320. package/build/client/_app/immutable/chunks/DNLcpWO_.js.br +0 -0
  321. package/build/client/_app/immutable/chunks/DNLcpWO_.js.gz +0 -0
  322. package/build/client/_app/immutable/chunks/DTXUDKH5.js.br +0 -0
  323. package/build/client/_app/immutable/chunks/DTXUDKH5.js.gz +0 -0
  324. package/build/client/_app/immutable/chunks/DYhTBLDc.js.br +0 -0
  325. package/build/client/_app/immutable/chunks/DYhTBLDc.js.gz +0 -0
  326. package/build/client/_app/immutable/chunks/DhBm2Vx9.js +0 -1
  327. package/build/client/_app/immutable/chunks/DhBm2Vx9.js.br +0 -0
  328. package/build/client/_app/immutable/chunks/DhBm2Vx9.js.gz +0 -0
  329. package/build/client/_app/immutable/chunks/Dp760K13.js.br +0 -0
  330. package/build/client/_app/immutable/chunks/Dp760K13.js.gz +0 -0
  331. package/build/client/_app/immutable/chunks/Dv0gjm25.js +0 -1
  332. package/build/client/_app/immutable/chunks/Dv0gjm25.js.br +0 -0
  333. package/build/client/_app/immutable/chunks/Dv0gjm25.js.gz +0 -0
  334. package/build/client/_app/immutable/chunks/Dv8m1cTI.js.br +0 -0
  335. package/build/client/_app/immutable/chunks/HJHYLHDn.js.br +0 -0
  336. package/build/client/_app/immutable/chunks/HJHYLHDn.js.gz +0 -0
  337. package/build/client/_app/immutable/chunks/HXxIsSOO.js.br +0 -0
  338. package/build/client/_app/immutable/chunks/HXxIsSOO.js.gz +0 -0
  339. package/build/client/_app/immutable/chunks/_yZg6Sf-.js +0 -2
  340. package/build/client/_app/immutable/chunks/_yZg6Sf-.js.br +0 -0
  341. package/build/client/_app/immutable/chunks/_yZg6Sf-.js.gz +0 -0
  342. package/build/client/_app/immutable/chunks/xebNt8-Z.js +0 -1
  343. package/build/client/_app/immutable/chunks/xebNt8-Z.js.br +0 -0
  344. package/build/client/_app/immutable/chunks/xebNt8-Z.js.gz +0 -0
  345. package/build/client/_app/immutable/entry/app.C4wJtvtH.js +0 -2
  346. package/build/client/_app/immutable/entry/app.C4wJtvtH.js.br +0 -0
  347. package/build/client/_app/immutable/entry/app.C4wJtvtH.js.gz +0 -0
  348. package/build/client/_app/immutable/entry/start.BId2vqhS.js +0 -1
  349. package/build/client/_app/immutable/entry/start.BId2vqhS.js.br +0 -2
  350. package/build/client/_app/immutable/entry/start.BId2vqhS.js.gz +0 -0
  351. package/build/client/_app/immutable/nodes/0.BCmKHiYo.js.br +0 -0
  352. package/build/client/_app/immutable/nodes/0.BCmKHiYo.js.gz +0 -0
  353. package/build/client/_app/immutable/nodes/1.8skEZBEw.js +0 -1
  354. package/build/client/_app/immutable/nodes/1.8skEZBEw.js.br +0 -0
  355. package/build/client/_app/immutable/nodes/1.8skEZBEw.js.gz +0 -0
  356. package/build/client/_app/immutable/nodes/10.YgmBfSWN.js +0 -5
  357. package/build/client/_app/immutable/nodes/10.YgmBfSWN.js.br +0 -0
  358. package/build/client/_app/immutable/nodes/10.YgmBfSWN.js.gz +0 -0
  359. package/build/client/_app/immutable/nodes/15.Wmhj7gOy.js.br +0 -0
  360. package/build/client/_app/immutable/nodes/15.Wmhj7gOy.js.gz +0 -0
  361. package/build/client/_app/immutable/nodes/17.D8ydco6g.js +0 -3
  362. package/build/client/_app/immutable/nodes/17.D8ydco6g.js.br +0 -0
  363. package/build/client/_app/immutable/nodes/17.D8ydco6g.js.gz +0 -0
  364. package/build/client/_app/immutable/nodes/18.C67CqGm5.js +0 -11
  365. package/build/client/_app/immutable/nodes/18.C67CqGm5.js.br +0 -0
  366. package/build/client/_app/immutable/nodes/18.C67CqGm5.js.gz +0 -0
  367. package/build/client/_app/immutable/nodes/2.BdQ4KFOK.js.br +0 -0
  368. package/build/client/_app/immutable/nodes/2.BdQ4KFOK.js.gz +0 -0
  369. package/build/client/_app/immutable/nodes/3.nCmkz6kS.js +0 -1
  370. package/build/client/_app/immutable/nodes/3.nCmkz6kS.js.br +0 -0
  371. package/build/client/_app/immutable/nodes/3.nCmkz6kS.js.gz +0 -0
  372. package/build/client/_app/immutable/nodes/4.DAwO0_Am.js +0 -1
  373. package/build/client/_app/immutable/nodes/4.DAwO0_Am.js.br +0 -0
  374. package/build/client/_app/immutable/nodes/4.DAwO0_Am.js.gz +0 -0
  375. package/build/client/_app/immutable/nodes/5.C_SvvZjG.js +0 -1
  376. package/build/client/_app/immutable/nodes/5.C_SvvZjG.js.br +0 -0
  377. package/build/client/_app/immutable/nodes/5.C_SvvZjG.js.gz +0 -0
  378. package/build/client/_app/immutable/nodes/6.DfOe9lph.js +0 -1
  379. package/build/client/_app/immutable/nodes/6.DfOe9lph.js.br +0 -0
  380. package/build/client/_app/immutable/nodes/6.DfOe9lph.js.gz +0 -0
  381. package/build/client/_app/immutable/nodes/8.DWNYX1kH.js.br +0 -0
  382. package/build/client/_app/immutable/nodes/8.DWNYX1kH.js.gz +0 -0
  383. package/build/client/_app/immutable/nodes/9.D7SG4T7Z.js +0 -2
  384. package/build/client/_app/immutable/nodes/9.D7SG4T7Z.js.br +0 -0
  385. package/build/client/_app/immutable/nodes/9.D7SG4T7Z.js.gz +0 -0
  386. package/build/server/chunks/0-D5BNSI7t.js +0 -9
  387. package/build/server/chunks/0-D5BNSI7t.js.map +0 -1
  388. package/build/server/chunks/1-C1mGPV-S.js +0 -9
  389. package/build/server/chunks/10-BUo1e_HG.js +0 -9
  390. package/build/server/chunks/10-BUo1e_HG.js.map +0 -1
  391. package/build/server/chunks/17-iNFqG1My.js +0 -9
  392. package/build/server/chunks/17-iNFqG1My.js.map +0 -1
  393. package/build/server/chunks/18-u8T5X6fM.js +0 -9
  394. package/build/server/chunks/18-u8T5X6fM.js.map +0 -1
  395. package/build/server/chunks/2-tLlSByHA.js +0 -9
  396. package/build/server/chunks/2-tLlSByHA.js.map +0 -1
  397. package/build/server/chunks/3-D1KY9yVZ.js +0 -9
  398. package/build/server/chunks/3-D1KY9yVZ.js.map +0 -1
  399. package/build/server/chunks/4-7zu0cJha.js +0 -9
  400. package/build/server/chunks/4-7zu0cJha.js.map +0 -1
  401. package/build/server/chunks/5-DblV-OAd.js +0 -9
  402. package/build/server/chunks/6-BDC5budM.js +0 -9
  403. package/build/server/chunks/6-BDC5budM.js.map +0 -1
  404. package/build/server/chunks/8-DhmD-opq.js +0 -9
  405. package/build/server/chunks/8-DhmD-opq.js.map +0 -1
  406. package/build/server/chunks/9-BGj28GI6.js +0 -9
  407. package/build/server/chunks/9-BGj28GI6.js.map +0 -1
  408. package/build/server/chunks/AgentSidebar-XdGj1sqT.js.map +0 -1
  409. package/build/server/chunks/Button-Dsy3QY7B.js.map +0 -1
  410. package/build/server/chunks/ChatStepTrace-1q74tVYO.js.map +0 -1
  411. package/build/server/chunks/Input-CHFlfduw.js.map +0 -1
  412. package/build/server/chunks/_layout.svelte-B2lwUacy.js.map +0 -1
  413. package/build/server/chunks/_layout.svelte-C7JgNCIu.js.map +0 -1
  414. package/build/server/chunks/_layout.svelte-DJ4kDgDL.js.map +0 -1
  415. package/build/server/chunks/_page.svelte-Bl1WcAXy.js +0 -671
  416. package/build/server/chunks/_page.svelte-Bl1WcAXy.js.map +0 -1
  417. package/build/server/chunks/_page.svelte-CA3-vgl2.js.map +0 -1
  418. package/build/server/chunks/_page.svelte-DDpBtd3X.js.map +0 -1
  419. package/build/server/chunks/_page.svelte-FufhMKh4.js.map +0 -1
  420. package/build/server/chunks/_page.svelte-GaxktsuT.js.map +0 -1
  421. package/build/server/chunks/client-DzvEkq51.js.map +0 -1
  422. package/build/server/chunks/source-selection-eA--VEKo.js +0 -124
  423. package/build/server/chunks/source-selection-eA--VEKo.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { tryReadConfig, writeConfig } from './config'
2
- import { access, readdir, readFile } from 'node:fs/promises'
2
+ import { access, readdir, readFile, stat, writeFile } from 'node:fs/promises'
3
3
  import path from 'node:path'
4
4
  import {
5
5
  getSuiteFilePath,
@@ -12,8 +12,14 @@ import {
12
12
  } from './suite'
13
13
  import { getRootPath } from '../lib/get-root-path'
14
14
  import z from 'zod'
15
- import type { ResultEntry, WrittenRunResult } from './academy'
15
+ import type {
16
+ ResultEntry,
17
+ WrittenRunReport,
18
+ WrittenRunReportConfig,
19
+ WrittenRunResult
20
+ } from './academy'
16
21
  import { makeContext, makeContextDiff } from './context-utils'
22
+ import { listAgentReportFiles, readReportFile, reportTimestamp } from './report-files'
17
23
 
18
24
  export type CsvRow = Record<string, string>
19
25
 
@@ -51,16 +57,52 @@ export interface TestResult {
51
57
  test_id: string
52
58
  success_rate_percent?: number
53
59
  success_rate?: string
54
- results: RunResult[]
60
+ results: LatestRunResultSummary[]
55
61
  report_timestamp: string // ISO
56
62
  is_old: boolean
57
63
  previous?: PreviousTestResultSummary
58
64
  }
65
+
66
+ export type LatestRunResultSummary = Pick<RunResult, 'success' | 'result' | 'evaluation' | 'errors'>
59
67
  export interface TestTable {
60
68
  rows: TestRow[]
61
69
  headers: string[]
62
70
  }
63
71
 
72
+ export interface TestRunHistorySummary {
73
+ timestamp: string
74
+ runScope: 'all' | 'pinned'
75
+ testCount: number
76
+ successCount: number
77
+ totalRuns: number
78
+ successRatePercent: number
79
+ successRate: string
80
+ passed: boolean
81
+ repeats: number
82
+ concurrency: number
83
+ }
84
+
85
+ export interface TestRunHistoryResultEntry {
86
+ success: boolean
87
+ result: string
88
+ evaluation: string
89
+ errors: string
90
+ }
91
+
92
+ export interface TestRunHistoryRow {
93
+ test: TestRow
94
+ success_rate?: string
95
+ success_rate_percent?: number
96
+ results: TestRunHistoryResultEntry[]
97
+ }
98
+
99
+ export interface TestRunHistoryDetail {
100
+ timestamp: string
101
+ runScope: 'all' | 'pinned'
102
+ runConfig: WrittenRunReportConfig
103
+ rows: TestRunHistoryRow[]
104
+ }
105
+
64
106
  const defaultTestRow: Omit<TestRow, 'id'> = {
65
107
  pinned: 'FALSE',
66
108
  scenario: 'New scenario',
@@ -71,7 +113,7 @@ const defaultTestRow: Omit<TestRow, 'id'> = {
71
113
  conversation: 'user: ...',
72
114
  expected_output: '',
73
115
  timeout: '0',
74
- similarity_threshold: '60',
116
+ similarity_threshold: '50',
75
117
  context: {}
76
118
  }
77
119
 
@@ -90,7 +132,8 @@ export async function loadTests(agent: string): Promise<TestTable> {
90
132
  seenIds.add(id)
91
133
  // previously tests were storing context as a string...
92
134
  const context = typeof r.context === 'string' ? JSON.parse(r.context) : r.context
93
- return { ...r, context, id }
135
+ const mocks = typeof r.mocks === 'string' ? JSON.parse(r.mocks) : r.mocks
136
+ return typeof mocks === 'undefined' ? { ...r, context, id } : { ...r, context, mocks, id }
94
137
  })
95
138
  if (changed) {
96
139
  await writeSuite(agent, { ...suite, tests: rows })
@@ -106,9 +149,11 @@ export async function loadTests(agent: string): Promise<TestTable> {
106
149
  'assertion_value',
107
150
  'conversation',
108
151
  'expected_output',
152
+ 'capture_mode',
109
153
  'timeout',
110
154
  'similarity_threshold',
111
- 'context'
155
+ 'context',
156
+ 'mocks'
112
157
  ]
113
158
  return { rows, headers }
114
159
  })
@@ -133,12 +178,16 @@ export async function patchTest(
133
178
  }
134
179
 
135
180
  function preparePatch(current: TestRow, patch: Partial<TestRow>) {
136
- return {
181
+ const updated: TestRow = {
137
182
  ...defaultTestRow,
138
183
  ...current,
139
184
  ...patch,
140
185
  context: makeContext(patch.context || current.context)
141
186
  }
187
+ if (typeof patch.mocks !== 'undefined' || typeof current.mocks !== 'undefined') {
188
+ updated.mocks = makeContext(patch.mocks ?? current.mocks)
189
+ }
190
+ return updated
142
191
  }
143
192
 
144
193
  // Batch update multiple rows in a single read/write to avoid race conditions
@@ -183,6 +232,9 @@ export async function addTest(
183
232
  ? makeContextDiff(seed?.context, suite.meta?.defaultContext)
184
233
  : makeContext(seed?.context)
185
234
  }
235
+ if (typeof seed?.mocks !== 'undefined') {
236
+ newRow.mocks = makeContext(seed.mocks)
237
+ }
186
238
  await writeSuite(agent, { ...(suite as any), tests: [newRow, ...suite.tests] })
187
239
  await ensureTestExpandedByDefault(agent, newRow.id)
188
240
  return newRow
@@ -207,7 +259,7 @@ async function ensureTestExpandedByDefault(agent: string, id: string): Promise<v
207
259
  async function ensureConfigExists(agent: string): Promise<void> {
208
260
  const existing = await tryReadConfig(agent)
209
261
  if (existing) return
210
- await writeConfig(agent, { repeats: 1, concurrency: 1, expandedTestIds: [] })
262
+ await writeConfig(agent, { repeats: 1, concurrency: 75, expandedTestIds: [] })
211
263
  }
212
264
 
213
265
  async function loadSuiteOrEmpty(agent: string): Promise<TestSuite> {
@@ -273,7 +325,7 @@ export async function loadConfig(agent: string): Promise<RunConfig> {
273
325
  : []
274
326
  return {
275
327
  repeats: typeof (cfg as any).repeats === 'number' ? (cfg as any).repeats : 1,
276
- concurrency: typeof (cfg as any).concurrency === 'number' ? (cfg as any).concurrency : 1,
328
+ concurrency: typeof (cfg as any).concurrency === 'number' ? (cfg as any).concurrency : 75,
277
329
  defaultContext,
278
330
  expandedTestIds
279
331
  }
@@ -306,72 +358,394 @@ export async function saveConfig(agent: string, cfg: RunConfig): Promise<void> {
306
358
 
307
359
  // We no longer migrate legacy reports automatically. Please update old reports manually.
308
360
 
309
- export async function loadLatestResultsByTest(agent: string): Promise<Record<string, TestResult>> {
310
- const projectRoot = path.dirname(await getRootPath())
311
- const outDir = path.join(projectRoot, 'data', 'test_runs')
312
- let entries: string[] = []
361
+ type ReportFileState = {
362
+ name: string
363
+ size: number
364
+ mtimeMs: number
365
+ }
366
+
367
+ type LatestResultsCacheEntry = {
368
+ files: ReportFileState[]
369
+ results: Record<string, TestResult>
370
+ }
371
+
372
+ type PersistedLatestResultsCache = LatestResultsCacheEntry & {
373
+ version: 1
374
+ }
375
+
376
+ const latestResultsCache = new Map<string, LatestResultsCacheEntry>()
377
+ const latestResultsLoads = new Map<string, Promise<Record<string, TestResult>>>()
378
+
379
+ function latestResultsCachePath(outDir: string, agent: string): string {
380
+ return path.join(outDir, `.bosun-latest-results-${encodeURIComponent(agent)}.json`)
381
+ }
382
+
383
+ async function reportFileStates(outDir: string, files: string[]): Promise<ReportFileState[]> {
384
+ return Promise.all(
385
+ files.map(async (name) => {
386
+ const info = await stat(path.join(outDir, name))
387
+ return { name, size: info.size, mtimeMs: info.mtimeMs }
388
+ })
389
+ )
390
+ }
391
+
392
+ function sameReportFile(a: ReportFileState, b: ReportFileState): boolean {
393
+ return a.name === b.name && a.size === b.size && a.mtimeMs === b.mtimeMs
394
+ }
395
+
396
+ function incrementalReportStart(
397
+ previous: ReportFileState[],
398
+ current: ReportFileState[]
399
+ ): number | null {
400
+ if (current.length < previous.length) return -1
401
+
402
+ const sharedLength = Math.min(previous.length, current.length)
403
+ for (let index = 0; index < sharedLength; index += 1) {
404
+ if (sameReportFile(previous[index], current[index])) continue
405
+ return index >= Math.max(0, previous.length - 1) ? index : -1
406
+ }
407
+
408
+ return current.length === previous.length ? null : previous.length
409
+ }
410
+
411
+ function isPersistedLatestResultsCache(value: unknown): value is PersistedLatestResultsCache {
412
+ if (!value || typeof value !== 'object') return false
413
+ const candidate = value as Partial<PersistedLatestResultsCache>
414
+ return (
415
+ candidate.version === 1 &&
416
+ Array.isArray(candidate.files) &&
417
+ candidate.files.every(
418
+ (file) =>
419
+ file &&
420
+ typeof file.name === 'string' &&
421
+ typeof file.size === 'number' &&
422
+ Number.isFinite(file.size) &&
423
+ typeof file.mtimeMs === 'number' &&
424
+ Number.isFinite(file.mtimeMs)
425
+ ) &&
426
+ !!candidate.results &&
427
+ typeof candidate.results === 'object' &&
428
+ !Array.isArray(candidate.results)
429
+ )
430
+ }
431
+
432
+ async function readPersistedLatestResultsCache(
433
+ outDir: string,
434
+ agent: string
435
+ ): Promise<LatestResultsCacheEntry | null> {
313
436
  try {
314
- entries = await readdir(outDir)
437
+ const raw = await readFile(latestResultsCachePath(outDir, agent), 'utf8')
438
+ const parsed: unknown = JSON.parse(raw)
439
+ if (!isPersistedLatestResultsCache(parsed)) return null
440
+ return { files: parsed.files, results: parsed.results }
315
441
  } catch {
316
- return {}
442
+ return null
317
443
  }
318
- const prefix = `${agent}.`
319
- const suffix = `.report.json`
320
- const files = entries.filter((n) => n.startsWith(prefix) && n.endsWith(suffix)).sort()
321
- if (!files.length) return {}
444
+ }
322
445
 
323
- const latestFile = files[files.length - 1]
324
- const latestTs = latestFile
325
- .slice(prefix.length, latestFile.length - suffix.length)
326
- .replace(/-/g, ':')
446
+ async function persistLatestResultsCache(
447
+ outDir: string,
448
+ agent: string,
449
+ entry: LatestResultsCacheEntry
450
+ ): Promise<void> {
451
+ try {
452
+ await writeFile(
453
+ latestResultsCachePath(outDir, agent),
454
+ JSON.stringify({ version: 1, ...entry } satisfies PersistedLatestResultsCache),
455
+ 'utf8'
456
+ )
457
+ } catch {
458
+ // The in-memory cache still avoids repeat work for the current Bosun process.
459
+ }
460
+ }
327
461
 
328
- const results: Record<string, TestResult> = {}
329
- for (let i = files.length - 1; i >= 0; i--) {
330
- const name = files[i]
331
- const full = path.join(outDir, name)
332
- const raw = await readFile(full, 'utf8')
333
- const parsed = JSON.parse(raw) as any
334
- const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rows) ? parsed.rows : []
335
- const ts = name.slice(prefix.length, name.length - suffix.length).replace(/-/g, ':')
336
- for (const r of rows) {
337
- const test_id = (r as any).test_id
338
- if (!test_id) {
462
+ function summarizeLatestRun(run: RunResult): LatestRunResultSummary {
463
+ return {
464
+ success: !!run?.success,
465
+ result: String(run?.result ?? ''),
466
+ evaluation: String(run?.evaluation ?? ''),
467
+ errors: String(run?.errors ?? '')
468
+ }
469
+ }
470
+
471
+ function reportRows(parsed: any): any[] {
472
+ return Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rows) ? parsed.rows : []
473
+ }
474
+
475
+ function resultSummary(result: TestResult): PreviousTestResultSummary {
476
+ return {
477
+ success_count: result.results.reduce((count, run) => count + (run.success ? 1 : 0), 0),
478
+ total_runs: result.results.length,
479
+ report_timestamp: result.report_timestamp
480
+ }
481
+ }
482
+
483
+ function resultFromReportRow(
484
+ row: any,
485
+ timestamp: string,
486
+ latestTimestamp: string,
487
+ previous?: PreviousTestResultSummary
488
+ ): TestResult {
489
+ return {
490
+ test_id: String(row.test_id),
491
+ success_rate_percent: row.success_rate_percent,
492
+ success_rate: row.success_rate,
493
+ results: (Array.isArray(row.results) ? (row.results as RunResult[]) : []).map(
494
+ summarizeLatestRun
495
+ ),
496
+ report_timestamp: timestamp,
497
+ is_old: timestamp < latestTimestamp,
498
+ ...(previous ? { previous } : {})
499
+ }
500
+ }
501
+
502
+ function cloneLatestResults(results: Record<string, TestResult>): Record<string, TestResult> {
503
+ return Object.fromEntries(
504
+ Object.entries(results).map(([testId, result]) => [
505
+ testId,
506
+ {
507
+ ...result,
508
+ results: result.results.map((run) => ({ ...run })),
509
+ ...(result.previous ? { previous: { ...result.previous } } : {})
510
+ }
511
+ ])
512
+ )
513
+ }
514
+
515
+ async function mergeIncrementalReports(
516
+ outDir: string,
517
+ files: ReportFileState[],
518
+ startIndex: number,
519
+ baseResults: Record<string, TestResult>,
520
+ agent: string
521
+ ): Promise<Record<string, TestResult>> {
522
+ const results = cloneLatestResults(baseResults)
523
+ const latestTimestamp = reportTimestamp(files[files.length - 1].name, agent)
524
+
525
+ for (let index = startIndex; index < files.length; index += 1) {
526
+ const file = files[index]
527
+ const timestamp = reportTimestamp(file.name, agent)
528
+ const parsed = await readReportFile<any>(path.join(outDir, file.name))
529
+
530
+ for (const row of reportRows(parsed)) {
531
+ const testId = String(row.test_id ?? '').trim()
532
+ if (!testId) {
339
533
  throw new Error(
340
- `Missing test_id in report ${name}. Please add 'test_id' and provenance columns manually.`
534
+ `Missing test_id in report ${file.name}. Please add 'test_id' and provenance columns manually.`
341
535
  )
342
536
  }
343
- const par = String((r as any).pinned_at_run ?? 'TRUE').toUpperCase()
344
- if (par === 'FALSE') continue
345
-
346
- const runResults: RunResult[] = Array.isArray((r as any).results)
347
- ? ((r as any).results as RunResult[])
348
- : []
349
- const summary: PreviousTestResultSummary = {
350
- success_count: runResults.reduce((acc, rr) => acc + (rr?.success ? 1 : 0), 0),
351
- total_runs: runResults.length,
352
- report_timestamp: ts
537
+ if (String(row.pinned_at_run ?? 'TRUE').toUpperCase() === 'FALSE') continue
538
+
539
+ const current = results[testId]
540
+ if (!current || timestamp > current.report_timestamp) {
541
+ results[testId] = resultFromReportRow(
542
+ row,
543
+ timestamp,
544
+ latestTimestamp,
545
+ current ? resultSummary(current) : undefined
546
+ )
547
+ } else if (timestamp === current.report_timestamp) {
548
+ results[testId] = resultFromReportRow(row, timestamp, latestTimestamp, current.previous)
353
549
  }
550
+ }
551
+ }
354
552
 
355
- const current = results[test_id]
356
- if (!current) {
357
- results[test_id] = {
358
- test_id,
359
- success_rate_percent: (r as any).success_rate_percent,
360
- success_rate: (r as any).success_rate,
361
- results: runResults,
362
- report_timestamp: ts,
363
- is_old: ts < latestTs
364
- }
365
- continue
553
+ for (const result of Object.values(results)) {
554
+ result.is_old = result.report_timestamp < latestTimestamp
555
+ }
556
+ return results
557
+ }
558
+
559
+ async function rebuildLatestResults(
560
+ agent: string,
561
+ outDir: string,
562
+ files: ReportFileState[]
563
+ ): Promise<Record<string, TestResult>> {
564
+ const latestTimestamp = reportTimestamp(files[files.length - 1].name, agent)
565
+ const wantedTestIds = new Set(
566
+ (await readSuite(agent)).tests.map((test) => String(test.id ?? '').trim()).filter(Boolean)
567
+ )
568
+ if (!wantedTestIds.size) return {}
569
+
570
+ const unresolvedLatest = new Set(wantedTestIds)
571
+ const unresolvedPrevious = new Set<string>()
572
+ const results: Record<string, TestResult> = {}
573
+
574
+ for (let index = files.length - 1; index >= 0; index -= 1) {
575
+ const file = files[index]
576
+ const timestamp = reportTimestamp(file.name, agent)
577
+ const parsed = await readReportFile<any>(path.join(outDir, file.name))
578
+
579
+ for (const row of reportRows(parsed)) {
580
+ const testId = String(row.test_id ?? '').trim()
581
+ if (!testId) {
582
+ throw new Error(
583
+ `Missing test_id in report ${file.name}. Please add 'test_id' and provenance columns manually.`
584
+ )
366
585
  }
367
- if (!current.previous && ts < current.report_timestamp) {
368
- current.previous = summary
586
+ if (
587
+ !wantedTestIds.has(testId) ||
588
+ String(row.pinned_at_run ?? 'TRUE').toUpperCase() === 'FALSE'
589
+ )
590
+ continue
591
+
592
+ const current = results[testId]
593
+ if (!current) {
594
+ results[testId] = resultFromReportRow(row, timestamp, latestTimestamp)
595
+ unresolvedLatest.delete(testId)
596
+ if (timestamp === latestTimestamp) unresolvedPrevious.add(testId)
597
+ } else if (unresolvedPrevious.has(testId) && timestamp < current.report_timestamp) {
598
+ current.previous = resultSummary(resultFromReportRow(row, timestamp, latestTimestamp))
599
+ unresolvedPrevious.delete(testId)
369
600
  }
370
601
  }
602
+
603
+ if (!unresolvedLatest.size && !unresolvedPrevious.size) break
604
+ }
605
+
606
+ return results
607
+ }
608
+
609
+ async function loadLatestResultsByTestUncached(agent: string): Promise<Record<string, TestResult>> {
610
+ const projectRoot = path.dirname(await getRootPath())
611
+ const outDir = path.join(projectRoot, 'data', 'test_runs')
612
+ let entries: string[] = []
613
+ try {
614
+ entries = await readdir(outDir)
615
+ } catch {
616
+ return {}
617
+ }
618
+ const files = listAgentReportFiles(entries, agent)
619
+ if (!files.length) return {}
620
+ const states = await reportFileStates(outDir, files)
621
+ const cached =
622
+ latestResultsCache.get(agent) ?? (await readPersistedLatestResultsCache(outDir, agent))
623
+ const incrementalStart = cached ? incrementalReportStart(cached.files, states) : -1
624
+ if (cached && incrementalStart === null) {
625
+ latestResultsCache.set(agent, cached)
626
+ return cached.results
371
627
  }
628
+
629
+ let results: Record<string, TestResult>
630
+ if (cached && incrementalStart !== null && incrementalStart >= 0) {
631
+ results = await mergeIncrementalReports(outDir, states, incrementalStart, cached.results, agent)
632
+ } else {
633
+ results = await rebuildLatestResults(agent, outDir, states)
634
+ }
635
+ const entry = { files: states, results }
636
+ latestResultsCache.set(agent, entry)
637
+ await persistLatestResultsCache(outDir, agent, entry)
372
638
  return results
373
639
  }
374
640
 
641
+ export function loadLatestResultsByTest(agent: string): Promise<Record<string, TestResult>> {
642
+ const pending = latestResultsLoads.get(agent)
643
+ if (pending) return pending
644
+
645
+ const load = loadLatestResultsByTestUncached(agent).finally(() => {
646
+ if (latestResultsLoads.get(agent) === load) latestResultsLoads.delete(agent)
647
+ })
648
+ latestResultsLoads.set(agent, load)
649
+ return load
650
+ }
651
+
652
+ function summarizeReport(report: WrittenRunReport, timestamp: string): TestRunHistorySummary {
653
+ requireTestSnapshots(report)
654
+ const rows = report.rows
655
+ const testCount = rows.length
656
+ const successCount = rows.reduce((acc, row) => {
657
+ return acc + row.results.reduce((inner, result) => inner + (result.success ? 1 : 0), 0)
658
+ }, 0)
659
+ const totalRuns = rows.reduce((acc, row) => {
660
+ return acc + row.results.length
661
+ }, 0)
662
+ const successRatePercent = totalRuns ? Math.round((successCount / totalRuns) * 100) : 0
663
+
664
+ return {
665
+ timestamp,
666
+ runScope: report.run_scope,
667
+ testCount,
668
+ successCount,
669
+ totalRuns,
670
+ successRatePercent,
671
+ successRate: `${successCount}/${totalRuns}`,
672
+ passed: totalRuns > 0 && successCount === totalRuns,
673
+ repeats: report.run_config.repeats,
674
+ concurrency: report.run_config.concurrency
675
+ }
676
+ }
677
+
678
+ async function getRunReportDir() {
679
+ const projectRoot = path.dirname(await getRootPath())
680
+ return path.join(projectRoot, 'data', 'test_runs')
681
+ }
682
+
683
+ async function listRunReportFiles(agent: string): Promise<string[]> {
684
+ const outDir = await getRunReportDir()
685
+ let entries: string[] = []
686
+ try {
687
+ entries = await readdir(outDir)
688
+ } catch {
689
+ return []
690
+ }
691
+ return listAgentReportFiles(entries, agent)
692
+ }
693
+
694
+ function requireTestSnapshots(report: WrittenRunReport): void {
695
+ for (const row of report.rows) {
696
+ if (!row.test) {
697
+ throw new Error(`Report ${report.run_timestamp} is missing preserved test snapshots`)
698
+ }
699
+ }
700
+ }
701
+
702
+ export async function listRunHistory(agent: string): Promise<TestRunHistorySummary[]> {
703
+ const outDir = await getRunReportDir()
704
+ const files = await listRunReportFiles(agent)
705
+ const summaries: TestRunHistorySummary[] = []
706
+
707
+ for (const name of files.slice().reverse()) {
708
+ try {
709
+ const parsed = await readReportFile<WrittenRunReport>(path.join(outDir, name))
710
+ summaries.push(summarizeReport(parsed, parsed.run_timestamp))
711
+ } catch (e) {
712
+ console.error(`Failed to read test report ${name}`, e)
713
+ }
714
+ }
715
+
716
+ return summaries
717
+ }
718
+
719
+ function normalizeHistoryRow(row: WrittenRunReport['rows'][number]): TestRunHistoryRow {
720
+ const rawResults = row.results
721
+ return {
722
+ test: row.test,
723
+ success_rate: row.success_rate,
724
+ success_rate_percent: row.success_rate_percent,
725
+ results: rawResults.map((result) => ({
726
+ success: result.success,
727
+ result: result.result,
728
+ evaluation: result.evaluation,
729
+ errors: result.errors
730
+ }))
731
+ }
732
+ }
733
+
734
+ export async function getRunHistory(
735
+ agent: string,
736
+ timestamp: string
737
+ ): Promise<TestRunHistoryDetail> {
738
+ const report = await getRun(agent, timestamp)
739
+ requireTestSnapshots(report)
740
+
741
+ return {
742
+ timestamp: report.run_timestamp,
743
+ runScope: report.run_scope,
744
+ runConfig: report.run_config,
745
+ rows: report.rows.map(normalizeHistoryRow)
746
+ }
747
+ }
748
+
375
749
  // Fallback UUID for Node environments without global crypto
376
750
  function randomUUID() {
377
751
  const rnd = () =>
@@ -384,22 +758,26 @@ function randomUUID() {
384
758
  async function getRun(agent: string, ts?: string) {
385
759
  const projectRoot = path.dirname(await getRootPath())
386
760
  const outDir = path.join(projectRoot, 'data', 'test_runs')
387
- const file = `${agent}.${(ts || '').replace(/:/g, '-')}.report.json`
388
- const reportPath = ts ? path.join(outDir, file) : null
761
+ const logicalFile = `${agent}.${(ts || '').replace(/:/g, '-')}.report.json`
389
762
 
390
763
  async function readLatestPath(): Promise<string> {
391
764
  const entries = await readdir(outDir)
392
- const prefix = `${agent}.`
393
- const suffix = `.report.json`
394
- const candidates = entries.filter((n) => n.startsWith(prefix) && n.endsWith(suffix)).sort()
765
+ const candidates = listAgentReportFiles(entries, agent)
395
766
  if (!candidates.length) throw new Error('No reports found')
396
767
  return path.join(outDir, candidates[candidates.length - 1])
397
768
  }
398
769
 
399
- const chosen = reportPath || (await readLatestPath())
400
- const raw = await readFile(chosen, 'utf8')
401
- const parsed = JSON.parse(raw) as WrittenRunResult
402
- return parsed
770
+ async function readTimestampPath(): Promise<string> {
771
+ const entries = await readdir(outDir)
772
+ const candidate = listAgentReportFiles(entries, agent).find((name) =>
773
+ name.startsWith(logicalFile)
774
+ )
775
+ if (!candidate) throw new Error(`Report not found: ${ts}`)
776
+ return path.join(outDir, candidate)
777
+ }
778
+
779
+ const chosen = ts ? await readTimestampPath() : await readLatestPath()
780
+ return readReportFile<WrittenRunResult>(chosen)
403
781
  }
404
782
 
405
783
  type ResultKey = keyof Omit<ResultEntry, 'success'>
@@ -442,5 +820,11 @@ export async function getResultEntry(
442
820
  }
443
821
 
444
822
  // TODO get separate RPCs for logs and traces (list)
445
- return Array.isArray(entryContent) ? entryContent.join('\n') : entryContent
823
+ if (Array.isArray(entryContent)) {
824
+ return entryContent
825
+ .map((entry) => (typeof entry === 'string' ? entry : JSON.stringify(entry)))
826
+ .join('\n')
827
+ }
828
+ if (typeof entryContent === 'string') return entryContent
829
+ return entryContent == null ? '' : JSON.stringify(entryContent)
446
830
  }