onbuzz 3.4.0 → 3.6.1

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 (483) hide show
  1. package/package.json +1 -1
  2. package/scripts/bump-version.js +116 -0
  3. package/src/core/agentPool.js +15 -3
  4. package/src/core/agentScheduler.js +79 -21
  5. package/src/core/messageProcessor.js +110 -2
  6. package/src/index.js +2 -2
  7. package/src/interfaces/__tests__/imageServing.test.js +228 -0
  8. package/src/interfaces/webServer.js +97 -13
  9. package/src/services/conversationCompactionService.js +2 -2
  10. package/src/services/visualEditorServer.js +26 -7
  11. package/src/tools/imageTool.js +41 -5
  12. package/src/tools/webTool.js +155 -36
  13. package/web-ui/build/index.html +2 -2
  14. package/web-ui/build/static/1c-8PZzOTzp.js +1 -0
  15. package/web-ui/build/static/abap-Bcx_Au1F.js +1 -0
  16. package/web-ui/build/static/abnf-BKTLqpWA.js +1 -0
  17. package/web-ui/build/static/abnf-J05BAvJt.js +1 -0
  18. package/web-ui/build/static/accesslog-Cp8_lqVY.js +1 -0
  19. package/web-ui/build/static/actionscript-BK0UaMrm.js +1 -0
  20. package/web-ui/build/static/actionscript-CyqZUddh.js +1 -0
  21. package/web-ui/build/static/ada-BNirS6Nr.js +1 -0
  22. package/web-ui/build/static/ada-BSFWcT1O.js +1 -0
  23. package/web-ui/build/static/agda-D0NJDJg7.js +1 -0
  24. package/web-ui/build/static/al-rWARKtwb.js +1 -0
  25. package/web-ui/build/static/angelscript-fCehtOYk.js +1 -0
  26. package/web-ui/build/static/antlr4-Dn9jrnZN.js +1 -0
  27. package/web-ui/build/static/apache-DaQCsvNW.js +1 -0
  28. package/web-ui/build/static/apacheconf-dY4i0Xvz.js +1 -0
  29. package/web-ui/build/static/apex-vhS4SI46.js +1 -0
  30. package/web-ui/build/static/apl-CKRkxH90.js +1 -0
  31. package/web-ui/build/static/applescript-CWmpQIEB.js +1 -0
  32. package/web-ui/build/static/applescript-DBaX7Uqo.js +1 -0
  33. package/web-ui/build/static/aql-8s41lrIa.js +1 -0
  34. package/web-ui/build/static/arcade-w2_RhAcq.js +1 -0
  35. package/web-ui/build/static/arduino-I7BtZTu6.js +1 -0
  36. package/web-ui/build/static/arduino-h2LZErKQ.js +1 -0
  37. package/web-ui/build/static/arff-C543-5a1.js +1 -0
  38. package/web-ui/build/static/armasm-DyZdFOzz.js +1 -0
  39. package/web-ui/build/static/asciidoc-ZzENlACu.js +1 -0
  40. package/web-ui/build/static/asciidoc-_j9x9bUz.js +1 -0
  41. package/web-ui/build/static/asm6502-CsNsmBfq.js +1 -0
  42. package/web-ui/build/static/asmatmel-CkIVf_tD.js +1 -0
  43. package/web-ui/build/static/aspectj-C6AQLme_.js +1 -0
  44. package/web-ui/build/static/aspnet-5AkdiVyL.js +1 -0
  45. package/web-ui/build/static/autohotkey-BRZVABiS.js +1 -0
  46. package/web-ui/build/static/autohotkey-DVTmfk_f.js +1 -0
  47. package/web-ui/build/static/autoit-3UEcWu5a.js +1 -0
  48. package/web-ui/build/static/autoit-BDByIKSH.js +1 -0
  49. package/web-ui/build/static/avisynth-BHc4uUkP.js +1 -0
  50. package/web-ui/build/static/avrasm-BAPq8_aI.js +1 -0
  51. package/web-ui/build/static/avro-idl-BKEBYUtv.js +1 -0
  52. package/web-ui/build/static/awk-CBCkArRT.js +1 -0
  53. package/web-ui/build/static/axapta-DlOgnXSZ.js +1 -0
  54. package/web-ui/build/static/bash-C6Brp5OE.js +1 -0
  55. package/web-ui/build/static/bash-DkEO7JRq.js +1 -0
  56. package/web-ui/build/static/basic-DG6TYB0R.js +1 -0
  57. package/web-ui/build/static/basic-DRPcNfAn.js +1 -0
  58. package/web-ui/build/static/batch-DdjZ5KC1.js +1 -0
  59. package/web-ui/build/static/bbcode-DCXEEs2w.js +1 -0
  60. package/web-ui/build/static/bicep-CpLhfOwt.js +1 -0
  61. package/web-ui/build/static/birb-DNWkqgQm.js +1 -0
  62. package/web-ui/build/static/bison-DwxbQHJ9.js +1 -0
  63. package/web-ui/build/static/bnf-Cgnt7npj.js +1 -0
  64. package/web-ui/build/static/bnf-DSTq_eu9.js +1 -0
  65. package/web-ui/build/static/brainfuck-Bi8mGutW.js +1 -0
  66. package/web-ui/build/static/brainfuck-DOWfqVtR.js +1 -0
  67. package/web-ui/build/static/brightscript-D95pbP-v.js +1 -0
  68. package/web-ui/build/static/bro-BrDVwXeg.js +1 -0
  69. package/web-ui/build/static/bsl-BMoXI84g.js +1 -0
  70. package/web-ui/build/static/c-CKH4C7-Z.js +1 -0
  71. package/web-ui/build/static/c-Z0txyaeJ.js +1 -0
  72. package/web-ui/build/static/c-like-Dzm9dMmR.js +1 -0
  73. package/web-ui/build/static/cal-DoyAwiUt.js +1 -0
  74. package/web-ui/build/static/capnproto-DeIi9LOH.js +1 -0
  75. package/web-ui/build/static/ceylon-Coim6DIe.js +1 -0
  76. package/web-ui/build/static/cfscript-CwsndC-j.js +1 -0
  77. package/web-ui/build/static/chaiscript-D6Aq-PSv.js +1 -0
  78. package/web-ui/build/static/cil-vi56VRk_.js +1 -0
  79. package/web-ui/build/static/clean-BfpKrTdp.js +1 -0
  80. package/web-ui/build/static/clojure-DUtl6BaB.js +1 -0
  81. package/web-ui/build/static/clojure-DXJHtDlY.js +1 -0
  82. package/web-ui/build/static/clojure-repl-BxwP5C3g.js +1 -0
  83. package/web-ui/build/static/cmake-C9_VZ1vH.js +1 -0
  84. package/web-ui/build/static/cmake-dplO-PGD.js +1 -0
  85. package/web-ui/build/static/cobol-DsZhu02V.js +1 -0
  86. package/web-ui/build/static/coffeescript-Cw9jtGNP.js +1 -0
  87. package/web-ui/build/static/coffeescript-DvDt4T2l.js +1 -0
  88. package/web-ui/build/static/concurnas-Bzc_Dcdd.js +1 -0
  89. package/web-ui/build/static/coq-Cv-5BqGo.js +1 -0
  90. package/web-ui/build/static/coq-DWFe2ssK.js +1 -0
  91. package/web-ui/build/static/cos-D6Lc6Cah.js +1 -0
  92. package/web-ui/build/static/cpp-BFmLjd76.js +1 -0
  93. package/web-ui/build/static/cpp-DVQgbHji.js +1 -0
  94. package/web-ui/build/static/crmsh-Cqveth9p.js +1 -0
  95. package/web-ui/build/static/crystal-0syYaH4Y.js +1 -0
  96. package/web-ui/build/static/crystal-Noptp-kr.js +1 -0
  97. package/web-ui/build/static/csharp-B799cFMH.js +1 -0
  98. package/web-ui/build/static/csharp-_HlvMZzJ.js +1 -0
  99. package/web-ui/build/static/cshtml-CnwOXlhP.js +1 -0
  100. package/web-ui/build/static/csp-1ffxIG_-.js +1 -0
  101. package/web-ui/build/static/csp-Bws60bPu.js +1 -0
  102. package/web-ui/build/static/css-BGdwXzpm.js +1 -0
  103. package/web-ui/build/static/css-extras-DZCECiOa.js +1 -0
  104. package/web-ui/build/static/csv-FMFGT0T4.js +1 -0
  105. package/web-ui/build/static/cypher-DnXoEwRp.js +1 -0
  106. package/web-ui/build/static/d-CBrts1xB.js +1 -0
  107. package/web-ui/build/static/d-qrJLxk2L.js +1 -0
  108. package/web-ui/build/static/dart-3vBSXJVV.js +1 -0
  109. package/web-ui/build/static/dart-Cj5b7BV9.js +1 -0
  110. package/web-ui/build/static/dataweave-BuFf63rk.js +1 -0
  111. package/web-ui/build/static/dax-Cl-se1JI.js +1 -0
  112. package/web-ui/build/static/delphi-CLkRb26y.js +1 -0
  113. package/web-ui/build/static/dhall--TIL2Z--.js +1 -0
  114. package/web-ui/build/static/diff-Bn-XL2om.js +1 -0
  115. package/web-ui/build/static/diff-BsTwly4w.js +1 -0
  116. package/web-ui/build/static/django-BfRtHnTS.js +1 -0
  117. package/web-ui/build/static/django-Dm9O4e3A.js +1 -0
  118. package/web-ui/build/static/dns-BIVEp3uD.js +1 -0
  119. package/web-ui/build/static/dns-zone-file-CO7LnOdh.js +1 -0
  120. package/web-ui/build/static/docker-BhwMip1R.js +1 -0
  121. package/web-ui/build/static/dockerfile-8Tjw9_jF.js +1 -0
  122. package/web-ui/build/static/dos-CRMiAo46.js +1 -0
  123. package/web-ui/build/static/dot-DPpW7LrJ.js +1 -0
  124. package/web-ui/build/static/dsconfig-D0zbYilI.js +1 -0
  125. package/web-ui/build/static/dts-C_-yqWEL.js +1 -0
  126. package/web-ui/build/static/dust-CucJgqnE.js +1 -0
  127. package/web-ui/build/static/ebnf-CCHK0H6j.js +1 -0
  128. package/web-ui/build/static/ebnf-CDdAcveH.js +1 -0
  129. package/web-ui/build/static/editorconfig-f-5b95UM.js +1 -0
  130. package/web-ui/build/static/eiffel-MmghFce7.js +1 -0
  131. package/web-ui/build/static/ejs-CPMN4-jk.js +1 -0
  132. package/web-ui/build/static/elixir-57Ldyw8h.js +1 -0
  133. package/web-ui/build/static/elixir-DTxnmhIt.js +1 -0
  134. package/web-ui/build/static/elm-ClV9zQoT.js +1 -0
  135. package/web-ui/build/static/elm-pX-i6o7U.js +1 -0
  136. package/web-ui/build/static/erb-BPeO9smT.js +1 -0
  137. package/web-ui/build/static/erb-BnZQ4STz.js +1 -0
  138. package/web-ui/build/static/erlang-DPtI7VK_.js +1 -0
  139. package/web-ui/build/static/erlang-KG5mg5wN.js +1 -0
  140. package/web-ui/build/static/erlang-repl-BqPVXZ3Y.js +1 -0
  141. package/web-ui/build/static/etlua-BRc0Qbbs.js +1 -0
  142. package/web-ui/build/static/excel-BAlZ9Hkj.js +1 -0
  143. package/web-ui/build/static/excel-formula-BOW-bnHh.js +1 -0
  144. package/web-ui/build/static/factor-DCCsCpGM.js +1 -0
  145. package/web-ui/build/static/false-CnqnCzBU.js +1 -0
  146. package/web-ui/build/static/firestore-security-rules-DtkQ3uEq.js +1 -0
  147. package/web-ui/build/static/fix-CfPjl4Xr.js +1 -0
  148. package/web-ui/build/static/flix-BCA3BceS.js +1 -0
  149. package/web-ui/build/static/flow-C-5ewqYW.js +1 -0
  150. package/web-ui/build/static/fortran-CfDtl8An.js +1 -0
  151. package/web-ui/build/static/fortran-DQ_knNPt.js +1 -0
  152. package/web-ui/build/static/fsharp-CcVQ3IqX.js +1 -0
  153. package/web-ui/build/static/fsharp-olQ6ojCa.js +1 -0
  154. package/web-ui/build/static/ftl-DIWHDyWt.js +1 -0
  155. package/web-ui/build/static/gams-_NVFTSj5.js +1 -0
  156. package/web-ui/build/static/gap-DIG5TV2b.js +1 -0
  157. package/web-ui/build/static/gauss-C8rjPjTG.js +1 -0
  158. package/web-ui/build/static/gcode-8wJu4gcL.js +1 -0
  159. package/web-ui/build/static/gcode-DjHf417I.js +1 -0
  160. package/web-ui/build/static/gdscript-BZdmRCYu.js +1 -0
  161. package/web-ui/build/static/gedcom-BVFJ8C_Q.js +1 -0
  162. package/web-ui/build/static/gherkin-7NQkoaub.js +1 -0
  163. package/web-ui/build/static/gherkin-bSpNbJ48.js +1 -0
  164. package/web-ui/build/static/git-BRY_UXsc.js +1 -0
  165. package/web-ui/build/static/glsl-Ck6ShGRC.js +1 -0
  166. package/web-ui/build/static/glsl-jwCJ0Pmg.js +1 -0
  167. package/web-ui/build/static/gml-BAjG4Kr2.js +1 -0
  168. package/web-ui/build/static/gml-BnhKb5Da.js +1 -0
  169. package/web-ui/build/static/gn-Dux09iX8.js +1 -0
  170. package/web-ui/build/static/go-BOG-9Cqk.js +1 -0
  171. package/web-ui/build/static/go-BWZB_3b5.js +1 -0
  172. package/web-ui/build/static/go-module-BVLW7KBE.js +1 -0
  173. package/web-ui/build/static/golo-BD_SOfwL.js +1 -0
  174. package/web-ui/build/static/gradle-zSadWOD2.js +1 -0
  175. package/web-ui/build/static/graphql-BQlyj78B.js +1 -0
  176. package/web-ui/build/static/groovy-BurRMqQS.js +1 -0
  177. package/web-ui/build/static/groovy-SP58zwlE.js +1 -0
  178. package/web-ui/build/static/haml-eZ5ah5KY.js +1 -0
  179. package/web-ui/build/static/haml-lFC47IZb.js +1 -0
  180. package/web-ui/build/static/handlebars-B4UXrB-Q.js +1 -0
  181. package/web-ui/build/static/handlebars-CQ-Q5HnC.js +1 -0
  182. package/web-ui/build/static/haskell-AQrRyTSy.js +1 -0
  183. package/web-ui/build/static/haskell-BZTSbaB_.js +1 -0
  184. package/web-ui/build/static/haxe-5l1X6ESp.js +1 -0
  185. package/web-ui/build/static/haxe-DBn90muG.js +1 -0
  186. package/web-ui/build/static/hcl-CnMewPLM.js +1 -0
  187. package/web-ui/build/static/hlsl-RAtuBzr5.js +1 -0
  188. package/web-ui/build/static/hoon-CSqRU_4M.js +1 -0
  189. package/web-ui/build/static/hpkp-_gNbXcHt.js +1 -0
  190. package/web-ui/build/static/hsp-DY1V4au3.js +1 -0
  191. package/web-ui/build/static/hsts-j5z2jJo8.js +1 -0
  192. package/web-ui/build/static/htmlbars-C5EHvatr.js +1 -0
  193. package/web-ui/build/static/http-DgWgQrZh.js +1 -0
  194. package/web-ui/build/static/http-DphJL0q2.js +1 -0
  195. package/web-ui/build/static/hy-DnBqjPsB.js +1 -0
  196. package/web-ui/build/static/ichigojam-DeiCOKyF.js +1 -0
  197. package/web-ui/build/static/icon-CWANFWY5.js +1 -0
  198. package/web-ui/build/static/icu-message-format-C7w3vpgC.js +1 -0
  199. package/web-ui/build/static/idris-BeD8eULz.js +1 -0
  200. package/web-ui/build/static/iecst-BIznHXqY.js +1 -0
  201. package/web-ui/build/static/ignore-BcFgcNaS.js +1 -0
  202. package/web-ui/build/static/index-D8uVofpo.js +13 -0
  203. package/web-ui/build/static/index-DPFadqM6.css +1 -0
  204. package/web-ui/build/static/index-SkOgWEAU.js +1 -0
  205. package/web-ui/build/static/index-Vd3WlhtC.js +747 -0
  206. package/web-ui/build/static/inform7-CkQD_jz-.js +1 -0
  207. package/web-ui/build/static/inform7-phQiuDty.js +1 -0
  208. package/web-ui/build/static/ini-Bw_QAbzV.js +1 -0
  209. package/web-ui/build/static/ini-CB8ZxX7y.js +1 -0
  210. package/web-ui/build/static/io-D6IgpCmL.js +1 -0
  211. package/web-ui/build/static/irpf90-Ctj0koST.js +1 -0
  212. package/web-ui/build/static/isbl-D2mGcH87.js +1 -0
  213. package/web-ui/build/static/j-KvHmDBWH.js +1 -0
  214. package/web-ui/build/static/java-BeBIdo5i.js +1 -0
  215. package/web-ui/build/static/java-llFZkHLe.js +1 -0
  216. package/web-ui/build/static/javadoc-DXvQGu0s.js +1 -0
  217. package/web-ui/build/static/javadoclike-B5qdA9KZ.js +1 -0
  218. package/web-ui/build/static/javascript-De6HzHvc.js +1 -0
  219. package/web-ui/build/static/javastacktrace-C5MolKiP.js +1 -0
  220. package/web-ui/build/static/jboss-cli-2TXd54zo.js +1 -0
  221. package/web-ui/build/static/jexl-W4AVA9fi.js +1 -0
  222. package/web-ui/build/static/jolie-DTJKRMZN.js +1 -0
  223. package/web-ui/build/static/jq-BYg-QQKh.js +1 -0
  224. package/web-ui/build/static/js-extras-BrYWd2VE.js +1 -0
  225. package/web-ui/build/static/js-templates-DorYpbHq.js +1 -0
  226. package/web-ui/build/static/jsdoc-CRF8n9pZ.js +1 -0
  227. package/web-ui/build/static/json-Dlcd7rla.js +1 -0
  228. package/web-ui/build/static/json-rhOJZzzZ.js +1 -0
  229. package/web-ui/build/static/json5-hTq1nNIW.js +1 -0
  230. package/web-ui/build/static/jsonp-CMg9Xvol.js +1 -0
  231. package/web-ui/build/static/jsstacktrace-hEeYxHtB.js +1 -0
  232. package/web-ui/build/static/jsx-B7PtA8WD.js +1 -0
  233. package/web-ui/build/static/julia-CNiEEY-n.js +1 -0
  234. package/web-ui/build/static/julia-eE0_SJlc.js +1 -0
  235. package/web-ui/build/static/julia-repl-CUJTTT4C.js +1 -0
  236. package/web-ui/build/static/keepalived-GUWJBqyD.js +1 -0
  237. package/web-ui/build/static/keyman-Bdf9MJyu.js +1 -0
  238. package/web-ui/build/static/kotlin-Ch6Bej5W.js +1 -0
  239. package/web-ui/build/static/kotlin-DFJ7D7Lw.js +1 -0
  240. package/web-ui/build/static/kumir-DJLIjcCs.js +1 -0
  241. package/web-ui/build/static/kusto-BM0YTwU4.js +1 -0
  242. package/web-ui/build/static/lasso-Z1DVS84K.js +1 -0
  243. package/web-ui/build/static/latex-BWbw71RK.js +1 -0
  244. package/web-ui/build/static/latex-CMzqmbhw.js +1 -0
  245. package/web-ui/build/static/latte-C1g8_grc.js +1 -0
  246. package/web-ui/build/static/ldif-C6QH3OIL.js +1 -0
  247. package/web-ui/build/static/leaf-CbR--ZsH.js +1 -0
  248. package/web-ui/build/static/less-ClVrKh2Z.js +1 -0
  249. package/web-ui/build/static/less-DNSxm8UA.js +1 -0
  250. package/web-ui/build/static/lilypond-lTzf_sWt.js +1 -0
  251. package/web-ui/build/static/liquid-Bn91mVfC.js +1 -0
  252. package/web-ui/build/static/lisp-CU3bHohQ.js +1 -0
  253. package/web-ui/build/static/lisp-DbgzE9W8.js +1 -0
  254. package/web-ui/build/static/livecodeserver-Cse1Uz3H.js +1 -0
  255. package/web-ui/build/static/livescript-BaxbgzWP.js +1 -0
  256. package/web-ui/build/static/livescript-nJt61DBy.js +1 -0
  257. package/web-ui/build/static/llvm-DBboo6UI.js +1 -0
  258. package/web-ui/build/static/llvm-vIy7XYVy.js +1 -0
  259. package/web-ui/build/static/log-CT7nfoDW.js +1 -0
  260. package/web-ui/build/static/lolcode-CUKVytZh.js +1 -0
  261. package/web-ui/build/static/lsl-CsAOlGF2.js +1 -0
  262. package/web-ui/build/static/lua-BuU2FFxP.js +1 -0
  263. package/web-ui/build/static/lua-CiDuKQaa.js +1 -0
  264. package/web-ui/build/static/magma-7vR0zcmS.js +1 -0
  265. package/web-ui/build/static/makefile-Buiz-Msh.js +1 -0
  266. package/web-ui/build/static/makefile-DXW_-6OY.js +1 -0
  267. package/web-ui/build/static/markdown-Bk5DUoGY.js +1 -0
  268. package/web-ui/build/static/markdown-CRS5W_Ai.js +1 -0
  269. package/web-ui/build/static/markup-templating-24odpmF3.js +1 -0
  270. package/web-ui/build/static/mathematica-BxcwhJUp.js +1 -0
  271. package/web-ui/build/static/matlab-3pJYx_Fb.js +1 -0
  272. package/web-ui/build/static/matlab-BqlRrzMf.js +1 -0
  273. package/web-ui/build/static/maxima-DlCfUpcj.js +1 -0
  274. package/web-ui/build/static/maxscript-Cu_gCaFU.js +1 -0
  275. package/web-ui/build/static/mel-D7iQ-5Up.js +1 -0
  276. package/web-ui/build/static/mel-DzBKNpoN.js +1 -0
  277. package/web-ui/build/static/mercury-Dfrb-i8A.js +1 -0
  278. package/web-ui/build/static/mermaid-WN7V2_Eq.js +1 -0
  279. package/web-ui/build/static/mipsasm-CcijzL0q.js +1 -0
  280. package/web-ui/build/static/mizar-Bk68zACP.js +1 -0
  281. package/web-ui/build/static/mizar-Twc2-iZ4.js +1 -0
  282. package/web-ui/build/static/mojolicious-DBbo2S7X.js +1 -0
  283. package/web-ui/build/static/mongodb-2RsFIjgg.js +1 -0
  284. package/web-ui/build/static/monkey-CPXtQ0Bf.js +1 -0
  285. package/web-ui/build/static/monkey-DjV7Wcek.js +1 -0
  286. package/web-ui/build/static/moonscript-B5M5as70.js +1 -0
  287. package/web-ui/build/static/moonscript-D1BHW4Il.js +1 -0
  288. package/web-ui/build/static/n1ql-D0heNDBD.js +1 -0
  289. package/web-ui/build/static/n1ql-DfHqXQD7.js +1 -0
  290. package/web-ui/build/static/n4js-CaPf44Dz.js +1 -0
  291. package/web-ui/build/static/nand2tetris-hdl-D1nf9mn4.js +1 -0
  292. package/web-ui/build/static/naniscript-DnCnu5ZX.js +1 -0
  293. package/web-ui/build/static/nasm-BZrSaMsK.js +1 -0
  294. package/web-ui/build/static/neon-D29Grm2v.js +1 -0
  295. package/web-ui/build/static/nevod-DgSNbQkE.js +1 -0
  296. package/web-ui/build/static/nginx-BAaDGDfT.js +1 -0
  297. package/web-ui/build/static/nginx-BvJ1lrHX.js +1 -0
  298. package/web-ui/build/static/nim--9zzVe5F.js +1 -0
  299. package/web-ui/build/static/nim-Br1relpU.js +1 -0
  300. package/web-ui/build/static/nix--0ftErCy.js +1 -0
  301. package/web-ui/build/static/nix-104ztQqr.js +1 -0
  302. package/web-ui/build/static/node-repl-BUMAf7_p.js +1 -0
  303. package/web-ui/build/static/nsis-BaeKybNA.js +1 -0
  304. package/web-ui/build/static/nsis-CdZEv2iA.js +1 -0
  305. package/web-ui/build/static/objectivec-DBB4ymdg.js +1 -0
  306. package/web-ui/build/static/objectivec-kFYXC6g4.js +1 -0
  307. package/web-ui/build/static/ocaml-D1GXvN7c.js +1 -0
  308. package/web-ui/build/static/ocaml-D80jRMPE.js +1 -0
  309. package/web-ui/build/static/opencl-fb7BfRdO.js +1 -0
  310. package/web-ui/build/static/openqasm-CWUBrR2w.js +1 -0
  311. package/web-ui/build/static/openscad-Dim7ILSL.js +1 -0
  312. package/web-ui/build/static/oxygene-BSwApkwz.js +1 -0
  313. package/web-ui/build/static/oz-CMtRoi5F.js +1 -0
  314. package/web-ui/build/static/parigp-AH8cZ38D.js +1 -0
  315. package/web-ui/build/static/parser-bBNjuhG3.js +1 -0
  316. package/web-ui/build/static/parser3-DUtoWEAd.js +1 -0
  317. package/web-ui/build/static/pascal-Cr3DPIYT.js +1 -0
  318. package/web-ui/build/static/pascaligo-pWW12jfs.js +1 -0
  319. package/web-ui/build/static/pcaxis-DBw9rxmr.js +1 -0
  320. package/web-ui/build/static/peoplecode-aCpMPm_s.js +1 -0
  321. package/web-ui/build/static/perl-BpZ7GmJ3.js +1 -0
  322. package/web-ui/build/static/perl-fnHTrqJL.js +1 -0
  323. package/web-ui/build/static/pf-Dz352ty7.js +1 -0
  324. package/web-ui/build/static/pgsql-CHPUdlI_.js +1 -0
  325. package/web-ui/build/static/php-BRwItjmS.js +1 -0
  326. package/web-ui/build/static/php-CrX_kswO.js +1 -0
  327. package/web-ui/build/static/php-extras-BmeRXDSO.js +1 -0
  328. package/web-ui/build/static/php-template-B0MFJ9RR.js +1 -0
  329. package/web-ui/build/static/phpdoc-wAPkJj9X.js +1 -0
  330. package/web-ui/build/static/plaintext-CmPk1gvP.js +1 -0
  331. package/web-ui/build/static/plsql-pWVw0sCJ.js +1 -0
  332. package/web-ui/build/static/pony-B4SXhyDS.js +1 -0
  333. package/web-ui/build/static/powerquery-ZJ28bdRR.js +1 -0
  334. package/web-ui/build/static/powershell-CWg1ca6z.js +1 -0
  335. package/web-ui/build/static/powershell-Dnl0aBXc.js +1 -0
  336. package/web-ui/build/static/processing-CbYVU7hZ.js +1 -0
  337. package/web-ui/build/static/processing-DnroirEw.js +1 -0
  338. package/web-ui/build/static/profile-DLNc-MTA.js +1 -0
  339. package/web-ui/build/static/prolog-4KlPFQus.js +1 -0
  340. package/web-ui/build/static/prolog-CtUicY87.js +1 -0
  341. package/web-ui/build/static/promql-C_i6OJVg.js +1 -0
  342. package/web-ui/build/static/properties-Cj0lBOSP.js +1 -0
  343. package/web-ui/build/static/properties-vGFibcz9.js +1 -0
  344. package/web-ui/build/static/protobuf-BOIGxbSP.js +1 -0
  345. package/web-ui/build/static/protobuf-CQ3su-J7.js +1 -0
  346. package/web-ui/build/static/psl-DeG5_YUF.js +1 -0
  347. package/web-ui/build/static/pug-BieVVXYz.js +1 -0
  348. package/web-ui/build/static/puppet-Ba40SVKU.js +1 -0
  349. package/web-ui/build/static/puppet-D7BzrcGt.js +1 -0
  350. package/web-ui/build/static/pure-DZnkz1iT.js +1 -0
  351. package/web-ui/build/static/purebasic-CLLZW_6G.js +1 -0
  352. package/web-ui/build/static/purebasic-CYPZo_H6.js +1 -0
  353. package/web-ui/build/static/purescript-Dyjfu5Id.js +1 -0
  354. package/web-ui/build/static/python-BdIWKxdN.js +1 -0
  355. package/web-ui/build/static/python-ofKsqxv7.js +1 -0
  356. package/web-ui/build/static/python-repl-DiTYb1xK.js +1 -0
  357. package/web-ui/build/static/q-B4P0If_I.js +1 -0
  358. package/web-ui/build/static/q-t_17xfY8.js +1 -0
  359. package/web-ui/build/static/qml-B5WhiN48.js +1 -0
  360. package/web-ui/build/static/qml-Dq0cESXJ.js +1 -0
  361. package/web-ui/build/static/qore-DCx30XRf.js +1 -0
  362. package/web-ui/build/static/qsharp-UrBScekp.js +1 -0
  363. package/web-ui/build/static/r-B0Ty1RKQ.js +1 -0
  364. package/web-ui/build/static/r-B0za8QKS.js +1 -0
  365. package/web-ui/build/static/racket-Dj6WEyhS.js +1 -0
  366. package/web-ui/build/static/reason-dj9hJSfr.js +1 -0
  367. package/web-ui/build/static/reasonml-B-q5_wag.js +1 -0
  368. package/web-ui/build/static/regex-4HEc5C1m.js +1 -0
  369. package/web-ui/build/static/rego-BdQe18RK.js +1 -0
  370. package/web-ui/build/static/renpy-CVMA2llL.js +1 -0
  371. package/web-ui/build/static/rest-9B4JWVGr.js +1 -0
  372. package/web-ui/build/static/rib-DR-U8OaT.js +1 -0
  373. package/web-ui/build/static/rip-Bu2t_rFZ.js +1 -0
  374. package/web-ui/build/static/roboconf-CJeXD5-I.js +1 -0
  375. package/web-ui/build/static/roboconf-DzDTVrdM.js +1 -0
  376. package/web-ui/build/static/robotframework-CR7KyPpN.js +1 -0
  377. package/web-ui/build/static/routeros-B2741z2k.js +1 -0
  378. package/web-ui/build/static/rsl-B9F_ZCgv.js +1 -0
  379. package/web-ui/build/static/ruby-I2JTNgyY.js +1 -0
  380. package/web-ui/build/static/ruby-QGDPOmJX.js +1 -0
  381. package/web-ui/build/static/ruleslanguage-CGzXEUCO.js +1 -0
  382. package/web-ui/build/static/rust-BxW5-WOm.js +1 -0
  383. package/web-ui/build/static/rust-CSOA43di.js +1 -0
  384. package/web-ui/build/static/sas-Bclfx4g3.js +1 -0
  385. package/web-ui/build/static/sas-xbQaiYjT.js +1 -0
  386. package/web-ui/build/static/sass-DJPbdNwd.js +1 -0
  387. package/web-ui/build/static/scala-Bo18NtHQ.js +1 -0
  388. package/web-ui/build/static/scala-Cy0JH-SG.js +1 -0
  389. package/web-ui/build/static/scheme-BjcWWjIx.js +1 -0
  390. package/web-ui/build/static/scheme-DQdj8PzN.js +1 -0
  391. package/web-ui/build/static/scilab-Bn1KHdK-.js +1 -0
  392. package/web-ui/build/static/scss-B1twkZBz.js +1 -0
  393. package/web-ui/build/static/scss-DmOuMI4v.js +1 -0
  394. package/web-ui/build/static/shell-BUlkJG0S.js +1 -0
  395. package/web-ui/build/static/shell-session-Bke-svxA.js +1 -0
  396. package/web-ui/build/static/smali-Ch9S16HV.js +1 -0
  397. package/web-ui/build/static/smali-D_yDr_Aj.js +1 -0
  398. package/web-ui/build/static/smalltalk-B9TfQ5Md.js +1 -0
  399. package/web-ui/build/static/smalltalk-EwbZxZsR.js +1 -0
  400. package/web-ui/build/static/smarty-9kDPpeSm.js +1 -0
  401. package/web-ui/build/static/sml-2fEfT7rd.js +1 -0
  402. package/web-ui/build/static/sml-BiwoLNk7.js +1 -0
  403. package/web-ui/build/static/solidity-n_x8Oe0h.js +1 -0
  404. package/web-ui/build/static/solution-file-B2mvjI3e.js +1 -0
  405. package/web-ui/build/static/soy-DPkgKBIS.js +1 -0
  406. package/web-ui/build/static/sparql-Cy95tds0.js +1 -0
  407. package/web-ui/build/static/splunk-spl-Ym3z9ouN.js +1 -0
  408. package/web-ui/build/static/sqf-CXZTG8WE.js +1 -0
  409. package/web-ui/build/static/sqf-Cwi3yg7f.js +1 -0
  410. package/web-ui/build/static/sql-DPxSQY4S.js +1 -0
  411. package/web-ui/build/static/sql-peh7ijGj.js +1 -0
  412. package/web-ui/build/static/sql_more-0YAbAuPw.js +1 -0
  413. package/web-ui/build/static/squirrel-CphzjV0e.js +1 -0
  414. package/web-ui/build/static/stan-0-xZ95-O.js +1 -0
  415. package/web-ui/build/static/stan-CaI4__2g.js +1 -0
  416. package/web-ui/build/static/stata-BrbzrGSs.js +1 -0
  417. package/web-ui/build/static/step21-C_qeyVLw.js +1 -0
  418. package/web-ui/build/static/stylus-Btycb2sZ.js +1 -0
  419. package/web-ui/build/static/stylus-FoBJ7jki.js +1 -0
  420. package/web-ui/build/static/subunit-Dpg-m04-.js +1 -0
  421. package/web-ui/build/static/swift-Cr9uZmgb.js +1 -0
  422. package/web-ui/build/static/swift-hGLFtD7e.js +1 -0
  423. package/web-ui/build/static/systemd-Bls2D9Vj.js +1 -0
  424. package/web-ui/build/static/t4-cs-C4qDO-jJ.js +1 -0
  425. package/web-ui/build/static/t4-templating-BbCFPMPO.js +1 -0
  426. package/web-ui/build/static/t4-vb-D1zoEccT.js +1 -0
  427. package/web-ui/build/static/taggerscript-CWHk9Gih.js +1 -0
  428. package/web-ui/build/static/tap-Bjt0UnzV.js +1 -0
  429. package/web-ui/build/static/tap-BnHKwLQs.js +1 -0
  430. package/web-ui/build/static/tcl-Zo9kx4y-.js +1 -0
  431. package/web-ui/build/static/tcl-fzLmefkt.js +1 -0
  432. package/web-ui/build/static/textile-9lIlUPH5.js +1 -0
  433. package/web-ui/build/static/thrift-M3K6r5Cy.js +1 -0
  434. package/web-ui/build/static/toml-HpaKqckc.js +1 -0
  435. package/web-ui/build/static/tp-DFKuxrKj.js +1 -0
  436. package/web-ui/build/static/tremor-D4_bUtMB.js +1 -0
  437. package/web-ui/build/static/tsx-o1RT-T90.js +1 -0
  438. package/web-ui/build/static/tt2-1xDqcN_2.js +1 -0
  439. package/web-ui/build/static/turtle-Dlt-aGky.js +1 -0
  440. package/web-ui/build/static/twig-CJ_BnGSR.js +1 -0
  441. package/web-ui/build/static/twig-CjsiSQb6.js +1 -0
  442. package/web-ui/build/static/typescript-B8B9zUn-.js +1 -0
  443. package/web-ui/build/static/typescript-D0Jgo8O7.js +1 -0
  444. package/web-ui/build/static/typoscript-C8Qke4ZB.js +1 -0
  445. package/web-ui/build/static/unrealscript-YxJdDNZ3.js +1 -0
  446. package/web-ui/build/static/uorazor-CtEVnqBv.js +1 -0
  447. package/web-ui/build/static/uri-YdaiQl4c.js +1 -0
  448. package/web-ui/build/static/v-CIyttMDD.js +1 -0
  449. package/web-ui/build/static/vala-DGslcym_.js +1 -0
  450. package/web-ui/build/static/vala-GFPx3uEJ.js +1 -0
  451. package/web-ui/build/static/vbnet-B20itab-.js +1 -0
  452. package/web-ui/build/static/vbnet-BdoN6egk.js +1 -0
  453. package/web-ui/build/static/vbscript-PHVh6Fp_.js +1 -0
  454. package/web-ui/build/static/vbscript-html-woH1VZ7U.js +1 -0
  455. package/web-ui/build/static/velocity-DtVfCZeg.js +1 -0
  456. package/web-ui/build/static/verilog-Bt6edXvM.js +1 -0
  457. package/web-ui/build/static/verilog-k_7lr9Zq.js +1 -0
  458. package/web-ui/build/static/vhdl-BMzOgOeK.js +1 -0
  459. package/web-ui/build/static/vhdl-BcAbtPG6.js +1 -0
  460. package/web-ui/build/static/vim-DrinG9a4.js +1 -0
  461. package/web-ui/build/static/vim-WihLATJL.js +1 -0
  462. package/web-ui/build/static/visual-basic-CJnvgPjM.js +1 -0
  463. package/web-ui/build/static/warpscript-zMlbUoZs.js +1 -0
  464. package/web-ui/build/static/wasm-GUnfTBUL.js +1 -0
  465. package/web-ui/build/static/web-idl-CfaLTG_r.js +1 -0
  466. package/web-ui/build/static/wiki-13AlLoOc.js +1 -0
  467. package/web-ui/build/static/wolfram-zHocYNXW.js +1 -0
  468. package/web-ui/build/static/wren-Byq862Iu.js +1 -0
  469. package/web-ui/build/static/x86asm-CLcOnePY.js +1 -0
  470. package/web-ui/build/static/xeora-BVHqWOFS.js +1 -0
  471. package/web-ui/build/static/xl-lXi8OYfr.js +1 -0
  472. package/web-ui/build/static/xml-KZjGBKxi.js +1 -0
  473. package/web-ui/build/static/xml-doc-DrQSDcEW.js +1 -0
  474. package/web-ui/build/static/xojo-DosHeFXU.js +1 -0
  475. package/web-ui/build/static/xquery-BZN1F14Q.js +1 -0
  476. package/web-ui/build/static/xquery-Cnz7ZLFr.js +1 -0
  477. package/web-ui/build/static/yaml-BzXOcy9u.js +1 -0
  478. package/web-ui/build/static/yaml-C207y5bt.js +1 -0
  479. package/web-ui/build/static/yang-ByrBdDIg.js +1 -0
  480. package/web-ui/build/static/zephir-bahTa7of.js +1 -0
  481. package/web-ui/build/static/zig-BlFYhdtC.js +1 -0
  482. package/web-ui/build/static/index-SmQFfvBs.js +0 -746
  483. package/web-ui/build/static/index-V2ySwjHp.css +0 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Unit tests for the /api/images/:sessionId/:filename endpoint.
