@recombine-ai/bosun 0.11.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 (403) hide show
  1. package/build/client/_app/immutable/assets/0.CH-WU4JN.css +1 -0
  2. package/build/client/_app/immutable/assets/0.CH-WU4JN.css.br +0 -0
  3. package/build/client/_app/immutable/assets/0.CH-WU4JN.css.gz +0 -0
  4. package/build/client/_app/immutable/assets/{10.BX46PfEJ.css → 10.DIVePEYf.css} +1 -1
  5. package/build/client/_app/immutable/assets/10.DIVePEYf.css.br +0 -0
  6. package/build/client/_app/immutable/assets/10.DIVePEYf.css.gz +0 -0
  7. package/build/client/_app/immutable/chunks/{AocOovHL.js → 07xNIa59.js} +2 -2
  8. package/build/client/_app/immutable/chunks/07xNIa59.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/07xNIa59.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/BDl2QuyN.js +1 -0
  11. package/build/client/_app/immutable/chunks/BDl2QuyN.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/BDl2QuyN.js.gz +0 -0
  13. package/build/client/_app/immutable/chunks/{CqxCTxvJ.js → BI42X8h_.js} +1 -1
  14. package/build/client/_app/immutable/chunks/BI42X8h_.js.br +0 -0
  15. package/build/client/_app/immutable/chunks/BI42X8h_.js.gz +0 -0
  16. package/build/client/_app/immutable/chunks/BJTwdUYr.js +1 -0
  17. package/build/client/_app/immutable/chunks/BJTwdUYr.js.br +0 -0
  18. package/build/client/_app/immutable/chunks/BJTwdUYr.js.gz +0 -0
  19. package/build/client/_app/immutable/chunks/BLu86SbA.js +1 -0
  20. package/build/client/_app/immutable/chunks/BLu86SbA.js.br +0 -0
  21. package/build/client/_app/immutable/chunks/BLu86SbA.js.gz +0 -0
  22. package/build/client/_app/immutable/chunks/BNifnXkO.js +1 -0
  23. package/build/client/_app/immutable/chunks/BNifnXkO.js.br +2 -0
  24. package/build/client/_app/immutable/chunks/BNifnXkO.js.gz +0 -0
  25. package/build/client/_app/immutable/chunks/{BM0jKynj.js → BPNEONy5.js} +1 -1
  26. package/build/client/_app/immutable/chunks/BPNEONy5.js.br +0 -0
  27. package/build/client/_app/immutable/chunks/BPNEONy5.js.gz +0 -0
  28. package/build/client/_app/immutable/chunks/B_K7D6rX.js +2 -0
  29. package/build/client/_app/immutable/chunks/B_K7D6rX.js.br +0 -0
  30. package/build/client/_app/immutable/chunks/B_K7D6rX.js.gz +0 -0
  31. package/build/client/_app/immutable/chunks/Bj8jfBTi.js +1 -0
  32. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.br +0 -0
  33. package/build/client/_app/immutable/chunks/Bj8jfBTi.js.gz +0 -0
  34. package/build/client/_app/immutable/chunks/{BR5f5i_E.js → BoPSJr4b.js} +1 -1
  35. package/build/client/_app/immutable/chunks/BoPSJr4b.js.br +0 -0
  36. package/build/client/_app/immutable/chunks/BoPSJr4b.js.gz +0 -0
  37. package/build/client/_app/immutable/chunks/BsDZe4N6.js +1 -0
  38. package/build/client/_app/immutable/chunks/BsDZe4N6.js.br +0 -0
  39. package/build/client/_app/immutable/chunks/BsDZe4N6.js.gz +0 -0
  40. package/build/client/_app/immutable/chunks/{CXo2BVs0.js → Buo-57fF.js} +1 -1
  41. package/build/client/_app/immutable/chunks/Buo-57fF.js.br +0 -0
  42. package/build/client/_app/immutable/chunks/Buo-57fF.js.gz +0 -0
  43. package/build/client/_app/immutable/chunks/{C-whEtKd.js → C39ai1md.js} +1 -1
  44. package/build/client/_app/immutable/chunks/C39ai1md.js.br +0 -0
  45. package/build/client/_app/immutable/chunks/C39ai1md.js.gz +0 -0
  46. package/build/client/_app/immutable/chunks/C7he4XPB.js +1 -0
  47. package/build/client/_app/immutable/chunks/C7he4XPB.js.br +0 -0
  48. package/build/client/_app/immutable/chunks/C7he4XPB.js.gz +0 -0
  49. package/build/client/_app/immutable/chunks/{DYhTBLDc.js → CAjPRvNs.js} +2 -2
  50. package/build/client/_app/immutable/chunks/CAjPRvNs.js.br +0 -0
  51. package/build/client/_app/immutable/chunks/CAjPRvNs.js.gz +0 -0
  52. package/build/client/_app/immutable/chunks/CJYOmGlC.js +1 -0
  53. package/build/client/_app/immutable/chunks/CJYOmGlC.js.br +0 -0
  54. package/build/client/_app/immutable/chunks/CJYOmGlC.js.gz +0 -0
  55. package/build/client/_app/immutable/chunks/{UiXQyKcj.js → CMQRtcOV.js} +1 -1
  56. package/build/client/_app/immutable/chunks/CMQRtcOV.js.br +0 -0
  57. package/build/client/_app/immutable/chunks/CMQRtcOV.js.gz +0 -0
  58. package/build/client/_app/immutable/chunks/{HJHYLHDn.js → CPXdAKdv.js} +1 -1
  59. package/build/client/_app/immutable/chunks/CPXdAKdv.js.br +0 -0
  60. package/build/client/_app/immutable/chunks/CPXdAKdv.js.gz +0 -0
  61. package/build/client/_app/immutable/chunks/{DTlGvFHe.js → CVlsAO8k.js} +1 -1
  62. package/build/client/_app/immutable/chunks/CVlsAO8k.js.br +0 -0
  63. package/build/client/_app/immutable/chunks/CVlsAO8k.js.gz +0 -0
  64. package/build/client/_app/immutable/chunks/{DNLcpWO_.js → CX0Ekq8t.js} +1 -1
  65. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.br +0 -0
  66. package/build/client/_app/immutable/chunks/CX0Ekq8t.js.gz +0 -0
  67. package/build/client/_app/immutable/chunks/Ca197UDq.js +1 -0
  68. package/build/client/_app/immutable/chunks/Ca197UDq.js.br +0 -0
  69. package/build/client/_app/immutable/chunks/Ca197UDq.js.gz +0 -0
  70. package/build/client/_app/immutable/chunks/CaHl3dME.js +1 -0
  71. package/build/client/_app/immutable/chunks/CaHl3dME.js.br +0 -0
  72. package/build/client/_app/immutable/chunks/CaHl3dME.js.gz +0 -0
  73. package/build/client/_app/immutable/chunks/Cql_e8Of.js +1 -0
  74. package/build/client/_app/immutable/chunks/Cql_e8Of.js.br +2 -0
  75. package/build/client/_app/immutable/chunks/Cql_e8Of.js.gz +0 -0
  76. package/build/client/_app/immutable/chunks/{Dv8m1cTI.js → CxLomzuE.js} +1 -1
  77. package/build/client/_app/immutable/chunks/CxLomzuE.js.br +0 -0
  78. package/build/client/_app/immutable/chunks/{Dv8m1cTI.js.gz → CxLomzuE.js.gz} +0 -0
  79. package/build/client/_app/immutable/chunks/Cyai3SOj.js +1 -0
  80. package/build/client/_app/immutable/chunks/Cyai3SOj.js.br +0 -0
  81. package/build/client/_app/immutable/chunks/Cyai3SOj.js.gz +0 -0
  82. package/build/client/_app/immutable/chunks/{-g1l5aoS.js → CyvQWZoe.js} +4 -4
  83. package/build/client/_app/immutable/chunks/CyvQWZoe.js.br +0 -0
  84. package/build/client/_app/immutable/chunks/CyvQWZoe.js.gz +0 -0
  85. package/build/client/_app/immutable/chunks/CzXNWL4m.js +1 -0
  86. package/build/client/_app/immutable/chunks/CzXNWL4m.js.br +1 -0
  87. package/build/client/_app/immutable/chunks/CzXNWL4m.js.gz +0 -0
  88. package/build/client/_app/immutable/chunks/D-B5X35l.js +1 -0
  89. package/build/client/_app/immutable/chunks/D-B5X35l.js.br +0 -0
  90. package/build/client/_app/immutable/chunks/D-B5X35l.js.gz +0 -0
  91. package/build/client/_app/immutable/chunks/DJriQKD9.js +2 -0
  92. package/build/client/_app/immutable/chunks/DJriQKD9.js.br +0 -0
  93. package/build/client/_app/immutable/chunks/DJriQKD9.js.gz +0 -0
  94. package/build/client/_app/immutable/chunks/{BL8rPcmP.js → DXUXXCDA.js} +1 -1
  95. package/build/client/_app/immutable/chunks/DXUXXCDA.js.br +0 -0
  96. package/build/client/_app/immutable/chunks/DXUXXCDA.js.gz +0 -0
  97. package/build/client/_app/immutable/chunks/{CZLNSMhF.js → Dslf9OwW.js} +10 -10
  98. package/build/client/_app/immutable/chunks/Dslf9OwW.js.br +0 -0
  99. package/build/client/_app/immutable/chunks/Dslf9OwW.js.gz +0 -0
  100. package/build/client/_app/immutable/chunks/QpaC6J6s.js +1 -0
  101. package/build/client/_app/immutable/chunks/QpaC6J6s.js.br +0 -0
  102. package/build/client/_app/immutable/chunks/QpaC6J6s.js.gz +0 -0
  103. package/build/client/_app/immutable/chunks/{HXxIsSOO.js → iAhzu4tv.js} +1 -1
  104. package/build/client/_app/immutable/chunks/iAhzu4tv.js.br +0 -0
  105. package/build/client/_app/immutable/chunks/iAhzu4tv.js.gz +0 -0
  106. package/build/client/_app/immutable/chunks/i_5fJYKS.js +3 -0
  107. package/build/client/_app/immutable/chunks/i_5fJYKS.js.br +0 -0
  108. package/build/client/_app/immutable/chunks/i_5fJYKS.js.gz +0 -0
  109. package/build/client/_app/immutable/chunks/{Cosugfe8.js → nYD9wsyN.js} +1 -1
  110. package/build/client/_app/immutable/chunks/nYD9wsyN.js.br +0 -0
  111. package/build/client/_app/immutable/chunks/nYD9wsyN.js.gz +0 -0
  112. package/build/client/_app/immutable/chunks/{BIpSIzUr.js → uLGP0py1.js} +1 -1
  113. package/build/client/_app/immutable/chunks/uLGP0py1.js.br +0 -0
  114. package/build/client/_app/immutable/chunks/uLGP0py1.js.gz +0 -0
  115. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js +2 -0
  116. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js.br +0 -0
  117. package/build/client/_app/immutable/entry/app.Cr7CMG-3.js.gz +0 -0
  118. package/build/client/_app/immutable/entry/start.CDNd0Bml.js +1 -0
  119. package/build/client/_app/immutable/entry/start.CDNd0Bml.js.br +2 -0
  120. package/build/client/_app/immutable/entry/start.CDNd0Bml.js.gz +0 -0
  121. package/build/client/_app/immutable/nodes/{0.BiYgQci8.js → 0.AyqKXI2h.js} +2 -2
  122. package/build/client/_app/immutable/nodes/0.AyqKXI2h.js.br +0 -0
  123. package/build/client/_app/immutable/nodes/0.AyqKXI2h.js.gz +0 -0
  124. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js +1 -0
  125. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js.br +0 -0
  126. package/build/client/_app/immutable/nodes/1.BRmfm-nN.js.gz +0 -0
  127. package/build/client/_app/immutable/nodes/10.D03jjkr4.js +7 -0
  128. package/build/client/_app/immutable/nodes/10.D03jjkr4.js.br +0 -0
  129. package/build/client/_app/immutable/nodes/10.D03jjkr4.js.gz +0 -0
  130. package/build/client/_app/immutable/nodes/{15.Wmhj7gOy.js → 15.BFy4oAT6.js} +1 -1
  131. package/build/client/_app/immutable/nodes/15.BFy4oAT6.js.br +0 -0
  132. package/build/client/_app/immutable/nodes/15.BFy4oAT6.js.gz +0 -0
  133. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js +3 -0
  134. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js.br +0 -0
  135. package/build/client/_app/immutable/nodes/17.BOYU_WTT.js.gz +0 -0
  136. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js +11 -0
  137. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js.br +0 -0
  138. package/build/client/_app/immutable/nodes/18.Ddx80a4f.js.gz +0 -0
  139. package/build/client/_app/immutable/nodes/{2.DniQTiGZ.js → 2.CZEboQjo.js} +20 -20
  140. package/build/client/_app/immutable/nodes/2.CZEboQjo.js.br +0 -0
  141. package/build/client/_app/immutable/nodes/2.CZEboQjo.js.gz +0 -0
  142. package/build/client/_app/immutable/nodes/3.dAV7FviN.js +1 -0
  143. package/build/client/_app/immutable/nodes/3.dAV7FviN.js.br +0 -0
  144. package/build/client/_app/immutable/nodes/3.dAV7FviN.js.gz +0 -0
  145. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js +1 -0
  146. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js.br +0 -0
  147. package/build/client/_app/immutable/nodes/4.Cm_vMAEA.js.gz +0 -0
  148. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js +1 -0
  149. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js.br +0 -0
  150. package/build/client/_app/immutable/nodes/5.DPWuTlaI.js.gz +0 -0
  151. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js +1 -0
  152. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js.br +0 -0
  153. package/build/client/_app/immutable/nodes/6.DY-NLaRW.js.gz +0 -0
  154. package/build/client/_app/immutable/nodes/{8.2oZsfzo8.js → 8.Chuqhf8W.js} +1 -1
  155. package/build/client/_app/immutable/nodes/8.Chuqhf8W.js.br +0 -0
  156. package/build/client/_app/immutable/nodes/8.Chuqhf8W.js.gz +0 -0
  157. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js +2 -0
  158. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js.br +0 -0
  159. package/build/client/_app/immutable/nodes/9.BOEM8f6B.js.gz +0 -0
  160. package/build/client/_app/version.json +1 -1
  161. package/build/client/_app/version.json.br +0 -0
  162. package/build/client/_app/version.json.gz +0 -0
  163. package/build/server/chunks/0-TQkJBtAu.js +9 -0
  164. package/build/server/chunks/0-TQkJBtAu.js.map +1 -0
  165. package/build/server/chunks/1-CKxKlw1l.js +9 -0
  166. package/build/server/chunks/{1-BQkMpHdq.js.map → 1-CKxKlw1l.js.map} +1 -1
  167. package/build/server/chunks/10-Bly5Nk7M.js +9 -0
  168. package/build/server/chunks/10-Bly5Nk7M.js.map +1 -0
  169. package/build/server/chunks/{15-CZBxPMAc.js → 15-BMEAVslz.js} +2 -2
  170. package/build/server/chunks/{15-CZBxPMAc.js.map → 15-BMEAVslz.js.map} +1 -1
  171. package/build/server/chunks/17-C8CYQOYP.js +9 -0
  172. package/build/server/chunks/17-C8CYQOYP.js.map +1 -0
  173. package/build/server/chunks/18-CtXVGBbS.js +9 -0
  174. package/build/server/chunks/18-CtXVGBbS.js.map +1 -0
  175. package/build/server/chunks/2-DvD0dAh0.js +9 -0
  176. package/build/server/chunks/2-DvD0dAh0.js.map +1 -0
  177. package/build/server/chunks/3-DBzpRDK8.js +9 -0
  178. package/build/server/chunks/3-DBzpRDK8.js.map +1 -0
  179. package/build/server/chunks/4-BUNytyC2.js +9 -0
  180. package/build/server/chunks/4-BUNytyC2.js.map +1 -0
  181. package/build/server/chunks/5-DNo8_tFS.js +9 -0
  182. package/build/server/chunks/{5-C7KuaSU9.js.map → 5-DNo8_tFS.js.map} +1 -1
  183. package/build/server/chunks/6-DUMdIfbf.js +9 -0
  184. package/build/server/chunks/6-DUMdIfbf.js.map +1 -0
  185. package/build/server/chunks/8-DxTJ555u.js +9 -0
  186. package/build/server/chunks/8-DxTJ555u.js.map +1 -0
  187. package/build/server/chunks/9-B4-5k50d.js +9 -0
  188. package/build/server/chunks/9-B4-5k50d.js.map +1 -0
  189. package/build/server/chunks/{AgentSidebar-XdGj1sqT.js → AgentSidebar-DvIlda-E.js} +5 -6
  190. package/build/server/chunks/AgentSidebar-DvIlda-E.js.map +1 -0
  191. package/build/server/chunks/{ChatStepTrace-DnvGtFpb.js → ChatStepTrace-CqqIjRLU.js} +41 -7
  192. package/build/server/chunks/ChatStepTrace-CqqIjRLU.js.map +1 -0
  193. package/build/server/chunks/MockGenerator-GEpXTgX0.js +117 -0
  194. package/build/server/chunks/MockGenerator-GEpXTgX0.js.map +1 -0
  195. package/build/server/chunks/{_layout.svelte-C5Y9nSXi.js → _layout.svelte-B-CkD4a0.js} +12 -6
  196. package/build/server/chunks/_layout.svelte-B-CkD4a0.js.map +1 -0
  197. package/build/server/chunks/{_layout.svelte-_oqm8kmr.js → _layout.svelte-BBFCF-xw.js} +7 -3
  198. package/build/server/chunks/_layout.svelte-BBFCF-xw.js.map +1 -0
  199. package/build/server/chunks/{_layout.svelte-C7JgNCIu.js → _layout.svelte-cPhmW5bZ.js} +3 -1
  200. package/build/server/chunks/_layout.svelte-cPhmW5bZ.js.map +1 -0
  201. package/build/server/chunks/{_page.svelte-QlYvVgTj.js → _page.svelte-0J2xaeyC.js} +70 -15
  202. package/build/server/chunks/_page.svelte-0J2xaeyC.js.map +1 -0
  203. package/build/server/chunks/{_page.svelte-BpRksNEG.js → _page.svelte-BgoX8Y6N.js} +2 -2
  204. package/build/server/chunks/{_page.svelte-BpRksNEG.js.map → _page.svelte-BgoX8Y6N.js.map} +1 -1
  205. package/build/server/chunks/{_page.svelte-bkzbqII3.js → _page.svelte-C_S_qUgZ.js} +479 -71
  206. package/build/server/chunks/_page.svelte-C_S_qUgZ.js.map +1 -0
  207. package/build/server/chunks/{_page.svelte-DDpBtd3X.js → _page.svelte-CqGOvPRO.js} +3 -4
  208. package/build/server/chunks/_page.svelte-CqGOvPRO.js.map +1 -0
  209. package/build/server/chunks/{_page.svelte-DOhPbizt.js → _page.svelte-Cxh9_vUl.js} +34 -22
  210. package/build/server/chunks/_page.svelte-Cxh9_vUl.js.map +1 -0
  211. package/build/server/chunks/{_page.svelte-CEhhEdMi.js → _page.svelte-zOI_s_3J.js} +122 -71
  212. package/build/server/chunks/_page.svelte-zOI_s_3J.js.map +1 -0
  213. package/build/server/chunks/conversation-view-memory-BZguFlTt.js +20 -0
  214. package/build/server/chunks/conversation-view-memory-BZguFlTt.js.map +1 -0
  215. package/build/server/chunks/log-content.svelte-CfQzj-v_.js.map +1 -1
  216. package/build/server/chunks/{client-DzvEkq51.js → source-selection-CcN1cgXc.js} +122 -117
  217. package/build/server/chunks/source-selection-CcN1cgXc.js.map +1 -0
  218. package/build/server/index.js +1 -1
  219. package/build/server/index.js.map +1 -1
  220. package/build/server/manifest.js +14 -14
  221. package/build/server/manifest.js.map +1 -1
  222. package/package.json +3 -3
  223. package/src/backend/lib/get-agent-factories.spec.ts +59 -0
  224. package/src/backend/lib/get-agent-factories.ts +31 -1
  225. package/src/backend/lib/voice-agent-lifecycle.test.ts +12 -8
  226. package/src/backend/lib/voice-agent-lifecycle.ts +2 -1
  227. package/src/backend/roleplay/agents.ts +18 -0
  228. package/src/backend/roleplay/router.ts +8 -3
  229. package/src/backend/tests/academy.ts +151 -93
  230. package/src/backend/tests/analyze.ts +0 -3
  231. package/src/backend/tests/data.spec.ts +139 -0
  232. package/src/backend/tests/data.ts +296 -65
  233. package/src/backend/tests/emulate-agents.ts +45 -45
  234. package/src/backend/tests/openai-prompt-cache.ts +68 -0
  235. package/src/backend/tests/promptfoo-runtime.spec.ts +41 -0
  236. package/src/backend/tests/promptfoo-runtime.ts +27 -0
  237. package/src/backend/tests/report-files.spec.ts +42 -0
  238. package/src/backend/tests/report-files.ts +101 -0
  239. package/src/backend/tests/router.ts +7 -1
  240. package/build/client/_app/immutable/assets/0.Dz9hcoGY.css +0 -1
  241. package/build/client/_app/immutable/assets/0.Dz9hcoGY.css.br +0 -0
  242. package/build/client/_app/immutable/assets/0.Dz9hcoGY.css.gz +0 -0
  243. package/build/client/_app/immutable/assets/10.BX46PfEJ.css.br +0 -0
  244. package/build/client/_app/immutable/assets/10.BX46PfEJ.css.gz +0 -0
  245. package/build/client/_app/immutable/chunks/-g1l5aoS.js.br +0 -0
  246. package/build/client/_app/immutable/chunks/-g1l5aoS.js.gz +0 -0
  247. package/build/client/_app/immutable/chunks/7E8m6dwk.js +0 -1
  248. package/build/client/_app/immutable/chunks/7E8m6dwk.js.br +0 -0
  249. package/build/client/_app/immutable/chunks/7E8m6dwk.js.gz +0 -0
  250. package/build/client/_app/immutable/chunks/7qRqthsa.js +0 -1
  251. package/build/client/_app/immutable/chunks/7qRqthsa.js.br +0 -2
  252. package/build/client/_app/immutable/chunks/7qRqthsa.js.gz +0 -0
  253. package/build/client/_app/immutable/chunks/AocOovHL.js.br +0 -0
  254. package/build/client/_app/immutable/chunks/AocOovHL.js.gz +0 -0
  255. package/build/client/_app/immutable/chunks/B0iEglSe.js +0 -1
  256. package/build/client/_app/immutable/chunks/B0iEglSe.js.br +0 -0
  257. package/build/client/_app/immutable/chunks/B0iEglSe.js.gz +0 -0
  258. package/build/client/_app/immutable/chunks/BIpSIzUr.js.br +0 -0
  259. package/build/client/_app/immutable/chunks/BIpSIzUr.js.gz +0 -0
  260. package/build/client/_app/immutable/chunks/BL8rPcmP.js.br +0 -0
  261. package/build/client/_app/immutable/chunks/BL8rPcmP.js.gz +0 -0
  262. package/build/client/_app/immutable/chunks/BM0jKynj.js.br +0 -0
  263. package/build/client/_app/immutable/chunks/BM0jKynj.js.gz +0 -0
  264. package/build/client/_app/immutable/chunks/BR5f5i_E.js.br +0 -0
  265. package/build/client/_app/immutable/chunks/BR5f5i_E.js.gz +0 -0
  266. package/build/client/_app/immutable/chunks/BTvePR6r.js +0 -1
  267. package/build/client/_app/immutable/chunks/BTvePR6r.js.br +0 -2
  268. package/build/client/_app/immutable/chunks/BTvePR6r.js.gz +0 -0
  269. package/build/client/_app/immutable/chunks/Bcu62lFq.js +0 -1
  270. package/build/client/_app/immutable/chunks/Bcu62lFq.js.br +0 -0
  271. package/build/client/_app/immutable/chunks/Bcu62lFq.js.gz +0 -0
  272. package/build/client/_app/immutable/chunks/C-whEtKd.js.br +0 -2
  273. package/build/client/_app/immutable/chunks/C-whEtKd.js.gz +0 -0
  274. package/build/client/_app/immutable/chunks/C1WqE8CP.js +0 -1
  275. package/build/client/_app/immutable/chunks/C1WqE8CP.js.br +0 -2
  276. package/build/client/_app/immutable/chunks/C1WqE8CP.js.gz +0 -0
  277. package/build/client/_app/immutable/chunks/C4SnSfql.js +0 -1
  278. package/build/client/_app/immutable/chunks/C4SnSfql.js.br +0 -0
  279. package/build/client/_app/immutable/chunks/C4SnSfql.js.gz +0 -0
  280. package/build/client/_app/immutable/chunks/CSv1VDXg.js +0 -1
  281. package/build/client/_app/immutable/chunks/CSv1VDXg.js.br +0 -0
  282. package/build/client/_app/immutable/chunks/CSv1VDXg.js.gz +0 -0
  283. package/build/client/_app/immutable/chunks/CXo2BVs0.js.br +0 -0
  284. package/build/client/_app/immutable/chunks/CXo2BVs0.js.gz +0 -0
  285. package/build/client/_app/immutable/chunks/CZLNSMhF.js.br +0 -0
  286. package/build/client/_app/immutable/chunks/CZLNSMhF.js.gz +0 -0
  287. package/build/client/_app/immutable/chunks/Cosugfe8.js.br +0 -0
  288. package/build/client/_app/immutable/chunks/Cosugfe8.js.gz +0 -0
  289. package/build/client/_app/immutable/chunks/CqxCTxvJ.js.br +0 -0
  290. package/build/client/_app/immutable/chunks/CqxCTxvJ.js.gz +0 -0
  291. package/build/client/_app/immutable/chunks/CxLG58rb.js +0 -1
  292. package/build/client/_app/immutable/chunks/CxLG58rb.js.br +0 -0
  293. package/build/client/_app/immutable/chunks/CxLG58rb.js.gz +0 -0
  294. package/build/client/_app/immutable/chunks/D-puyTnN.js +0 -1
  295. package/build/client/_app/immutable/chunks/D-puyTnN.js.br +0 -0
  296. package/build/client/_app/immutable/chunks/D-puyTnN.js.gz +0 -0
  297. package/build/client/_app/immutable/chunks/D4obUD_s.js +0 -1
  298. package/build/client/_app/immutable/chunks/D4obUD_s.js.br +0 -0
  299. package/build/client/_app/immutable/chunks/D4obUD_s.js.gz +0 -0
  300. package/build/client/_app/immutable/chunks/D8e7SQh9.js +0 -1
  301. package/build/client/_app/immutable/chunks/D8e7SQh9.js.br +0 -0
  302. package/build/client/_app/immutable/chunks/D8e7SQh9.js.gz +0 -0
  303. package/build/client/_app/immutable/chunks/DNLcpWO_.js.br +0 -0
  304. package/build/client/_app/immutable/chunks/DNLcpWO_.js.gz +0 -0
  305. package/build/client/_app/immutable/chunks/DTlGvFHe.js.br +0 -0
  306. package/build/client/_app/immutable/chunks/DTlGvFHe.js.gz +0 -0
  307. package/build/client/_app/immutable/chunks/DYhTBLDc.js.br +0 -0
  308. package/build/client/_app/immutable/chunks/DYhTBLDc.js.gz +0 -0
  309. package/build/client/_app/immutable/chunks/DlZKMmqG.js +0 -4
  310. package/build/client/_app/immutable/chunks/DlZKMmqG.js.br +0 -0
  311. package/build/client/_app/immutable/chunks/DlZKMmqG.js.gz +0 -0
  312. package/build/client/_app/immutable/chunks/Dv0gjm25.js +0 -1
  313. package/build/client/_app/immutable/chunks/Dv0gjm25.js.br +0 -0
  314. package/build/client/_app/immutable/chunks/Dv0gjm25.js.gz +0 -0
  315. package/build/client/_app/immutable/chunks/Dv8m1cTI.js.br +0 -0
  316. package/build/client/_app/immutable/chunks/HJHYLHDn.js.br +0 -0
  317. package/build/client/_app/immutable/chunks/HJHYLHDn.js.gz +0 -0
  318. package/build/client/_app/immutable/chunks/HXxIsSOO.js.br +0 -0
  319. package/build/client/_app/immutable/chunks/HXxIsSOO.js.gz +0 -0
  320. package/build/client/_app/immutable/chunks/UiXQyKcj.js.br +0 -0
  321. package/build/client/_app/immutable/chunks/UiXQyKcj.js.gz +0 -0
  322. package/build/client/_app/immutable/chunks/_yZg6Sf-.js +0 -2
  323. package/build/client/_app/immutable/chunks/_yZg6Sf-.js.br +0 -0
  324. package/build/client/_app/immutable/chunks/_yZg6Sf-.js.gz +0 -0
  325. package/build/client/_app/immutable/chunks/ks45gSgq.js +0 -1
  326. package/build/client/_app/immutable/chunks/ks45gSgq.js.br +0 -0
  327. package/build/client/_app/immutable/chunks/ks45gSgq.js.gz +0 -0
  328. package/build/client/_app/immutable/entry/app.qxHHRD2v.js +0 -2
  329. package/build/client/_app/immutable/entry/app.qxHHRD2v.js.br +0 -0
  330. package/build/client/_app/immutable/entry/app.qxHHRD2v.js.gz +0 -0
  331. package/build/client/_app/immutable/entry/start.CSxD7zE8.js +0 -1
  332. package/build/client/_app/immutable/entry/start.CSxD7zE8.js.br +0 -2
  333. package/build/client/_app/immutable/entry/start.CSxD7zE8.js.gz +0 -0
  334. package/build/client/_app/immutable/nodes/0.BiYgQci8.js.br +0 -0
  335. package/build/client/_app/immutable/nodes/0.BiYgQci8.js.gz +0 -0
  336. package/build/client/_app/immutable/nodes/1.CShtqUFK.js +0 -1
  337. package/build/client/_app/immutable/nodes/1.CShtqUFK.js.br +0 -0
  338. package/build/client/_app/immutable/nodes/1.CShtqUFK.js.gz +0 -0
  339. package/build/client/_app/immutable/nodes/10.dE-XOJgk.js +0 -7
  340. package/build/client/_app/immutable/nodes/10.dE-XOJgk.js.br +0 -0
  341. package/build/client/_app/immutable/nodes/10.dE-XOJgk.js.gz +0 -0
  342. package/build/client/_app/immutable/nodes/15.Wmhj7gOy.js.br +0 -0
  343. package/build/client/_app/immutable/nodes/15.Wmhj7gOy.js.gz +0 -0
  344. package/build/client/_app/immutable/nodes/17.BwzZwCD0.js +0 -3
  345. package/build/client/_app/immutable/nodes/17.BwzZwCD0.js.br +0 -0
  346. package/build/client/_app/immutable/nodes/17.BwzZwCD0.js.gz +0 -0
  347. package/build/client/_app/immutable/nodes/18.BUMDuaQF.js +0 -11
  348. package/build/client/_app/immutable/nodes/18.BUMDuaQF.js.br +0 -0
  349. package/build/client/_app/immutable/nodes/18.BUMDuaQF.js.gz +0 -0
  350. package/build/client/_app/immutable/nodes/2.DniQTiGZ.js.br +0 -0
  351. package/build/client/_app/immutable/nodes/2.DniQTiGZ.js.gz +0 -0
  352. package/build/client/_app/immutable/nodes/3.BIO1e-qq.js +0 -1
  353. package/build/client/_app/immutable/nodes/3.BIO1e-qq.js.br +0 -0
  354. package/build/client/_app/immutable/nodes/3.BIO1e-qq.js.gz +0 -0
  355. package/build/client/_app/immutable/nodes/4.BwfVTawo.js +0 -1
  356. package/build/client/_app/immutable/nodes/4.BwfVTawo.js.br +0 -0
  357. package/build/client/_app/immutable/nodes/4.BwfVTawo.js.gz +0 -0
  358. package/build/client/_app/immutable/nodes/5.DdodCQcX.js +0 -1
  359. package/build/client/_app/immutable/nodes/5.DdodCQcX.js.br +0 -0
  360. package/build/client/_app/immutable/nodes/5.DdodCQcX.js.gz +0 -0
  361. package/build/client/_app/immutable/nodes/6.EIjYVI_W.js +0 -1
  362. package/build/client/_app/immutable/nodes/6.EIjYVI_W.js.br +0 -0
  363. package/build/client/_app/immutable/nodes/6.EIjYVI_W.js.gz +0 -0
  364. package/build/client/_app/immutable/nodes/8.2oZsfzo8.js.br +0 -0
  365. package/build/client/_app/immutable/nodes/8.2oZsfzo8.js.gz +0 -0
  366. package/build/client/_app/immutable/nodes/9.CjWG6Ha9.js +0 -2
  367. package/build/client/_app/immutable/nodes/9.CjWG6Ha9.js.br +0 -0
  368. package/build/client/_app/immutable/nodes/9.CjWG6Ha9.js.gz +0 -0
  369. package/build/server/chunks/0-S8VzkGgQ.js +0 -9
  370. package/build/server/chunks/0-S8VzkGgQ.js.map +0 -1
  371. package/build/server/chunks/1-BQkMpHdq.js +0 -9
  372. package/build/server/chunks/10-D1k3_nkw.js +0 -9
  373. package/build/server/chunks/10-D1k3_nkw.js.map +0 -1
  374. package/build/server/chunks/17-B72NO3Iu.js +0 -9
  375. package/build/server/chunks/17-B72NO3Iu.js.map +0 -1
  376. package/build/server/chunks/18-4kgQqgz-.js +0 -9
  377. package/build/server/chunks/18-4kgQqgz-.js.map +0 -1
  378. package/build/server/chunks/2-DO8jW3Pl.js +0 -9
  379. package/build/server/chunks/2-DO8jW3Pl.js.map +0 -1
  380. package/build/server/chunks/3-0_f3eZZi.js +0 -9
  381. package/build/server/chunks/3-0_f3eZZi.js.map +0 -1
  382. package/build/server/chunks/4-UxjRsyDW.js +0 -9
  383. package/build/server/chunks/4-UxjRsyDW.js.map +0 -1
  384. package/build/server/chunks/5-C7KuaSU9.js +0 -9
  385. package/build/server/chunks/6-C6EG0SXo.js +0 -9
  386. package/build/server/chunks/6-C6EG0SXo.js.map +0 -1
  387. package/build/server/chunks/8-BTSvDZhI.js +0 -9
  388. package/build/server/chunks/8-BTSvDZhI.js.map +0 -1
  389. package/build/server/chunks/9-CepnUatA.js +0 -9
  390. package/build/server/chunks/9-CepnUatA.js.map +0 -1
  391. package/build/server/chunks/AgentSidebar-XdGj1sqT.js.map +0 -1
  392. package/build/server/chunks/ChatStepTrace-DnvGtFpb.js.map +0 -1
  393. package/build/server/chunks/_layout.svelte-C5Y9nSXi.js.map +0 -1
  394. package/build/server/chunks/_layout.svelte-C7JgNCIu.js.map +0 -1
  395. package/build/server/chunks/_layout.svelte-_oqm8kmr.js.map +0 -1
  396. package/build/server/chunks/_page.svelte-CEhhEdMi.js.map +0 -1
  397. package/build/server/chunks/_page.svelte-DDpBtd3X.js.map +0 -1
  398. package/build/server/chunks/_page.svelte-DOhPbizt.js.map +0 -1
  399. package/build/server/chunks/_page.svelte-QlYvVgTj.js.map +0 -1
  400. package/build/server/chunks/_page.svelte-bkzbqII3.js.map +0 -1
  401. package/build/server/chunks/client-DzvEkq51.js.map +0 -1
  402. package/build/server/chunks/source-selection-eA--VEKo.js +0 -124
  403. 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,
