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.
- package/package.json +1 -1
- package/scripts/bump-version.js +116 -0
- package/src/core/agentPool.js +15 -3
- package/src/core/agentScheduler.js +79 -21
- package/src/core/messageProcessor.js +110 -2
- package/src/index.js +2 -2
- package/src/interfaces/__tests__/imageServing.test.js +228 -0
- package/src/interfaces/webServer.js +97 -13
- package/src/services/conversationCompactionService.js +2 -2
- package/src/services/visualEditorServer.js +26 -7
- package/src/tools/imageTool.js +41 -5
- package/src/tools/webTool.js +155 -36
- package/web-ui/build/index.html +2 -2
- package/web-ui/build/static/1c-8PZzOTzp.js +1 -0
- package/web-ui/build/static/abap-Bcx_Au1F.js +1 -0
- package/web-ui/build/static/abnf-BKTLqpWA.js +1 -0
- package/web-ui/build/static/abnf-J05BAvJt.js +1 -0
- package/web-ui/build/static/accesslog-Cp8_lqVY.js +1 -0
- package/web-ui/build/static/actionscript-BK0UaMrm.js +1 -0
- package/web-ui/build/static/actionscript-CyqZUddh.js +1 -0
- package/web-ui/build/static/ada-BNirS6Nr.js +1 -0
- package/web-ui/build/static/ada-BSFWcT1O.js +1 -0
- package/web-ui/build/static/agda-D0NJDJg7.js +1 -0
- package/web-ui/build/static/al-rWARKtwb.js +1 -0
- package/web-ui/build/static/angelscript-fCehtOYk.js +1 -0
- package/web-ui/build/static/antlr4-Dn9jrnZN.js +1 -0
- package/web-ui/build/static/apache-DaQCsvNW.js +1 -0
- package/web-ui/build/static/apacheconf-dY4i0Xvz.js +1 -0
- package/web-ui/build/static/apex-vhS4SI46.js +1 -0
- package/web-ui/build/static/apl-CKRkxH90.js +1 -0
- package/web-ui/build/static/applescript-CWmpQIEB.js +1 -0
- package/web-ui/build/static/applescript-DBaX7Uqo.js +1 -0
- package/web-ui/build/static/aql-8s41lrIa.js +1 -0
- package/web-ui/build/static/arcade-w2_RhAcq.js +1 -0
- package/web-ui/build/static/arduino-I7BtZTu6.js +1 -0
- package/web-ui/build/static/arduino-h2LZErKQ.js +1 -0
- package/web-ui/build/static/arff-C543-5a1.js +1 -0
- package/web-ui/build/static/armasm-DyZdFOzz.js +1 -0
- package/web-ui/build/static/asciidoc-ZzENlACu.js +1 -0
- package/web-ui/build/static/asciidoc-_j9x9bUz.js +1 -0
- package/web-ui/build/static/asm6502-CsNsmBfq.js +1 -0
- package/web-ui/build/static/asmatmel-CkIVf_tD.js +1 -0
- package/web-ui/build/static/aspectj-C6AQLme_.js +1 -0
- package/web-ui/build/static/aspnet-5AkdiVyL.js +1 -0
- package/web-ui/build/static/autohotkey-BRZVABiS.js +1 -0
- package/web-ui/build/static/autohotkey-DVTmfk_f.js +1 -0
- package/web-ui/build/static/autoit-3UEcWu5a.js +1 -0
- package/web-ui/build/static/autoit-BDByIKSH.js +1 -0
- package/web-ui/build/static/avisynth-BHc4uUkP.js +1 -0
- package/web-ui/build/static/avrasm-BAPq8_aI.js +1 -0
- package/web-ui/build/static/avro-idl-BKEBYUtv.js +1 -0
- package/web-ui/build/static/awk-CBCkArRT.js +1 -0
- package/web-ui/build/static/axapta-DlOgnXSZ.js +1 -0
- package/web-ui/build/static/bash-C6Brp5OE.js +1 -0
- package/web-ui/build/static/bash-DkEO7JRq.js +1 -0
- package/web-ui/build/static/basic-DG6TYB0R.js +1 -0
- package/web-ui/build/static/basic-DRPcNfAn.js +1 -0
- package/web-ui/build/static/batch-DdjZ5KC1.js +1 -0
- package/web-ui/build/static/bbcode-DCXEEs2w.js +1 -0
- package/web-ui/build/static/bicep-CpLhfOwt.js +1 -0
- package/web-ui/build/static/birb-DNWkqgQm.js +1 -0
- package/web-ui/build/static/bison-DwxbQHJ9.js +1 -0
- package/web-ui/build/static/bnf-Cgnt7npj.js +1 -0
- package/web-ui/build/static/bnf-DSTq_eu9.js +1 -0
- package/web-ui/build/static/brainfuck-Bi8mGutW.js +1 -0
- package/web-ui/build/static/brainfuck-DOWfqVtR.js +1 -0
- package/web-ui/build/static/brightscript-D95pbP-v.js +1 -0
- package/web-ui/build/static/bro-BrDVwXeg.js +1 -0
- package/web-ui/build/static/bsl-BMoXI84g.js +1 -0
- package/web-ui/build/static/c-CKH4C7-Z.js +1 -0
- package/web-ui/build/static/c-Z0txyaeJ.js +1 -0
- package/web-ui/build/static/c-like-Dzm9dMmR.js +1 -0
- package/web-ui/build/static/cal-DoyAwiUt.js +1 -0
- package/web-ui/build/static/capnproto-DeIi9LOH.js +1 -0
- package/web-ui/build/static/ceylon-Coim6DIe.js +1 -0
- package/web-ui/build/static/cfscript-CwsndC-j.js +1 -0
- package/web-ui/build/static/chaiscript-D6Aq-PSv.js +1 -0
- package/web-ui/build/static/cil-vi56VRk_.js +1 -0
- package/web-ui/build/static/clean-BfpKrTdp.js +1 -0
- package/web-ui/build/static/clojure-DUtl6BaB.js +1 -0
- package/web-ui/build/static/clojure-DXJHtDlY.js +1 -0
- package/web-ui/build/static/clojure-repl-BxwP5C3g.js +1 -0
- package/web-ui/build/static/cmake-C9_VZ1vH.js +1 -0
- package/web-ui/build/static/cmake-dplO-PGD.js +1 -0
- package/web-ui/build/static/cobol-DsZhu02V.js +1 -0
- package/web-ui/build/static/coffeescript-Cw9jtGNP.js +1 -0
- package/web-ui/build/static/coffeescript-DvDt4T2l.js +1 -0
- package/web-ui/build/static/concurnas-Bzc_Dcdd.js +1 -0
- package/web-ui/build/static/coq-Cv-5BqGo.js +1 -0
- package/web-ui/build/static/coq-DWFe2ssK.js +1 -0
- package/web-ui/build/static/cos-D6Lc6Cah.js +1 -0
- package/web-ui/build/static/cpp-BFmLjd76.js +1 -0
- package/web-ui/build/static/cpp-DVQgbHji.js +1 -0
- package/web-ui/build/static/crmsh-Cqveth9p.js +1 -0
- package/web-ui/build/static/crystal-0syYaH4Y.js +1 -0
- package/web-ui/build/static/crystal-Noptp-kr.js +1 -0
- package/web-ui/build/static/csharp-B799cFMH.js +1 -0
- package/web-ui/build/static/csharp-_HlvMZzJ.js +1 -0
- package/web-ui/build/static/cshtml-CnwOXlhP.js +1 -0
- package/web-ui/build/static/csp-1ffxIG_-.js +1 -0
- package/web-ui/build/static/csp-Bws60bPu.js +1 -0
- package/web-ui/build/static/css-BGdwXzpm.js +1 -0
- package/web-ui/build/static/css-extras-DZCECiOa.js +1 -0
- package/web-ui/build/static/csv-FMFGT0T4.js +1 -0
- package/web-ui/build/static/cypher-DnXoEwRp.js +1 -0
- package/web-ui/build/static/d-CBrts1xB.js +1 -0
- package/web-ui/build/static/d-qrJLxk2L.js +1 -0
- package/web-ui/build/static/dart-3vBSXJVV.js +1 -0
- package/web-ui/build/static/dart-Cj5b7BV9.js +1 -0
- package/web-ui/build/static/dataweave-BuFf63rk.js +1 -0
- package/web-ui/build/static/dax-Cl-se1JI.js +1 -0
- package/web-ui/build/static/delphi-CLkRb26y.js +1 -0
- package/web-ui/build/static/dhall--TIL2Z--.js +1 -0
- package/web-ui/build/static/diff-Bn-XL2om.js +1 -0
- package/web-ui/build/static/diff-BsTwly4w.js +1 -0
- package/web-ui/build/static/django-BfRtHnTS.js +1 -0
- package/web-ui/build/static/django-Dm9O4e3A.js +1 -0
- package/web-ui/build/static/dns-BIVEp3uD.js +1 -0
- package/web-ui/build/static/dns-zone-file-CO7LnOdh.js +1 -0
- package/web-ui/build/static/docker-BhwMip1R.js +1 -0
- package/web-ui/build/static/dockerfile-8Tjw9_jF.js +1 -0
- package/web-ui/build/static/dos-CRMiAo46.js +1 -0
- package/web-ui/build/static/dot-DPpW7LrJ.js +1 -0
- package/web-ui/build/static/dsconfig-D0zbYilI.js +1 -0
- package/web-ui/build/static/dts-C_-yqWEL.js +1 -0
- package/web-ui/build/static/dust-CucJgqnE.js +1 -0
- package/web-ui/build/static/ebnf-CCHK0H6j.js +1 -0
- package/web-ui/build/static/ebnf-CDdAcveH.js +1 -0
- package/web-ui/build/static/editorconfig-f-5b95UM.js +1 -0
- package/web-ui/build/static/eiffel-MmghFce7.js +1 -0
- package/web-ui/build/static/ejs-CPMN4-jk.js +1 -0
- package/web-ui/build/static/elixir-57Ldyw8h.js +1 -0
- package/web-ui/build/static/elixir-DTxnmhIt.js +1 -0
- package/web-ui/build/static/elm-ClV9zQoT.js +1 -0
- package/web-ui/build/static/elm-pX-i6o7U.js +1 -0
- package/web-ui/build/static/erb-BPeO9smT.js +1 -0
- package/web-ui/build/static/erb-BnZQ4STz.js +1 -0
- package/web-ui/build/static/erlang-DPtI7VK_.js +1 -0
- package/web-ui/build/static/erlang-KG5mg5wN.js +1 -0
- package/web-ui/build/static/erlang-repl-BqPVXZ3Y.js +1 -0
- package/web-ui/build/static/etlua-BRc0Qbbs.js +1 -0
- package/web-ui/build/static/excel-BAlZ9Hkj.js +1 -0
- package/web-ui/build/static/excel-formula-BOW-bnHh.js +1 -0
- package/web-ui/build/static/factor-DCCsCpGM.js +1 -0
- package/web-ui/build/static/false-CnqnCzBU.js +1 -0
- package/web-ui/build/static/firestore-security-rules-DtkQ3uEq.js +1 -0
- package/web-ui/build/static/fix-CfPjl4Xr.js +1 -0
- package/web-ui/build/static/flix-BCA3BceS.js +1 -0
- package/web-ui/build/static/flow-C-5ewqYW.js +1 -0
- package/web-ui/build/static/fortran-CfDtl8An.js +1 -0
- package/web-ui/build/static/fortran-DQ_knNPt.js +1 -0
- package/web-ui/build/static/fsharp-CcVQ3IqX.js +1 -0
- package/web-ui/build/static/fsharp-olQ6ojCa.js +1 -0
- package/web-ui/build/static/ftl-DIWHDyWt.js +1 -0
- package/web-ui/build/static/gams-_NVFTSj5.js +1 -0
- package/web-ui/build/static/gap-DIG5TV2b.js +1 -0
- package/web-ui/build/static/gauss-C8rjPjTG.js +1 -0
- package/web-ui/build/static/gcode-8wJu4gcL.js +1 -0
- package/web-ui/build/static/gcode-DjHf417I.js +1 -0
- package/web-ui/build/static/gdscript-BZdmRCYu.js +1 -0
- package/web-ui/build/static/gedcom-BVFJ8C_Q.js +1 -0
- package/web-ui/build/static/gherkin-7NQkoaub.js +1 -0
- package/web-ui/build/static/gherkin-bSpNbJ48.js +1 -0
- package/web-ui/build/static/git-BRY_UXsc.js +1 -0
- package/web-ui/build/static/glsl-Ck6ShGRC.js +1 -0
- package/web-ui/build/static/glsl-jwCJ0Pmg.js +1 -0
- package/web-ui/build/static/gml-BAjG4Kr2.js +1 -0
- package/web-ui/build/static/gml-BnhKb5Da.js +1 -0
- package/web-ui/build/static/gn-Dux09iX8.js +1 -0
- package/web-ui/build/static/go-BOG-9Cqk.js +1 -0
- package/web-ui/build/static/go-BWZB_3b5.js +1 -0
- package/web-ui/build/static/go-module-BVLW7KBE.js +1 -0
- package/web-ui/build/static/golo-BD_SOfwL.js +1 -0
- package/web-ui/build/static/gradle-zSadWOD2.js +1 -0
- package/web-ui/build/static/graphql-BQlyj78B.js +1 -0
- package/web-ui/build/static/groovy-BurRMqQS.js +1 -0
- package/web-ui/build/static/groovy-SP58zwlE.js +1 -0
- package/web-ui/build/static/haml-eZ5ah5KY.js +1 -0
- package/web-ui/build/static/haml-lFC47IZb.js +1 -0
- package/web-ui/build/static/handlebars-B4UXrB-Q.js +1 -0
- package/web-ui/build/static/handlebars-CQ-Q5HnC.js +1 -0
- package/web-ui/build/static/haskell-AQrRyTSy.js +1 -0
- package/web-ui/build/static/haskell-BZTSbaB_.js +1 -0
- package/web-ui/build/static/haxe-5l1X6ESp.js +1 -0
- package/web-ui/build/static/haxe-DBn90muG.js +1 -0
- package/web-ui/build/static/hcl-CnMewPLM.js +1 -0
- package/web-ui/build/static/hlsl-RAtuBzr5.js +1 -0
- package/web-ui/build/static/hoon-CSqRU_4M.js +1 -0
- package/web-ui/build/static/hpkp-_gNbXcHt.js +1 -0
- package/web-ui/build/static/hsp-DY1V4au3.js +1 -0
- package/web-ui/build/static/hsts-j5z2jJo8.js +1 -0
- package/web-ui/build/static/htmlbars-C5EHvatr.js +1 -0
- package/web-ui/build/static/http-DgWgQrZh.js +1 -0
- package/web-ui/build/static/http-DphJL0q2.js +1 -0
- package/web-ui/build/static/hy-DnBqjPsB.js +1 -0
- package/web-ui/build/static/ichigojam-DeiCOKyF.js +1 -0
- package/web-ui/build/static/icon-CWANFWY5.js +1 -0
- package/web-ui/build/static/icu-message-format-C7w3vpgC.js +1 -0
- package/web-ui/build/static/idris-BeD8eULz.js +1 -0
- package/web-ui/build/static/iecst-BIznHXqY.js +1 -0
- package/web-ui/build/static/ignore-BcFgcNaS.js +1 -0
- package/web-ui/build/static/index-D8uVofpo.js +13 -0
- package/web-ui/build/static/index-DPFadqM6.css +1 -0
- package/web-ui/build/static/index-SkOgWEAU.js +1 -0
- package/web-ui/build/static/index-Vd3WlhtC.js +747 -0
- package/web-ui/build/static/inform7-CkQD_jz-.js +1 -0
- package/web-ui/build/static/inform7-phQiuDty.js +1 -0
- package/web-ui/build/static/ini-Bw_QAbzV.js +1 -0
- package/web-ui/build/static/ini-CB8ZxX7y.js +1 -0
- package/web-ui/build/static/io-D6IgpCmL.js +1 -0
- package/web-ui/build/static/irpf90-Ctj0koST.js +1 -0
- package/web-ui/build/static/isbl-D2mGcH87.js +1 -0
- package/web-ui/build/static/j-KvHmDBWH.js +1 -0
- package/web-ui/build/static/java-BeBIdo5i.js +1 -0
- package/web-ui/build/static/java-llFZkHLe.js +1 -0
- package/web-ui/build/static/javadoc-DXvQGu0s.js +1 -0
- package/web-ui/build/static/javadoclike-B5qdA9KZ.js +1 -0
- package/web-ui/build/static/javascript-De6HzHvc.js +1 -0
- package/web-ui/build/static/javastacktrace-C5MolKiP.js +1 -0
- package/web-ui/build/static/jboss-cli-2TXd54zo.js +1 -0
- package/web-ui/build/static/jexl-W4AVA9fi.js +1 -0
- package/web-ui/build/static/jolie-DTJKRMZN.js +1 -0
- package/web-ui/build/static/jq-BYg-QQKh.js +1 -0
- package/web-ui/build/static/js-extras-BrYWd2VE.js +1 -0
- package/web-ui/build/static/js-templates-DorYpbHq.js +1 -0
- package/web-ui/build/static/jsdoc-CRF8n9pZ.js +1 -0
- package/web-ui/build/static/json-Dlcd7rla.js +1 -0
- package/web-ui/build/static/json-rhOJZzzZ.js +1 -0
- package/web-ui/build/static/json5-hTq1nNIW.js +1 -0
- package/web-ui/build/static/jsonp-CMg9Xvol.js +1 -0
- package/web-ui/build/static/jsstacktrace-hEeYxHtB.js +1 -0
- package/web-ui/build/static/jsx-B7PtA8WD.js +1 -0
- package/web-ui/build/static/julia-CNiEEY-n.js +1 -0
- package/web-ui/build/static/julia-eE0_SJlc.js +1 -0
- package/web-ui/build/static/julia-repl-CUJTTT4C.js +1 -0
- package/web-ui/build/static/keepalived-GUWJBqyD.js +1 -0
- package/web-ui/build/static/keyman-Bdf9MJyu.js +1 -0
- package/web-ui/build/static/kotlin-Ch6Bej5W.js +1 -0
- package/web-ui/build/static/kotlin-DFJ7D7Lw.js +1 -0
- package/web-ui/build/static/kumir-DJLIjcCs.js +1 -0
- package/web-ui/build/static/kusto-BM0YTwU4.js +1 -0
- package/web-ui/build/static/lasso-Z1DVS84K.js +1 -0
- package/web-ui/build/static/latex-BWbw71RK.js +1 -0
- package/web-ui/build/static/latex-CMzqmbhw.js +1 -0
- package/web-ui/build/static/latte-C1g8_grc.js +1 -0
- package/web-ui/build/static/ldif-C6QH3OIL.js +1 -0
- package/web-ui/build/static/leaf-CbR--ZsH.js +1 -0
- package/web-ui/build/static/less-ClVrKh2Z.js +1 -0
- package/web-ui/build/static/less-DNSxm8UA.js +1 -0
- package/web-ui/build/static/lilypond-lTzf_sWt.js +1 -0
- package/web-ui/build/static/liquid-Bn91mVfC.js +1 -0
- package/web-ui/build/static/lisp-CU3bHohQ.js +1 -0
- package/web-ui/build/static/lisp-DbgzE9W8.js +1 -0
- package/web-ui/build/static/livecodeserver-Cse1Uz3H.js +1 -0
- package/web-ui/build/static/livescript-BaxbgzWP.js +1 -0
- package/web-ui/build/static/livescript-nJt61DBy.js +1 -0
- package/web-ui/build/static/llvm-DBboo6UI.js +1 -0
- package/web-ui/build/static/llvm-vIy7XYVy.js +1 -0
- package/web-ui/build/static/log-CT7nfoDW.js +1 -0
- package/web-ui/build/static/lolcode-CUKVytZh.js +1 -0
- package/web-ui/build/static/lsl-CsAOlGF2.js +1 -0
- package/web-ui/build/static/lua-BuU2FFxP.js +1 -0
- package/web-ui/build/static/lua-CiDuKQaa.js +1 -0
- package/web-ui/build/static/magma-7vR0zcmS.js +1 -0
- package/web-ui/build/static/makefile-Buiz-Msh.js +1 -0
- package/web-ui/build/static/makefile-DXW_-6OY.js +1 -0
- package/web-ui/build/static/markdown-Bk5DUoGY.js +1 -0
- package/web-ui/build/static/markdown-CRS5W_Ai.js +1 -0
- package/web-ui/build/static/markup-templating-24odpmF3.js +1 -0
- package/web-ui/build/static/mathematica-BxcwhJUp.js +1 -0
- package/web-ui/build/static/matlab-3pJYx_Fb.js +1 -0
- package/web-ui/build/static/matlab-BqlRrzMf.js +1 -0
- package/web-ui/build/static/maxima-DlCfUpcj.js +1 -0
- package/web-ui/build/static/maxscript-Cu_gCaFU.js +1 -0
- package/web-ui/build/static/mel-D7iQ-5Up.js +1 -0
- package/web-ui/build/static/mel-DzBKNpoN.js +1 -0
- package/web-ui/build/static/mercury-Dfrb-i8A.js +1 -0
- package/web-ui/build/static/mermaid-WN7V2_Eq.js +1 -0
- package/web-ui/build/static/mipsasm-CcijzL0q.js +1 -0
- package/web-ui/build/static/mizar-Bk68zACP.js +1 -0
- package/web-ui/build/static/mizar-Twc2-iZ4.js +1 -0
- package/web-ui/build/static/mojolicious-DBbo2S7X.js +1 -0
- package/web-ui/build/static/mongodb-2RsFIjgg.js +1 -0
- package/web-ui/build/static/monkey-CPXtQ0Bf.js +1 -0
- package/web-ui/build/static/monkey-DjV7Wcek.js +1 -0
- package/web-ui/build/static/moonscript-B5M5as70.js +1 -0
- package/web-ui/build/static/moonscript-D1BHW4Il.js +1 -0
- package/web-ui/build/static/n1ql-D0heNDBD.js +1 -0
- package/web-ui/build/static/n1ql-DfHqXQD7.js +1 -0
- package/web-ui/build/static/n4js-CaPf44Dz.js +1 -0
- package/web-ui/build/static/nand2tetris-hdl-D1nf9mn4.js +1 -0
- package/web-ui/build/static/naniscript-DnCnu5ZX.js +1 -0
- package/web-ui/build/static/nasm-BZrSaMsK.js +1 -0
- package/web-ui/build/static/neon-D29Grm2v.js +1 -0
- package/web-ui/build/static/nevod-DgSNbQkE.js +1 -0
- package/web-ui/build/static/nginx-BAaDGDfT.js +1 -0
- package/web-ui/build/static/nginx-BvJ1lrHX.js +1 -0
- package/web-ui/build/static/nim--9zzVe5F.js +1 -0
- package/web-ui/build/static/nim-Br1relpU.js +1 -0
- package/web-ui/build/static/nix--0ftErCy.js +1 -0
- package/web-ui/build/static/nix-104ztQqr.js +1 -0
- package/web-ui/build/static/node-repl-BUMAf7_p.js +1 -0
- package/web-ui/build/static/nsis-BaeKybNA.js +1 -0
- package/web-ui/build/static/nsis-CdZEv2iA.js +1 -0
- package/web-ui/build/static/objectivec-DBB4ymdg.js +1 -0
- package/web-ui/build/static/objectivec-kFYXC6g4.js +1 -0
- package/web-ui/build/static/ocaml-D1GXvN7c.js +1 -0
- package/web-ui/build/static/ocaml-D80jRMPE.js +1 -0
- package/web-ui/build/static/opencl-fb7BfRdO.js +1 -0
- package/web-ui/build/static/openqasm-CWUBrR2w.js +1 -0
- package/web-ui/build/static/openscad-Dim7ILSL.js +1 -0
- package/web-ui/build/static/oxygene-BSwApkwz.js +1 -0
- package/web-ui/build/static/oz-CMtRoi5F.js +1 -0
- package/web-ui/build/static/parigp-AH8cZ38D.js +1 -0
- package/web-ui/build/static/parser-bBNjuhG3.js +1 -0
- package/web-ui/build/static/parser3-DUtoWEAd.js +1 -0
- package/web-ui/build/static/pascal-Cr3DPIYT.js +1 -0
- package/web-ui/build/static/pascaligo-pWW12jfs.js +1 -0
- package/web-ui/build/static/pcaxis-DBw9rxmr.js +1 -0
- package/web-ui/build/static/peoplecode-aCpMPm_s.js +1 -0
- package/web-ui/build/static/perl-BpZ7GmJ3.js +1 -0
- package/web-ui/build/static/perl-fnHTrqJL.js +1 -0
- package/web-ui/build/static/pf-Dz352ty7.js +1 -0
- package/web-ui/build/static/pgsql-CHPUdlI_.js +1 -0
- package/web-ui/build/static/php-BRwItjmS.js +1 -0
- package/web-ui/build/static/php-CrX_kswO.js +1 -0
- package/web-ui/build/static/php-extras-BmeRXDSO.js +1 -0
- package/web-ui/build/static/php-template-B0MFJ9RR.js +1 -0
- package/web-ui/build/static/phpdoc-wAPkJj9X.js +1 -0
- package/web-ui/build/static/plaintext-CmPk1gvP.js +1 -0
- package/web-ui/build/static/plsql-pWVw0sCJ.js +1 -0
- package/web-ui/build/static/pony-B4SXhyDS.js +1 -0
- package/web-ui/build/static/powerquery-ZJ28bdRR.js +1 -0
- package/web-ui/build/static/powershell-CWg1ca6z.js +1 -0
- package/web-ui/build/static/powershell-Dnl0aBXc.js +1 -0
- package/web-ui/build/static/processing-CbYVU7hZ.js +1 -0
- package/web-ui/build/static/processing-DnroirEw.js +1 -0
- package/web-ui/build/static/profile-DLNc-MTA.js +1 -0
- package/web-ui/build/static/prolog-4KlPFQus.js +1 -0
- package/web-ui/build/static/prolog-CtUicY87.js +1 -0
- package/web-ui/build/static/promql-C_i6OJVg.js +1 -0
- package/web-ui/build/static/properties-Cj0lBOSP.js +1 -0
- package/web-ui/build/static/properties-vGFibcz9.js +1 -0
- package/web-ui/build/static/protobuf-BOIGxbSP.js +1 -0
- package/web-ui/build/static/protobuf-CQ3su-J7.js +1 -0
- package/web-ui/build/static/psl-DeG5_YUF.js +1 -0
- package/web-ui/build/static/pug-BieVVXYz.js +1 -0
- package/web-ui/build/static/puppet-Ba40SVKU.js +1 -0
- package/web-ui/build/static/puppet-D7BzrcGt.js +1 -0
- package/web-ui/build/static/pure-DZnkz1iT.js +1 -0
- package/web-ui/build/static/purebasic-CLLZW_6G.js +1 -0
- package/web-ui/build/static/purebasic-CYPZo_H6.js +1 -0
- package/web-ui/build/static/purescript-Dyjfu5Id.js +1 -0
- package/web-ui/build/static/python-BdIWKxdN.js +1 -0
- package/web-ui/build/static/python-ofKsqxv7.js +1 -0
- package/web-ui/build/static/python-repl-DiTYb1xK.js +1 -0
- package/web-ui/build/static/q-B4P0If_I.js +1 -0
- package/web-ui/build/static/q-t_17xfY8.js +1 -0
- package/web-ui/build/static/qml-B5WhiN48.js +1 -0
- package/web-ui/build/static/qml-Dq0cESXJ.js +1 -0
- package/web-ui/build/static/qore-DCx30XRf.js +1 -0
- package/web-ui/build/static/qsharp-UrBScekp.js +1 -0
- package/web-ui/build/static/r-B0Ty1RKQ.js +1 -0
- package/web-ui/build/static/r-B0za8QKS.js +1 -0
- package/web-ui/build/static/racket-Dj6WEyhS.js +1 -0
- package/web-ui/build/static/reason-dj9hJSfr.js +1 -0
- package/web-ui/build/static/reasonml-B-q5_wag.js +1 -0
- package/web-ui/build/static/regex-4HEc5C1m.js +1 -0
- package/web-ui/build/static/rego-BdQe18RK.js +1 -0
- package/web-ui/build/static/renpy-CVMA2llL.js +1 -0
- package/web-ui/build/static/rest-9B4JWVGr.js +1 -0
- package/web-ui/build/static/rib-DR-U8OaT.js +1 -0
- package/web-ui/build/static/rip-Bu2t_rFZ.js +1 -0
- package/web-ui/build/static/roboconf-CJeXD5-I.js +1 -0
- package/web-ui/build/static/roboconf-DzDTVrdM.js +1 -0
- package/web-ui/build/static/robotframework-CR7KyPpN.js +1 -0
- package/web-ui/build/static/routeros-B2741z2k.js +1 -0
- package/web-ui/build/static/rsl-B9F_ZCgv.js +1 -0
- package/web-ui/build/static/ruby-I2JTNgyY.js +1 -0
- package/web-ui/build/static/ruby-QGDPOmJX.js +1 -0
- package/web-ui/build/static/ruleslanguage-CGzXEUCO.js +1 -0
- package/web-ui/build/static/rust-BxW5-WOm.js +1 -0
- package/web-ui/build/static/rust-CSOA43di.js +1 -0
- package/web-ui/build/static/sas-Bclfx4g3.js +1 -0
- package/web-ui/build/static/sas-xbQaiYjT.js +1 -0
- package/web-ui/build/static/sass-DJPbdNwd.js +1 -0
- package/web-ui/build/static/scala-Bo18NtHQ.js +1 -0
- package/web-ui/build/static/scala-Cy0JH-SG.js +1 -0
- package/web-ui/build/static/scheme-BjcWWjIx.js +1 -0
- package/web-ui/build/static/scheme-DQdj8PzN.js +1 -0
- package/web-ui/build/static/scilab-Bn1KHdK-.js +1 -0
- package/web-ui/build/static/scss-B1twkZBz.js +1 -0
- package/web-ui/build/static/scss-DmOuMI4v.js +1 -0
- package/web-ui/build/static/shell-BUlkJG0S.js +1 -0
- package/web-ui/build/static/shell-session-Bke-svxA.js +1 -0
- package/web-ui/build/static/smali-Ch9S16HV.js +1 -0
- package/web-ui/build/static/smali-D_yDr_Aj.js +1 -0
- package/web-ui/build/static/smalltalk-B9TfQ5Md.js +1 -0
- package/web-ui/build/static/smalltalk-EwbZxZsR.js +1 -0
- package/web-ui/build/static/smarty-9kDPpeSm.js +1 -0
- package/web-ui/build/static/sml-2fEfT7rd.js +1 -0
- package/web-ui/build/static/sml-BiwoLNk7.js +1 -0
- package/web-ui/build/static/solidity-n_x8Oe0h.js +1 -0
- package/web-ui/build/static/solution-file-B2mvjI3e.js +1 -0
- package/web-ui/build/static/soy-DPkgKBIS.js +1 -0
- package/web-ui/build/static/sparql-Cy95tds0.js +1 -0
- package/web-ui/build/static/splunk-spl-Ym3z9ouN.js +1 -0
- package/web-ui/build/static/sqf-CXZTG8WE.js +1 -0
- package/web-ui/build/static/sqf-Cwi3yg7f.js +1 -0
- package/web-ui/build/static/sql-DPxSQY4S.js +1 -0
- package/web-ui/build/static/sql-peh7ijGj.js +1 -0
- package/web-ui/build/static/sql_more-0YAbAuPw.js +1 -0
- package/web-ui/build/static/squirrel-CphzjV0e.js +1 -0
- package/web-ui/build/static/stan-0-xZ95-O.js +1 -0
- package/web-ui/build/static/stan-CaI4__2g.js +1 -0
- package/web-ui/build/static/stata-BrbzrGSs.js +1 -0
- package/web-ui/build/static/step21-C_qeyVLw.js +1 -0
- package/web-ui/build/static/stylus-Btycb2sZ.js +1 -0
- package/web-ui/build/static/stylus-FoBJ7jki.js +1 -0
- package/web-ui/build/static/subunit-Dpg-m04-.js +1 -0
- package/web-ui/build/static/swift-Cr9uZmgb.js +1 -0
- package/web-ui/build/static/swift-hGLFtD7e.js +1 -0
- package/web-ui/build/static/systemd-Bls2D9Vj.js +1 -0
- package/web-ui/build/static/t4-cs-C4qDO-jJ.js +1 -0
- package/web-ui/build/static/t4-templating-BbCFPMPO.js +1 -0
- package/web-ui/build/static/t4-vb-D1zoEccT.js +1 -0
- package/web-ui/build/static/taggerscript-CWHk9Gih.js +1 -0
- package/web-ui/build/static/tap-Bjt0UnzV.js +1 -0
- package/web-ui/build/static/tap-BnHKwLQs.js +1 -0
- package/web-ui/build/static/tcl-Zo9kx4y-.js +1 -0
- package/web-ui/build/static/tcl-fzLmefkt.js +1 -0
- package/web-ui/build/static/textile-9lIlUPH5.js +1 -0
- package/web-ui/build/static/thrift-M3K6r5Cy.js +1 -0
- package/web-ui/build/static/toml-HpaKqckc.js +1 -0
- package/web-ui/build/static/tp-DFKuxrKj.js +1 -0
- package/web-ui/build/static/tremor-D4_bUtMB.js +1 -0
- package/web-ui/build/static/tsx-o1RT-T90.js +1 -0
- package/web-ui/build/static/tt2-1xDqcN_2.js +1 -0
- package/web-ui/build/static/turtle-Dlt-aGky.js +1 -0
- package/web-ui/build/static/twig-CJ_BnGSR.js +1 -0
- package/web-ui/build/static/twig-CjsiSQb6.js +1 -0
- package/web-ui/build/static/typescript-B8B9zUn-.js +1 -0
- package/web-ui/build/static/typescript-D0Jgo8O7.js +1 -0
- package/web-ui/build/static/typoscript-C8Qke4ZB.js +1 -0
- package/web-ui/build/static/unrealscript-YxJdDNZ3.js +1 -0
- package/web-ui/build/static/uorazor-CtEVnqBv.js +1 -0
- package/web-ui/build/static/uri-YdaiQl4c.js +1 -0
- package/web-ui/build/static/v-CIyttMDD.js +1 -0
- package/web-ui/build/static/vala-DGslcym_.js +1 -0
- package/web-ui/build/static/vala-GFPx3uEJ.js +1 -0
- package/web-ui/build/static/vbnet-B20itab-.js +1 -0
- package/web-ui/build/static/vbnet-BdoN6egk.js +1 -0
- package/web-ui/build/static/vbscript-PHVh6Fp_.js +1 -0
- package/web-ui/build/static/vbscript-html-woH1VZ7U.js +1 -0
- package/web-ui/build/static/velocity-DtVfCZeg.js +1 -0
- package/web-ui/build/static/verilog-Bt6edXvM.js +1 -0
- package/web-ui/build/static/verilog-k_7lr9Zq.js +1 -0
- package/web-ui/build/static/vhdl-BMzOgOeK.js +1 -0
- package/web-ui/build/static/vhdl-BcAbtPG6.js +1 -0
- package/web-ui/build/static/vim-DrinG9a4.js +1 -0
- package/web-ui/build/static/vim-WihLATJL.js +1 -0
- package/web-ui/build/static/visual-basic-CJnvgPjM.js +1 -0
- package/web-ui/build/static/warpscript-zMlbUoZs.js +1 -0
- package/web-ui/build/static/wasm-GUnfTBUL.js +1 -0
- package/web-ui/build/static/web-idl-CfaLTG_r.js +1 -0
- package/web-ui/build/static/wiki-13AlLoOc.js +1 -0
- package/web-ui/build/static/wolfram-zHocYNXW.js +1 -0
- package/web-ui/build/static/wren-Byq862Iu.js +1 -0
- package/web-ui/build/static/x86asm-CLcOnePY.js +1 -0
- package/web-ui/build/static/xeora-BVHqWOFS.js +1 -0
- package/web-ui/build/static/xl-lXi8OYfr.js +1 -0
- package/web-ui/build/static/xml-KZjGBKxi.js +1 -0
- package/web-ui/build/static/xml-doc-DrQSDcEW.js +1 -0
- package/web-ui/build/static/xojo-DosHeFXU.js +1 -0
- package/web-ui/build/static/xquery-BZN1F14Q.js +1 -0
- package/web-ui/build/static/xquery-Cnz7ZLFr.js +1 -0
- package/web-ui/build/static/yaml-BzXOcy9u.js +1 -0
- package/web-ui/build/static/yaml-C207y5bt.js +1 -0
- package/web-ui/build/static/yang-ByrBdDIg.js +1 -0
- package/web-ui/build/static/zephir-bahTa7of.js +1 -0
- package/web-ui/build/static/zig-BlFYhdtC.js +1 -0
- package/web-ui/build/static/index-SmQFfvBs.js +0 -746
- 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(
|
|
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
|
-
//
|
|
1979
|
-
|
|
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
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
});
|
package/src/tools/imageTool.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|