3
+ *
4
+ * Covers:
5
+ * - Path traversal rejection
6
+ * - Serving images from project dir (with and without active session)
7
+ * - Serving images from OS temp dir
8
+ * - Serving images from agent working directories
9
+ * - 404 when image doesn't exist anywhere
10
+ * - Content-type correctness for different extensions
11
+ */
12
+
13
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
14
+ import express from 'express';
15
+ import { createServer } from 'http';
16
+ import path from 'path';
17
+ import fs from 'fs/promises';
18
+ import os from 'os';
19
+
20
+ // ── Minimal WebServer stand-in ──────────────────────────────────
21
+ // We only mount the single route under test so the full webServer
22
+ // dependency tree isn't required.
23
+
24
+ const HTTP_STATUS = { BAD_REQUEST: 400, NOT_FOUND: 404, INTERNAL_SERVER_ERROR: 500, FORBIDDEN: 403 };
25
+
26
+ function buildApp({ sessions, orchestrator, projectDir, logger }) {
27
+ const app = express();
28
+
29
+ // Replicate the exact route from webServer.js
30
+ app.get('/api/images/:sessionId/:filename', async (req, res) => {
31
+ try {
32
+ const { sessionId, filename } = req.params;
33
+
34
+ const normalizedFilename = path.basename(filename);
35
+ if (normalizedFilename !== filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
36
+ return res.status(HTTP_STATUS.BAD_REQUEST).json({ success: false, error: 'Invalid filename' });
37
+ }
38
+
39
+ const session = sessions.get(sessionId);
40
+ const effectiveProjectDir = session?.projectDir || projectDir || process.cwd();
41
+
42
+ let imagePath = null;
43
+ const searchPaths = [
44
+ path.join(effectiveProjectDir, 'images', normalizedFilename),
45
+ path.join('/tmp/loxia-images', sessionId, normalizedFilename),
46
+ path.join('/tmp/loxia-images', normalizedFilename),
47
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', sessionId, normalizedFilename),
48
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', normalizedFilename)
49
+ ];
50
+
51
+ if (orchestrator?.agentPool) {
52
+ try {
53
+ const agents = await orchestrator.agentPool.getAllAgents();
54
+ for (const agent of agents) {
55
+ if (agent.directoryAccess?.workingDirectory) {
56
+ searchPaths.push(path.join(agent.directoryAccess.workingDirectory, 'images', normalizedFilename));
57
+ }
58
+ }
59
+ } catch (_) { /* ignore */ }
60
+ }
61
+
62
+ for (const sp of searchPaths) {
63
+ try {
64
+ const stats = await fs.stat(sp);
65
+ if (stats.isFile()) { imagePath = sp; break; }
66
+ } catch (_) { continue; }
67
+ }
68
+
69
+ if (!imagePath) {
70
+ return res.status(HTTP_STATUS.NOT_FOUND).json({ success: false, error: 'Image not found' });
71
+ }
72
+
73
+ res.sendFile(imagePath, (err) => {
74
+ if (err && !res.headersSent) {
75
+ res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: 'Failed to serve image' });
76
+ }
77
+ });
78
+ } catch (error) {
79
+ if (!res.headersSent) {
80
+ res.status(HTTP_STATUS.INTERNAL_SERVER_ERROR).json({ success: false, error: error.message });
81
+ }
82
+ }
83
+ });
84
+
85
+ return app;
86
+ }
87
+
88
+ // ── Helpers ─────────────────────────────────────────────────────
89
+
90
+ async function fetch(url) {
91
+ const resp = await globalThis.fetch(url);
92
+ const body = resp.headers.get('content-type')?.includes('json')
93
+ ? await resp.json()
94
+ : await resp.text();
95
+ return { status: resp.status, body, headers: resp.headers };
96
+ }
97
+
98
+ // ── Test suite ──────────────────────────────────────────────────
99
+
100
+ describe('/api/images/:sessionId/:filename', () => {
101
+ let server, baseUrl;
102
+ let tmpProjectDir;
103
+ let tmpAgentDir;
104
+ const sessions = new Map();
105
+ const sessionId = 'test-session-abc123';
106
+
107
+ beforeAll(async () => {
108
+ // Create temp directories that simulate project and agent dirs
109
+ tmpProjectDir = path.join(os.tmpdir(), `loxia-img-test-proj-${Date.now()}`);
110
+ tmpAgentDir = path.join(os.tmpdir(), `loxia-img-test-agent-${Date.now()}`);
111
+ await fs.mkdir(path.join(tmpProjectDir, 'images'), { recursive: true });
112
+ await fs.mkdir(path.join(tmpAgentDir, 'images'), { recursive: true });
113
+
114
+ // Also create the OS temp search path
115
+ const osTempPath = path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', sessionId);
116
+ await fs.mkdir(osTempPath, { recursive: true });
117
+
118
+ // Write test images
119
+ const pngHeader = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); // PNG magic bytes
120
+ await fs.writeFile(path.join(tmpProjectDir, 'images', 'project-image.png'), pngHeader);
121
+ await fs.writeFile(path.join(tmpAgentDir, 'images', 'agent-image.png'), pngHeader);
122
+ await fs.writeFile(path.join(osTempPath, 'temp-image.png'), pngHeader);
123
+
124
+ // Register session
125
+ sessions.set(sessionId, { projectDir: tmpProjectDir });
126
+
127
+ const orchestrator = {
128
+ agentPool: {
129
+ getAllAgents: async () => [
130
+ { directoryAccess: { workingDirectory: tmpAgentDir } }
131
+ ]
132
+ }
133
+ };
134
+
135
+ const app = buildApp({
136
+ sessions,
137
+ orchestrator,
138
+ projectDir: tmpProjectDir,
139
+ logger: { info() {}, warn() {}, error() {}, debug() {} }
140
+ });
141
+
142
+ server = createServer(app);
143
+ await new Promise(resolve => server.listen(0, resolve));
144
+ const port = server.address().port;
145
+ baseUrl = `http://localhost:${port}`;
146
+ });
147
+
148
+ afterAll(async () => {
149
+ server?.close();
150
+ await fs.rm(tmpProjectDir, { recursive: true, force: true }).catch(() => {});
151
+ await fs.rm(tmpAgentDir, { recursive: true, force: true }).catch(() => {});
152
+ const osTempPath = path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', sessionId);
153
+ await fs.rm(osTempPath, { recursive: true, force: true }).catch(() => {});
154
+ });
155
+
156
+ // ── Security ──────────────────────────────────────────────
157
+
158
+ it('rejects path traversal with ..', async () => {
159
+ const { status, body } = await fetch(`${baseUrl}/api/images/${sessionId}/..%2F..%2Fetc%2Fpasswd`);
160
+ expect(status).toBe(400);
161
+ expect(body.error).toBe('Invalid filename');
162
+ });
163
+
164
+ it('rejects filenames with slashes', async () => {
165
+ const { status, body } = await fetch(`${baseUrl}/api/images/${sessionId}/sub%2Ffile.png`);
166
+ expect(status).toBe(400);
167
+ expect(body.error).toBe('Invalid filename');
168
+ });
169
+
170
+ it('rejects filenames with backslashes', async () => {
171
+ const { status, body } = await fetch(`${baseUrl}/api/images/${sessionId}/sub%5Cfile.png`);
172
+ expect(status).toBe(400);
173
+ expect(body.error).toBe('Invalid filename');
174
+ });
175
+
176
+ // ── Serving from project directory (session active) ───────
177
+
178
+ it('serves image from project/images/ when session is active', async () => {
179
+ const { status, headers } = await fetch(`${baseUrl}/api/images/${sessionId}/project-image.png`);
180
+ expect(status).toBe(200);
181
+ expect(headers.get('content-type')).toMatch(/image|octet/);
182
+ });
183
+
184
+ // ── Serving without active session (session expired) ──────
185
+
186
+ it('still serves image when session has expired (falls back to cwd)', async () => {
187
+ // Use a sessionId that doesn't exist in the map
188
+ // Image is in the OS temp dir under the expired sessionId
189
+ const expiredSessionId = sessionId; // temp image was created under this id
190
+ // Remove session temporarily
191
+ sessions.delete(sessionId);
192
+
193
+ // The image is in OS temp: $TEMP/loxia-images/test-session-abc123/temp-image.png
194
+ const { status } = await fetch(`${baseUrl}/api/images/${expiredSessionId}/temp-image.png`);
195
+ expect(status).toBe(200);
196
+
197
+ // Restore session for other tests
198
+ sessions.set(sessionId, { projectDir: tmpProjectDir });
199
+ });
200
+
201
+ // ── Serving from agent working directory ──────────────────
202
+
203
+ it('finds image in agent working directory', async () => {
204
+ const { status } = await fetch(`${baseUrl}/api/images/${sessionId}/agent-image.png`);
205
+ expect(status).toBe(200);
206
+ });
207
+
208
+ // ── Serving from OS temp directory ────────────────────────
209
+
210
+ it('finds image in OS temp loxia-images directory', async () => {
211
+ const { status } = await fetch(`${baseUrl}/api/images/${sessionId}/temp-image.png`);
212
+ expect(status).toBe(200);
213
+ });
214
+
215
+ // ── 404 for missing images ────────────────────────────────
216
+
217
+ it('returns 404 for non-existent image', async () => {
218
+ const { status, body } = await fetch(`${baseUrl}/api/images/${sessionId}/does-not-exist.png`);
219
+ expect(status).toBe(404);
220
+ expect(body.error).toBe('Image not found');
221
+ });
222
+
223
+ it('returns 404 for unknown session + missing file', async () => {
224
+ const { status, body } = await fetch(`${baseUrl}/api/images/unknown-session-xyz/nope.png`);
225
+ expect(status).toBe(404);
226
+ expect(body.error).toBe('Image not found');
227
+ });
228
+ });
@@ -1946,15 +1946,6 @@ class WebServer {
1946
1946
  try {
1947
1947
  const { sessionId, filename } = req.params;
1948
1948
 
1949
- // Security validation: Check if sessionId exists
1950
- const session = this.sessions.get(sessionId);
1951
- if (!session) {
1952
- return res.status(HTTP_STATUS.FORBIDDEN).json({
1953
- success: false,
1954
- error: 'Invalid session'
1955
- });
1956
- }
1957
-
1958
1949
  // Security validation: Check filename for path traversal attempts
1959
1950
  const normalizedFilename = path.basename(filename);
1960
1951
  if (normalizedFilename !== filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
@@ -1964,19 +1955,32 @@ class WebServer {
1964
1955
  });
1965
1956
  }
1966
1957
 
1958
+ // Session may or may not still exist (server restarts lose session map)
1959
+ const session = this.sessions.get(sessionId);
1960
+ const projectDir = session?.projectDir || process.cwd();
1961
+
1967
1962
  // Try to locate the image in multiple possible locations
1968
1963
  let imagePath = null;
1969
1964
  const searchPaths = [
1970
1965
  // 1. Session's project directory images folder
1971
- path.join(session.projectDir || process.cwd(), 'images', normalizedFilename),
1966
+ path.join(projectDir, 'images', normalizedFilename),
1972
1967
  // 2. Temp directory for this session
1973
1968
  path.join('/tmp/loxia-images', sessionId, normalizedFilename),
1974
1969
  // 3. General temp images directory
1975
- path.join('/tmp/loxia-images', normalizedFilename)
1970
+ path.join('/tmp/loxia-images', normalizedFilename),
1971
+ // 4. OS temp dir (Windows: %TEMP%, Linux: /tmp)
1972
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', sessionId, normalizedFilename),
1973
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', normalizedFilename)
1976
1974
  ];
1977
1975
 
1978
- // 4. Search in agent working directories for this session
1979
- // Get all agents and check their workingDirectory
1976
+ // 5. Search ALL known project dirs from active sessions (not just current)
1977
+ for (const [, sess] of this.sessions) {
1978
+ if (sess?.projectDir && sess.projectDir !== projectDir) {
1979
+ searchPaths.push(path.join(sess.projectDir, 'images', normalizedFilename));
1980
+ }
1981
+ }
1982
+
1983
+ // 6. Search in agent working directories AND agent projectDirs
1980
1984
  if (this.orchestrator?.agentPool) {
1981
1985
  try {
1982
1986
  const agents = await this.orchestrator.agentPool.getAllAgents();
@@ -1986,6 +1990,10 @@ class WebServer {
1986
1990
  path.join(agent.directoryAccess.workingDirectory, 'images', normalizedFilename)
1987
1991
  );
1988
1992
  }
1993
+ // Also check the agent's own projectDir (may differ from session projectDir)
1994
+ if (agent.projectDir) {
1995
+ searchPaths.push(path.join(agent.projectDir, 'images', normalizedFilename));
1996
+ }
1989
1997
  }
1990
1998
  } catch (error) {
1991
1999
  this.logger.warn('Failed to get agent working directories for image search', {
@@ -1994,6 +2002,12 @@ class WebServer {
1994
2002
  }
1995
2003
  }
1996
2004
 
2005
+ // 7. Common user project directory (fallback for when session is gone)
2006
+ const homeDir = process.env.USERPROFILE || process.env.HOME || '';
2007
+ if (homeDir) {
2008
+ searchPaths.push(path.join(homeDir, 'Loxia', 'images', normalizedFilename));
2009
+ }
2010
+
1997
2011
  // Find the first existing file
1998
2012
  for (const searchPath of searchPaths) {
1999
2013
  try {
@@ -2576,6 +2590,76 @@ class WebServer {
2576
2590
  }
2577
2591
  });
2578
2592
 
2593
+ // ============================================
2594
+ // Artifacts API Endpoint
2595
+ // ============================================
2596
+
2597
+ // DEBUG: Inject a test artifact into an agent (remove after testing)
2598
+ this.app.post('/api/agents/:agentId/artifacts/test', async (req, res) => {
2599
+ try {
2600
+ const { agentId } = req.params;
2601
+ const agent = await this.orchestrator.agentPool.getAgent(agentId);
2602
+ if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' });
2603
+
2604
+ if (!agent.artifacts) agent.artifacts = {};
2605
+ const testPath = (agent.directoryAccess?.workingDirectory || '/test') + '/test-artifact.js';
2606
+ const displayPath = 'test-artifact.js';
2607
+
2608
+ agent.artifacts[testPath] = {
2609
+ displayPath,
2610
+ versions: [{
2611
+ id: `v-${Date.now()}`,
2612
+ content: '// Test artifact\nconsole.log("Hello from artifacts!");',
2613
+ timestamp: new Date().toISOString(),
2614
+ action: 'write',
2615
+ size: 50,
2616
+ fullPath: testPath
2617
+ }]
2618
+ };
2619
+
2620
+ // Broadcast via WebSocket
2621
+ const sessionIds = this.orchestrator.webSocketManager?.getSessionsForAgent?.(agentId) || [];
2622
+ for (const sid of sessionIds) {
2623
+ this.orchestrator.webSocketManager.broadcastToSession(sid, {
2624
+ type: 'artifacts_updated',
2625
+ data: { agentId, artifacts: agent.artifacts, workingDirectory: agent.directoryAccess?.workingDirectory || '' }
2626
+ });
2627
+ }
2628
+
2629
+ res.json({ success: true, artifactCount: Object.keys(agent.artifacts).length });
2630
+ } catch (error) {
2631
+ res.status(500).json({ success: false, error: error.message });
2632
+ }
2633
+ });
2634
+
2635
+ // Get artifacts (files written by the agent) with version history
2636
+ this.app.get('/api/agents/:agentId/artifacts', async (req, res) => {
2637
+ try {
2638
+ const { agentId } = req.params;
2639
+ const agent = await this.orchestrator.agentPool.getAgent(agentId);
2640
+
2641
+ if (!agent) {
2642
+ return res.status(404).json({ success: false, error: 'Agent not found' });
2643
+ }
2644
+
2645
+ // Return the artifacts map (or empty if none yet)
2646
+ // { [filePath]: { displayPath, versions: [{ id, content, timestamp, action, size }] } }
2647
+ res.json({
2648
+ success: true,
2649
+ agentId,
2650
+ artifacts: agent.artifacts || {},
2651
+ workingDirectory: agent.directoryAccess?.workingDirectory || ''
2652
+ });
2653
+
2654
+ } catch (error) {
2655
+ this.logger.error('Failed to get agent artifacts', {
2656
+ agentId: req.params.agentId,
2657
+ error: error.message
2658
+ });
2659
+ res.status(500).json({ success: false, error: error.message });
2660
+ }
2661
+ });
2662
+
2579
2663
  // ============================================
2580
2664
  // Terminal Tasks API Endpoints
2581
2665
  // ============================================
@@ -825,12 +825,12 @@ class ConversationCompactionService {
825
825
  const filteredMessages = allMessages.filter(m => {
826
826
  if (m === mainSystemMsg) return true;
827
827
  if (m.role === 'system') return false;
828
- if (m.type === 'tool_result' || m.role === 'tool') return false;
828
+ if (m.type === 'tool_result' || m.type === 'tool-result' || m.role === 'tool') return false;
829
829
  return true;
830
830
  });
831
831
 
832
832
  const removedSystemCount = allMessages.filter(m => m.role === 'system' && m !== mainSystemMsg).length;
833
- const removedToolCount = allMessages.filter(m => m.type === 'tool_result' || m.role === 'tool').length;
833
+ const removedToolCount = allMessages.filter(m => m.type === 'tool_result' || m.type === 'tool-result' || m.role === 'tool').length;
834
834
 
835
835
  // 3. Apply sandwich using segment identification
836
836
  const segments = this._identifySegments(filteredMessages);
@@ -155,10 +155,10 @@ class VisualEditorServer {
155
155
  return { success: true, port: this.port, message: 'Already running' };
156
156
  }
157
157
 
158
- // Find a free port starting from the preferred port
158
+ // Find a free port by checking on 0.0.0.0 (matching the actual bind address)
159
159
  const preferredPort = this.port;
160
160
  try {
161
- const actualPort = await findFreePort(preferredPort);
161
+ const actualPort = await findFreePort(preferredPort, 100, '0.0.0.0');
162
162
 
163
163
  if (actualPort !== preferredPort) {
164
164
  this.logger.info?.(`[VisualEditorServer] Port ${preferredPort} taken, using ${actualPort}`) ||
@@ -170,6 +170,15 @@ class VisualEditorServer {
170
170
  throw err;
171
171
  }
172
172
 
173
+ return this._tryListen(this.port, 10);
174
+ }
175
+
176
+ /**
177
+ * Try to listen on a port, retrying on EADDRINUSE up to maxRetries times.
178
+ * Handles the TOCTOU race between findFreePort and actual listen().
179
+ */
180
+ async _tryListen(port, maxRetries) {
181
+ this.port = port;
173
182
  this.app = express();
174
183
  this._setupMiddleware();
175
184
  this._setupRoutes();
@@ -197,9 +206,19 @@ class VisualEditorServer {
197
206
  });
198
207
 
199
208
  this.server.on('error', (err) => {
200
- // This shouldn't happen since we checked with findFreePort, but handle anyway
201
- this.logger.error?.(`[VisualEditorServer] Server error: ${err.message}`);
202
- reject(err);
209
+ if (err.code === 'EADDRINUSE' && maxRetries > 0) {
210
+ const nextPort = this.port + 1;
211
+ this.logger.info?.(`[VisualEditorServer] Port ${this.port} in use, trying ${nextPort}...`) ||
212
+ console.log(`[VisualEditorServer] Port ${this.port} in use, trying ${nextPort}...`);
213
+ // Clean up and retry on next port
214
+ try { this.server.close(); } catch {}
215
+ this.app = null;
216
+ this.server = null;
217
+ resolve(this._tryListen(nextPort, maxRetries - 1));
218
+ } else {
219
+ this.logger.error?.(`[VisualEditorServer] Server error: ${err.message}`);
220
+ reject(err);
221
+ }
203
222
  });
204
223
  });
205
224
  }
@@ -1158,8 +1177,8 @@ class VisualEditorServer {
1158
1177
 
1159
1178
  // Listen for commands from parent (Loxia Web-UI) or WebSocket
1160
1179
  window.addEventListener('message', (e) => {
1161
- if (e.data && (e.data.type === 'highlight' || e.data.type === 'scroll-to')) {
1162
- // Forward to app iframe
1180
+ if (e.data && (e.data.type === 'highlight' || e.data.type === 'scroll-to' || e.data.type === 'toggle')) {
1181
+ // Forward to app iframe (including toggle for Select/Preview mode switching)
1163
1182
  iframe.contentWindow.postMessage(e.data, '*');
1164
1183
  }
1165
1184
  });
@@ -479,13 +479,33 @@ export class ImageTool extends BaseTool {
479
479
  let imageUrl;
480
480
  let isTemporary = false;
481
481
 
482
+ this.logger?.info('Image generation result', {
483
+ jobId: job.jobId,
484
+ savedToDisk: result.savedToDisk,
485
+ resolvedOutputPath: result.resolvedOutputPath,
486
+ temporaryUrl: result.temporaryUrl?.substring(0, 80),
487
+ downloadError: result.downloadError,
488
+ isBase64Response: result.isBase64Response
489
+ });
490
+
482
491
  if (result.savedToDisk && result.resolvedOutputPath) {
483
492
  // Image was saved successfully - use our server endpoint
484
493
  imageUrl = this._convertToWebUrl(result.resolvedOutputPath, job.sessionId);
494
+ this.logger?.info('Using local server URL for image', { imageUrl });
485
495
  } else if (result.temporaryUrl) {
486
496
  // Download failed - use temporary AI-generated URL (expires in ~1 hour)
487
497
  imageUrl = result.temporaryUrl;
488
498
  isTemporary = true;
499
+ this.logger?.warn('Using temporary AI URL for image (local save failed)', {
500
+ imageUrl: imageUrl.substring(0, 80),
501
+ downloadError: result.downloadError
502
+ });
503
+ } else {
504
+ this.logger?.error('No image URL available - neither local save nor temporary URL', {
505
+ jobId: job.jobId,
506
+ savedToDisk: result.savedToDisk,
507
+ hasTemporaryUrl: !!result.temporaryUrl
508
+ });
489
509
  }
490
510
 
491
511
  global.loxiaWebServer.broadcastToSession(job.sessionId, {
@@ -788,11 +808,19 @@ export class ImageTool extends BaseTool {
788
808
 
789
809
  try {
790
810
  if (b64Json) {
791
- // Flux response: Save base64 directly to disk
792
- this.logger?.info('Saving base64 image directly to disk (Flux response)');
811
+ // Flux/GPT-Image response: Save base64 directly to disk
812
+ this.logger?.info(`Saving base64 image to disk: ${resolvedOutputPath}`);
793
813
  const imageBuffer = Buffer.from(b64Json, 'base64');
794
814
  await fs.writeFile(resolvedOutputPath, imageBuffer);
795
- savedToDisk = true;
815
+
816
+ // Verify the file was actually written
817
+ const stat = await fs.stat(resolvedOutputPath);
818
+ if (stat.size > 0) {
819
+ savedToDisk = true;
820
+ this.logger?.info(`Image saved successfully: ${resolvedOutputPath} (${stat.size} bytes)`);
821
+ } else {
822
+ this.logger?.warn(`Image file is empty after write: ${resolvedOutputPath}`);
823
+ }
796
824
 
797
825
  // For web display, we'll use our local server endpoint (set below)
798
826
  displayUrl = null; // Will be converted to web URL later
@@ -800,7 +828,15 @@ export class ImageTool extends BaseTool {
800
828
  // URL response: Download from URL
801
829
  this.logger?.info(`Downloading image from URL: ${imageUrl.substring(0, 50)}...`);
802
830
  await this._downloadImage(imageUrl, resolvedOutputPath);
803
- savedToDisk = true;
831
+
832
+ // Verify the file was actually written
833
+ const stat = await fs.stat(resolvedOutputPath);
834
+ if (stat.size > 0) {
835
+ savedToDisk = true;
836
+ this.logger?.info(`Image downloaded successfully: ${resolvedOutputPath} (${stat.size} bytes)`);
837
+ } else {
838
+ this.logger?.warn(`Downloaded image file is empty: ${resolvedOutputPath}`);
839
+ }
804
840
  displayUrl = imageUrl;
805
841
  }
806
842
 
@@ -811,7 +847,7 @@ export class ImageTool extends BaseTool {
811
847
  } catch (error) {
812
848
  // Save failed, but we might still have a temporary URL
813
849
  downloadError = error.message;
814
- this.logger?.warn(`Failed to save image to disk: ${error.message}`);
850
+ this.logger?.error(`Failed to save image to disk at ${resolvedOutputPath}: ${error.message}`);
815
851
 
816
852
  if (!imageUrl) {
817
853
  // No URL fallback for Flux - this is a real failure