@@ -19,6 +19,7 @@ import type {
19
19
  WrittenRunResult
20
20
  } from './academy'
21
21
  import { makeContext, makeContextDiff } from './context-utils'
22
+ import { listAgentReportFiles, readReportFile, reportTimestamp } from './report-files'
22
23
 
23
24
  export type CsvRow = Record<string, string>
24
25
 
@@ -56,11 +57,13 @@ export interface TestResult {
56
57
  test_id: string
57
58
  success_rate_percent?: number
58
59
  success_rate?: string
59
- results: RunResult[]
60
+ results: LatestRunResultSummary[]
60
61
  report_timestamp: string // ISO
61
62
  is_old: boolean
62
63
  previous?: PreviousTestResultSummary
63
64
  }
65
+
66
+ export type LatestRunResultSummary = Pick<RunResult, 'success' | 'result' | 'evaluation' | 'errors'>
64
67
  export interface TestTable {
65
68
  rows: TestRow[]
66
69
  headers: string[]
@@ -256,7 +259,7 @@ async function ensureTestExpandedByDefault(agent: string, id: string): Promise<v
256
259
  async function ensureConfigExists(agent: string): Promise<void> {
257
260
  const existing = await tryReadConfig(agent)
258
261
  if (existing) return
259
- await writeConfig(agent, { repeats: 1, concurrency: 1, expandedTestIds: [] })
262
+ await writeConfig(agent, { repeats: 1, concurrency: 75, expandedTestIds: [] })
260
263
  }
261
264
 
262
265
  async function loadSuiteOrEmpty(agent: string): Promise<TestSuite> {
@@ -322,7 +325,7 @@ export async function loadConfig(agent: string): Promise<RunConfig> {
322
325
  : []
323
326
  return {
324
327
  repeats: typeof (cfg as any).repeats === 'number' ? (cfg as any).repeats : 1,
325
- 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,
326
329
  defaultContext,
327
330
  expandedTestIds
328
331
  }
@@ -355,70 +358,297 @@ export async function saveConfig(agent: string, cfg: RunConfig): Promise<void> {
355
358
 
356
359
  // We no longer migrate legacy reports automatically. Please update old reports manually.
357
360
 
358
- export async function loadLatestResultsByTest(agent: string): Promise<Record<string, TestResult>> {
359
- const projectRoot = path.dirname(await getRootPath())
360
- const outDir = path.join(projectRoot, 'data', 'test_runs')
361
- 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> {
362
436
  try {
363
- 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 }
364
441
  } catch {
365
- return {}
442
+ return null
366
443
  }
367
- const prefix = `${agent}.`
368
- const suffix = `.report.json`
369
- const files = entries.filter((n) => n.startsWith(prefix) && n.endsWith(suffix)).sort()
370
- if (!files.length) return {}
444
+ }
445
+
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
+ }
371
461
 
372
- const latestFile = files[files.length - 1]
373
- const latestTs = latestFile
374
- .slice(prefix.length, latestFile.length - suffix.length)
375
- .replace(/-/g, ':')
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
+ }
376
470
 
377
- const results: Record<string, TestResult> = {}
378
- for (let i = files.length - 1; i >= 0; i--) {
379
- const name = files[i]
380
- const full = path.join(outDir, name)
381
- const raw = await readFile(full, 'utf8')
382
- const parsed = JSON.parse(raw)
383
- const rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.rows) ? parsed.rows : []
384
- const ts = name.slice(prefix.length, name.length - suffix.length).replace(/-/g, ':')
385
- for (const r of rows) {
386
- const test_id = r.test_id
387
- if (!test_id) {
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) {
388
533
  throw new Error(
389
- `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.`
390
535
  )
391
536
  }
392
- const par = String(r.pinned_at_run ?? 'TRUE').toUpperCase()
393
- if (par === 'FALSE') continue
394
-
395
- const runResults: RunResult[] = Array.isArray(r.results) ? (r.results as RunResult[]) : []
396
- const summary: PreviousTestResultSummary = {
397
- success_count: runResults.reduce((acc, rr) => acc + (rr?.success ? 1 : 0), 0),
398
- total_runs: runResults.length,
399
- 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)
400
549
  }
550
+ }
551
+ }
401
552
 
402
- const current = results[test_id]
403
- if (!current) {
404
- results[test_id] = {
405
- test_id,
406
- success_rate_percent: r.success_rate_percent,
407
- success_rate: r.success_rate,
408
- results: runResults,
409
- report_timestamp: ts,
410
- is_old: ts < latestTs
411
- }
412
- 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
+ )
413
585
  }
414
- if (!current.previous && ts < current.report_timestamp) {
415
- 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)
416
600
  }
417
601
  }
602
+
603
+ if (!unresolvedLatest.size && !unresolvedPrevious.size) break
418
604
  }
605
+
419
606
  return results
420
607
  }
421
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
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)
638
+ return results
639
+ }
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
+
422
652
  function summarizeReport(report: WrittenRunReport, timestamp: string): TestRunHistorySummary {
423
653
  requireTestSnapshots(report)
424
654
  const rows = report.rows
@@ -458,9 +688,7 @@ async function listRunReportFiles(agent: string): Promise<string[]> {
458
688
  } catch {
459
689
  return []
460
690
  }
461
- const prefix = `${agent}.`
462
- const suffix = `.report.json`
463
- return entries.filter((n) => n.startsWith(prefix) && n.endsWith(suffix)).sort()
691
+ return listAgentReportFiles(entries, agent)
464
692
  }
465
693
 
466
694
  function requireTestSnapshots(report: WrittenRunReport): void {
@@ -478,8 +706,7 @@ export async function listRunHistory(agent: string): Promise<TestRunHistorySumma
478
706
 
479
707
  for (const name of files.slice().reverse()) {
480
708
  try {
481
- const raw = await readFile(path.join(outDir, name), 'utf8')
482
- const parsed = JSON.parse(raw) as WrittenRunReport
709
+ const parsed = await readReportFile<WrittenRunReport>(path.join(outDir, name))
483
710
  summaries.push(summarizeReport(parsed, parsed.run_timestamp))
484
711
  } catch (e) {
485
712
  console.error(`Failed to read test report ${name}`, e)
@@ -531,22 +758,26 @@ function randomUUID() {
531
758
  async function getRun(agent: string, ts?: string) {
532
759
  const projectRoot = path.dirname(await getRootPath())
533
760
  const outDir = path.join(projectRoot, 'data', 'test_runs')
534
- const file = `${agent}.${(ts || '').replace(/:/g, '-')}.report.json`
535
- const reportPath = ts ? path.join(outDir, file) : null
761
+ const logicalFile = `${agent}.${(ts || '').replace(/:/g, '-')}.report.json`
536
762
 
537
763
  async function readLatestPath(): Promise<string> {
538
764
  const entries = await readdir(outDir)
539
- const prefix = `${agent}.`
540
- const suffix = `.report.json`
541
- const candidates = entries.filter((n) => n.startsWith(prefix) && n.endsWith(suffix)).sort()
765
+ const candidates = listAgentReportFiles(entries, agent)
542
766
  if (!candidates.length) throw new Error('No reports found')
543
767
  return path.join(outDir, candidates[candidates.length - 1])
544
768
  }
545
769
 
546
- const chosen = reportPath || (await readLatestPath())
547
- const raw = await readFile(chosen, 'utf8')
548
- const parsed = JSON.parse(raw) as WrittenRunResult
549
- 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)
550
781
  }
551
782
 
552
783
  type ResultKey = keyof Omit<ResultEntry, 'success'>
@@ -5,6 +5,7 @@ import { getAgentFactories } from '../lib/get-agent-factories'
5
5
  import { getImportNonce } from '../lib/import-nonce'
6
6
  import { getRootPath } from '../lib/get-root-path'
7
7
  import { sendCallStartMessage } from '../lib/voice-agent-lifecycle'
8
+ import { withTestPromptCacheBust } from './openai-prompt-cache'
8
9
 
9
10
  export async function loadAgents(props: {
10
11
  agentId: string
@@ -63,29 +64,31 @@ export async function loadAgents(props: {
63
64
  ctx.set({ ...current, recombineRegion: region })
64
65
  }
65
66
 
66
- const agent = await factory({
67
- stepTracer: stepTracer,
68
- stepRegistry,
69
- logger: logger,
70
- scheduler: scheduler,
71
- getMessages: () => history,
72
- async sendMessage(message) {
73
- const m =
74
- typeof message === 'string' ? ({ sender: 'agent', text: message } as Message) : message
75
- try {
76
- history.push(m)
77
- } catch {
78
- // Best-effort: if history is immutable, ignore push errors
79
- logger.log('Failed to push message to history:', m)
67
+ const agent = await withTestPromptCacheBust(() =>
68
+ factory({
69
+ stepTracer: stepTracer,
70
+ stepRegistry,
71
+ logger: logger,
72
+ scheduler: scheduler,
73
+ getMessages: () => history,
74
+ async sendMessage(message) {
75
+ const m =
76
+ typeof message === 'string' ? ({ sender: 'agent', text: message } as Message) : message
77
+ try {
78
+ history.push(m)
79
+ } catch {
80
+ // Best-effort: if history is immutable, ignore push errors
81
+ logger.log('Failed to push message to history:', m)
82
+ }
83
+ await sendMessage(m)
84
+ },
85
+ ctx,
86
+ config: {
87
+ isTest: true,
88
+ enableTimers: false
80
89
  }
81
- await sendMessage(m)
82
- },
83
- ctx,
84
- config: {
85
- isTest: true,
86
- enableTimers: false
87
- }
88
- })
90
+ })
91
+ )
89
92
 
90
93
  const hasSplitContext =
91
94
  typeof mocks !== 'undefined' &&
@@ -100,32 +103,29 @@ export async function loadAgents(props: {
100
103
  ctx.swap(context)
101
104
  }
102
105
 
103
- process.on('unhandledRejection', (r) => {
104
- console.error('UNHANDLED', r)
105
- })
106
-
107
106
  const withAbort = wrapAbort(abortSignal)
108
107
 
109
- process.env.IS_TEST = 'true'
110
108
  try {
111
- if (isVoiceAgent(agent)) {
112
- // preparation
113
- history = []
114
- const callStartResult = await withAbort(agent.onCallStart())
115
- await withAbort(sendCallStartMessage(callStartResult, sendMessage))
116
- await withAbort(agent.waitForIdle())
109
+ await withTestPromptCacheBust(async () => {
110
+ if (isVoiceAgent(agent)) {
111
+ // preparation
112
+ history = []
113
+ const callStartResult = await withAbort(agent.onCallStart())
114
+ await withAbort(sendCallStartMessage(callStartResult, sendMessage))
115
+ await withAbort(agent.waitForIdle())
117
116
 
118
- // actual test
119
- history = getMessages()
120
- onBeforeTestResponse?.()
121
- await withAbort(agent.streamResponse())
122
- await withAbort(agent.waitForIdle())
123
- } else {
124
- history = getMessages()
125
- onBeforeTestResponse?.()
126
- await withAbort(agent.start())
127
- await withAbort(agent.respondToMessage())
128
- }
117
+ // actual test
118
+ history = getMessages()
119
+ onBeforeTestResponse?.()
120
+ await withAbort(agent.streamResponse())
121
+ await withAbort(agent.waitForIdle())
122
+ } else {
123
+ history = getMessages()
124
+ onBeforeTestResponse?.()
125
+ await withAbort(agent.start())
126
+ await withAbort(agent.respondToMessage())
127
+ }
128
+ })
129
129
  } catch (err) {
130
130
  const error = err instanceof Error ? err : new Error(String(err))
131
131
  try {
@@ -134,8 +134,8 @@ export async function loadAgents(props: {
134
134
  } catch {
135
135
  /* noop */
136
136
  }
137
+ if (abortSignal?.aborted) throw error
137
138
  }
138
- process.env.IS_TEST = undefined
139
139
  }
140
140
 
141
141
  function isVoiceAgent(agent: TestAgent): agent is TestVoiceAgent {
@@ -0,0 +1,68 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { createRequire } from 'node:module'
4
+
5
+ const cacheBustContext = new AsyncLocalStorage<boolean>()
6
+ const patchedPrototypes = new WeakSet<object>()
7
+
8
+ type OpenAIResourceConstructor = {
9
+ prototype: {
10
+ create: (body: Record<string, unknown>, ...args: unknown[]) => unknown
11
+ }
12
+ }
13
+
14
+ /**
15
+ * OpenAI clients are constructed by the project under test, outside Bosun's control. Patch the
16
+ * request-resource prototypes once, then use async-local state to alter only requests made by a
17
+ * Tests run. Roleplay/Playground requests execute outside this context and remain unchanged.
18
+ */
19
+ export function installTestPromptCacheBust(projectEntryPath: string): void {
20
+ const projectRequire = createRequire(projectEntryPath)
21
+ patchOpenAIResources(projectRequire)
22
+
23
+ try {
24
+ const enginePackagePath = projectRequire.resolve('@recombine-ai/engine/package.json')
25
+ patchOpenAIResources(createRequire(enginePackagePath))
26
+ } catch {
27
+ // Some projects may use OpenAI directly without @recombine-ai/engine.
28
+ }
29
+ }
30
+
31
+ export function withTestPromptCacheBust<T>(run: () => Promise<T>): Promise<T> {
32
+ return cacheBustContext.run(true, run)
33
+ }
34
+
35
+ function patchOpenAIResources(requireFromProject: NodeJS.Require): void {
36
+ patchResource(requireFromProject, 'openai/resources/chat/completions', 'Completions')
37
+ patchResource(requireFromProject, 'openai/resources/responses/responses', 'Responses')
38
+ }
39
+
40
+ function patchResource(
41
+ requireFromProject: NodeJS.Require,
42
+ moduleId: string,
43
+ exportName: string
44
+ ): void {
45
+ let Resource: OpenAIResourceConstructor | undefined
46
+ try {
47
+ Resource = requireFromProject(moduleId)?.[exportName] as OpenAIResourceConstructor | undefined
48
+ } catch {
49
+ return
50
+ }
51
+
52
+ const prototype = Resource?.prototype
53
+ if (!prototype || patchedPrototypes.has(prototype) || typeof prototype.create !== 'function')
54
+ return
55
+
56
+ const originalCreate = prototype.create
57
+ prototype.create = function (body: Record<string, unknown>, ...args: unknown[]) {
58
+ if (!cacheBustContext.getStore() || !body || typeof body !== 'object') {
59
+ return Reflect.apply(originalCreate, this, [body, ...args])
60
+ }
61
+
62
+ return Reflect.apply(originalCreate, this, [
63
+ { ...body, prompt_cache_key: `bosun-test-${randomUUID()}` },
64
+ ...args
65
+ ])
66
+ }
67
+ patchedPrototypes.add(prototype)
68
+ }
@@ -0,0 +1,41 @@
1
+ import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { afterEach, describe, expect, it, vi } from 'vitest'
4
+
5
+ import { loadPromptfooForTests } from './promptfoo-runtime'
6
+
7
+ const requireFromTest = createRequire(import.meta.url)
8
+ const originalLogLevel = process.env.LOG_LEVEL
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks()
12
+ if (originalLogLevel === undefined) delete process.env.LOG_LEVEL
13
+ else process.env.LOG_LEVEL = originalLogLevel
14
+ })
15
+
16
+ describe('loadPromptfooForTests', () => {
17
+ it('keeps Promptfoo quiet without changing the project log level', async () => {
18
+ process.env.LOG_LEVEL = 'debug'
19
+
20
+ const runtime = loadPromptfooForTests()
21
+
22
+ expect(typeof runtime.evaluate).toBe('function')
23
+ expect(process.env.LOG_LEVEL).toBe('debug')
24
+
25
+ const promptfooEntry = requireFromTest.resolve('promptfoo')
26
+ const promptfooLogger = requireFromTest(
27
+ path.join(path.dirname(promptfooEntry), 'logger.js')
28
+ ) as {
29
+ getLogLevel(): string
30
+ default: { debug(message: string): void }
31
+ }
32
+ expect(promptfooLogger.getLogLevel()).toBe('warn')
33
+
34
+ const stdoutWrite = vi.spyOn(process.stdout, 'write')
35
+ const marker = 'OpenAI embeddings API response: [should-not-reach-stdout]'
36
+ promptfooLogger.default.debug(marker)
37
+ await new Promise<void>((resolve) => setImmediate(resolve))
38
+
39
+ expect(stdoutWrite.mock.calls.some(([chunk]) => String(chunk).includes(marker))).toBe(false)
40
+ })
41
+ })