onbuzz 3.4.0 → 3.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/bump-version.js +116 -0
- package/src/__test-utils__/fixtures/malformedJson.js +31 -0
- package/src/__test-utils__/globalSetup.js +9 -0
- package/src/__test-utils__/globalTeardown.js +12 -0
- package/src/__test-utils__/mockFactories.js +101 -0
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
- package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
- package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
- package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
- package/src/core/__tests__/agentPool.test.js +601 -0
- package/src/core/__tests__/agentScheduler.test.js +576 -0
- package/src/core/__tests__/contextManager.test.js +252 -0
- package/src/core/__tests__/flowExecutor.test.js +262 -0
- package/src/core/__tests__/messageProcessor.test.js +627 -0
- package/src/core/__tests__/orchestrator.test.js +257 -0
- package/src/core/__tests__/stateManager.test.js +375 -0
- package/src/core/agentPool.js +26 -4
- package/src/core/agentScheduler.js +79 -21
- package/src/core/messageProcessor.js +110 -2
- package/src/index.js +27 -11
- package/src/interfaces/__tests__/imageServing.test.js +228 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
- package/src/interfaces/webServer.js +97 -13
- package/src/services/__tests__/agentActivityService.test.js +319 -0
- package/src/services/__tests__/apiKeyManager.test.js +206 -0
- package/src/services/__tests__/benchmarkService.test.js +184 -0
- package/src/services/__tests__/budgetService.test.js +211 -0
- package/src/services/__tests__/contextInjectionService.test.js +205 -0
- package/src/services/__tests__/conversationCompactionService.test.js +280 -0
- package/src/services/__tests__/credentialVault.test.js +469 -0
- package/src/services/__tests__/errorHandler.test.js +314 -0
- package/src/services/__tests__/fileAttachmentService.test.js +278 -0
- package/src/services/__tests__/flowContextService.test.js +199 -0
- package/src/services/__tests__/memoryService.test.js +450 -0
- package/src/services/__tests__/modelRouterService.test.js +388 -0
- package/src/services/__tests__/modelsService.test.js +261 -0
- package/src/services/__tests__/portRegistry.test.js +123 -0
- package/src/services/__tests__/projectDetector.test.js +34 -0
- package/src/services/__tests__/promptService.test.js +242 -0
- package/src/services/__tests__/qualityInspector.test.js +97 -0
- package/src/services/__tests__/scheduleService.test.js +308 -0
- package/src/services/__tests__/serviceRegistry.test.js +74 -0
- package/src/services/__tests__/skillsService.test.js +402 -0
- package/src/services/__tests__/tokenCountingService.test.js +48 -0
- package/src/services/conversationCompactionService.js +2 -2
- package/src/services/visualEditorServer.js +26 -7
- package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
- package/src/tools/__tests__/agentDelayTool.test.js +342 -0
- package/src/tools/__tests__/asyncToolManager.test.js +344 -0
- package/src/tools/__tests__/baseTool.test.js +420 -0
- package/src/tools/__tests__/codeMapTool.test.js +348 -0
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
- package/src/tools/__tests__/fileTreeTool.test.js +274 -0
- package/src/tools/__tests__/filesystemTool.test.js +717 -0
- package/src/tools/__tests__/helpTool.test.js +204 -0
- package/src/tools/__tests__/jobDoneTool.test.js +296 -0
- package/src/tools/__tests__/memoryTool.test.js +297 -0
- package/src/tools/__tests__/seekTool.test.js +282 -0
- package/src/tools/__tests__/skillsTool.test.js +226 -0
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
- package/src/tools/__tests__/taskManagerTool.test.js +725 -0
- package/src/tools/__tests__/terminalTool.test.js +384 -0
- package/src/tools/__tests__/userPromptTool.test.js +297 -0
- package/src/tools/__tests__/webTool.e2e.test.js +25 -11
- package/src/tools/imageTool.js +41 -5
- package/src/tools/webTool.js +161 -48
- package/src/types/__tests__/agent.test.js +499 -0
- package/src/types/__tests__/contextReference.test.js +606 -0
- package/src/types/__tests__/conversation.test.js +555 -0
- package/src/types/__tests__/toolCommand.test.js +584 -0
- package/src/types/contextReference.js +1 -1
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
- package/src/utilities/__tests__/configManager.test.js +397 -0
- package/src/utilities/__tests__/constants.test.js +49 -0
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
- package/src/utilities/__tests__/fileProcessor.test.js +104 -0
- package/src/utilities/__tests__/jsonRepair.test.js +104 -0
- package/src/utilities/__tests__/logger.test.js +129 -0
- package/src/utilities/__tests__/platformUtils.test.js +87 -0
- package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
- package/src/utilities/__tests__/tagParser.test.js +887 -0
- package/src/utilities/__tests__/toolConstants.test.js +94 -0
- package/src/utilities/tagParser.js +2 -2
- 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/src/tools/browserTool.js +0 -897
- package/src/utilities/platformUtils.test.js +0 -98
- package/web-ui/build/static/index-SmQFfvBs.js +0 -746
- package/web-ui/build/static/index-V2ySwjHp.css +0 -1
package/src/core/agentPool.js
CHANGED
|
@@ -400,6 +400,10 @@ class AgentPool {
|
|
|
400
400
|
newConversation.compactizationStrategy = oldConversation.compactizationStrategy;
|
|
401
401
|
newConversation.originalTokenCount = oldConversation.originalTokenCount;
|
|
402
402
|
newConversation.compactedTokenCount = oldConversation.compactedTokenCount;
|
|
403
|
+
// CRITICAL: Copy the sync watermark too — without this, getMessagesForAI
|
|
404
|
+
// cannot sync new messages to compactizedMessages after a model switch,
|
|
405
|
+
// causing the AI to only see the compacted summary and repeat itself endlessly.
|
|
406
|
+
newConversation.originalMessageCountAtCompaction = oldConversation.originalMessageCountAtCompaction;
|
|
403
407
|
}
|
|
404
408
|
|
|
405
409
|
newConversation.lastUpdated = new Date().toISOString();
|
|
@@ -829,7 +833,17 @@ class AgentPool {
|
|
|
829
833
|
}
|
|
830
834
|
} catch (error) {
|
|
831
835
|
this.logger.warn(`Failed to clean up visual editor for agent: ${error.message}`, { agentId });
|
|
832
|
-
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Kill any running terminal processes for this agent
|
|
839
|
+
try {
|
|
840
|
+
const terminalTool = this.toolsRegistry?.getTool?.('terminal');
|
|
841
|
+
if (terminalTool && typeof terminalTool.cleanupAgent === 'function') {
|
|
842
|
+
await terminalTool.cleanupAgent(agentId);
|
|
843
|
+
this.logger.info(`Terminal processes cleaned up for agent: ${agentId}`);
|
|
844
|
+
}
|
|
845
|
+
} catch (error) {
|
|
846
|
+
this.logger.warn(`Failed to clean up terminal processes for agent: ${error.message}`, { agentId });
|
|
833
847
|
}
|
|
834
848
|
|
|
835
849
|
// Clean up agent resources
|
|
@@ -1554,7 +1568,11 @@ class AgentPool {
|
|
|
1554
1568
|
// vs which ones were already included in the compaction (sandwich strategy)
|
|
1555
1569
|
const compactedLength = conversation.compactizedMessages.length;
|
|
1556
1570
|
const originalLength = conversation.messages.length;
|
|
1557
|
-
|
|
1571
|
+
// SAFETY: If watermark is null/undefined (bug, migration, or cleared state),
|
|
1572
|
+
// fall back to compactedLength — NOT originalLength. Using originalLength silently
|
|
1573
|
+
// drops all unsynced messages because (originalLength > originalLength) is always false.
|
|
1574
|
+
// Using compactedLength ensures any messages beyond what's in the compacted array get synced.
|
|
1575
|
+
const originalCountAtCompaction = conversation.originalMessageCountAtCompaction ?? compactedLength;
|
|
1558
1576
|
|
|
1559
1577
|
// Only sync if NEW messages were added after compaction
|
|
1560
1578
|
// (i.e., current original length > original length when compaction happened)
|
|
@@ -1721,7 +1739,10 @@ class AgentPool {
|
|
|
1721
1739
|
if (!conversation || !conversation.compactizedMessages) return { synced: 0 };
|
|
1722
1740
|
|
|
1723
1741
|
const originalLength = conversation.messages.length;
|
|
1724
|
-
const
|
|
1742
|
+
const compactedLength = conversation.compactizedMessages.length;
|
|
1743
|
+
// SAFETY: Use ?? compactedLength instead of || originalLength to prevent silent message loss
|
|
1744
|
+
// when watermark is null (see getMessagesForAI for detailed explanation)
|
|
1745
|
+
const originalCountAtCompaction = conversation.originalMessageCountAtCompaction ?? compactedLength;
|
|
1725
1746
|
|
|
1726
1747
|
if (originalLength > originalCountAtCompaction) {
|
|
1727
1748
|
const newCount = originalLength - originalCountAtCompaction;
|
|
@@ -1734,7 +1755,8 @@ class AgentPool {
|
|
|
1734
1755
|
modelId,
|
|
1735
1756
|
synced: newCount,
|
|
1736
1757
|
newMessageRoles: newMessages.map(m => m.role),
|
|
1737
|
-
compactizedMessagesLength: conversation.compactizedMessages.length
|
|
1758
|
+
compactizedMessagesLength: conversation.compactizedMessages.length,
|
|
1759
|
+
watermarkWasNull: conversation.originalMessageCountAtCompaction === null
|
|
1738
1760
|
});
|
|
1739
1761
|
|
|
1740
1762
|
return { synced: newCount };
|
|
@@ -664,11 +664,11 @@ class AgentScheduler {
|
|
|
664
664
|
});
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
-
//
|
|
667
|
+
// Build tool results as a SEPARATE message (role: 'user' with tool-result type marker)
|
|
668
|
+
// so they are distinguishable from actual user messages in the conversation history.
|
|
668
669
|
const toolResults = allMessages.filter(m => m.queueType === 'toolResults');
|
|
670
|
+
let toolResultContent = '';
|
|
669
671
|
if (toolResults.length > 0) {
|
|
670
|
-
if (consolidatedContent) consolidatedContent += '\n\n';
|
|
671
|
-
|
|
672
672
|
// Group results by responseTurnId (the AI message that triggered them)
|
|
673
673
|
const turnGroups = new Map();
|
|
674
674
|
for (const msg of toolResults) {
|
|
@@ -681,20 +681,20 @@ class AgentScheduler {
|
|
|
681
681
|
const turnCount = turnGroups.size;
|
|
682
682
|
const toolCount = toolResults.length;
|
|
683
683
|
const toolIds = [...new Set(toolResults.map(m => m.toolId).filter(Boolean))];
|
|
684
|
-
|
|
684
|
+
toolResultContent += `[Tool Results — ${toolCount} result${toolCount > 1 ? 's' : ''} from ${turnCount} tool batch${turnCount > 1 ? 'es' : ''}: ${toolIds.join(', ')}]\n`;
|
|
685
685
|
|
|
686
686
|
if (turnCount === 1) {
|
|
687
687
|
// Single batch — flat list (no sub-headers needed)
|
|
688
688
|
toolResults.forEach(msg => {
|
|
689
|
-
|
|
689
|
+
toolResultContent += `${this.formatToolResult(msg)}\n`;
|
|
690
690
|
});
|
|
691
691
|
} else {
|
|
692
692
|
// Multiple batches — group with labeled sub-headers
|
|
693
693
|
let batchIndex = 1;
|
|
694
694
|
for (const [, group] of turnGroups) {
|
|
695
|
-
|
|
695
|
+
toolResultContent += `\n--- Batch ${batchIndex} of ${turnCount} ---\n`;
|
|
696
696
|
group.forEach(msg => {
|
|
697
|
-
|
|
697
|
+
toolResultContent += `${this.formatToolResult(msg)}\n`;
|
|
698
698
|
});
|
|
699
699
|
batchIndex++;
|
|
700
700
|
}
|
|
@@ -709,18 +709,49 @@ class AgentScheduler {
|
|
|
709
709
|
// PHASE 2: Auto-create tasks for incoming messages
|
|
710
710
|
await this.autoCreateTasksForMessages(agentId, userMessages, interAgentMessages);
|
|
711
711
|
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
timestamp: new Date().toISOString(),
|
|
718
|
-
type: 'consolidated-input',
|
|
719
|
-
originalMessageCount: allMessages.length
|
|
720
|
-
};
|
|
712
|
+
// Decide message strategy based on what's queued:
|
|
713
|
+
// - Tool results only → single message with type: 'tool-result' (most common in agent loops)
|
|
714
|
+
// - User/inter-agent only → single message with type: 'consolidated-input'
|
|
715
|
+
// - Both → must merge into single user message (API requires alternating user/assistant)
|
|
716
|
+
const hasNonToolContent = consolidatedContent.trim().length > 0;
|
|
721
717
|
|
|
722
|
-
|
|
723
|
-
|
|
718
|
+
if (toolResultContent && !hasNonToolContent) {
|
|
719
|
+
// TOOL RESULTS ONLY — add as dedicated tool-result message
|
|
720
|
+
const toolMessage = {
|
|
721
|
+
id: `tool-result-${Date.now()}`,
|
|
722
|
+
role: MESSAGE_ROLES.USER,
|
|
723
|
+
content: toolResultContent.trim(),
|
|
724
|
+
timestamp: new Date().toISOString(),
|
|
725
|
+
type: 'tool-result',
|
|
726
|
+
originalMessageCount: toolResults.length
|
|
727
|
+
};
|
|
728
|
+
await this.addMessageToConversation(agentId, toolMessage, false);
|
|
729
|
+
} else if (!toolResultContent && hasNonToolContent) {
|
|
730
|
+
// USER/INTER-AGENT ONLY — add as user message
|
|
731
|
+
const userMessage = {
|
|
732
|
+
id: `consolidated-${Date.now()}`,
|
|
733
|
+
role: MESSAGE_ROLES.USER,
|
|
734
|
+
content: consolidatedContent.trim(),
|
|
735
|
+
timestamp: new Date().toISOString(),
|
|
736
|
+
type: 'consolidated-input',
|
|
737
|
+
originalMessageCount: userMessages.length + interAgentMessages.length
|
|
738
|
+
};
|
|
739
|
+
await this.addMessageToConversation(agentId, userMessage, false);
|
|
740
|
+
} else if (toolResultContent && hasNonToolContent) {
|
|
741
|
+
// BOTH — merge into single message to avoid consecutive user messages
|
|
742
|
+
// (API requires strict user/assistant alternation)
|
|
743
|
+
// Put tool results first, then user content (user content is higher priority context)
|
|
744
|
+
const mergedContent = toolResultContent.trim() + '\n\n' + consolidatedContent.trim();
|
|
745
|
+
const mergedMessage = {
|
|
746
|
+
id: `consolidated-${Date.now()}`,
|
|
747
|
+
role: MESSAGE_ROLES.USER,
|
|
748
|
+
content: mergedContent,
|
|
749
|
+
timestamp: new Date().toISOString(),
|
|
750
|
+
type: 'consolidated-input',
|
|
751
|
+
originalMessageCount: allMessages.length
|
|
752
|
+
};
|
|
753
|
+
await this.addMessageToConversation(agentId, mergedMessage, false);
|
|
754
|
+
}
|
|
724
755
|
|
|
725
756
|
// CRITICAL: Update conversation tracking when inter-agent messages are processed
|
|
726
757
|
if (agent && interAgentMessages.length > 0) {
|
|
@@ -1604,9 +1635,12 @@ class AgentScheduler {
|
|
|
1604
1635
|
sessionId: sessionId,
|
|
1605
1636
|
platformProvided: platformProvided,
|
|
1606
1637
|
onChunk: (chunk) => {
|
|
1638
|
+
// Normalize chunk to string — some providers (Ollama) send {content, type} objects
|
|
1639
|
+
const chunkText = typeof chunk === 'string' ? chunk : (chunk?.content || chunk?.text || String(chunk));
|
|
1640
|
+
|
|
1607
1641
|
// Update flow progress if in flow execution
|
|
1608
1642
|
if (flowProgress) {
|
|
1609
|
-
flowProgress.charactersStreamed +=
|
|
1643
|
+
flowProgress.charactersStreamed += chunkText.length;
|
|
1610
1644
|
flowProgress.chunkCount++;
|
|
1611
1645
|
|
|
1612
1646
|
// Broadcast flow progress every 500ms or 50 chunks
|
|
@@ -1622,7 +1656,7 @@ class AgentScheduler {
|
|
|
1622
1656
|
type: 'stream_chunk',
|
|
1623
1657
|
agentId,
|
|
1624
1658
|
messageId: streamMessageId,
|
|
1625
|
-
content:
|
|
1659
|
+
content: chunkText,
|
|
1626
1660
|
timestamp: new Date().toISOString(),
|
|
1627
1661
|
// Include flow context if present
|
|
1628
1662
|
...(flowContext && {
|
|
@@ -2089,7 +2123,10 @@ class AgentScheduler {
|
|
|
2089
2123
|
// The scheduler's addMessageToConversation only pushes to conversation.messages.
|
|
2090
2124
|
if (modelConversation.compactizedMessages) {
|
|
2091
2125
|
const originalLength = modelConversation.messages.length;
|
|
2092
|
-
const
|
|
2126
|
+
const compactedLength = modelConversation.compactizedMessages.length;
|
|
2127
|
+
// SAFETY: Use ?? compactedLength instead of || originalLength to prevent silent message loss
|
|
2128
|
+
// when watermark is null (see agentPool.getMessagesForAI for detailed explanation)
|
|
2129
|
+
const originalCount = modelConversation.originalMessageCountAtCompaction ?? compactedLength;
|
|
2093
2130
|
if (originalLength > originalCount) {
|
|
2094
2131
|
const newCount = originalLength - originalCount;
|
|
2095
2132
|
const newMessages = modelConversation.messages.slice(-newCount);
|
|
@@ -2446,6 +2483,27 @@ class AgentScheduler {
|
|
|
2446
2483
|
toolCount: toolExecutions.length,
|
|
2447
2484
|
hasError: !!error
|
|
2448
2485
|
});
|
|
2486
|
+
|
|
2487
|
+
// If any filesystem tool ran, broadcast updated artifacts to UI
|
|
2488
|
+
const hasFilesystemOps = toolExecutions.some(t => t.toolId === 'filesystem');
|
|
2489
|
+
if (hasFilesystemOps && !error) {
|
|
2490
|
+
try {
|
|
2491
|
+
const agent = this.agentPool.agents?.get(agentId);
|
|
2492
|
+
if (agent?.artifacts && Object.keys(agent.artifacts).length > 0) {
|
|
2493
|
+
this.webSocketManager.broadcastToSession(sessionId, {
|
|
2494
|
+
type: 'artifacts_updated',
|
|
2495
|
+
data: {
|
|
2496
|
+
agentId,
|
|
2497
|
+
artifacts: agent.artifacts,
|
|
2498
|
+
workingDirectory: agent.directoryAccess?.workingDirectory || ''
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
console.log('[Artifacts] Broadcast to session:', sessionId, Object.keys(agent.artifacts).length, 'files');
|
|
2502
|
+
}
|
|
2503
|
+
} catch (e) {
|
|
2504
|
+
// Non-fatal — UI can still fetch via API
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2449
2507
|
}
|
|
2450
2508
|
}
|
|
2451
2509
|
|
|
@@ -412,12 +412,12 @@ class MessageProcessor {
|
|
|
412
412
|
});
|
|
413
413
|
|
|
414
414
|
let result;
|
|
415
|
+
let toolInput = command.parameters; // Hoisted for artifact tracking access
|
|
415
416
|
if (command.isAsync) {
|
|
416
417
|
result = await this.executeAsyncTool(command, tool, context);
|
|
417
418
|
} else {
|
|
418
419
|
// Synchronous tool execution
|
|
419
420
|
// If we have parameters object, use it. Otherwise parse the content.
|
|
420
|
-
let toolInput = command.parameters;
|
|
421
421
|
|
|
422
422
|
if (!toolInput && command.content) {
|
|
423
423
|
// Content is a string, need to parse it using tool's parseParameters method
|
|
@@ -475,7 +475,16 @@ class MessageProcessor {
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
results.push(result);
|
|
478
|
-
|
|
478
|
+
|
|
479
|
+
// ── Artifact tracking (fire-and-forget) ─────────────────────
|
|
480
|
+
// After successful filesystem writes, persist artifact metadata
|
|
481
|
+
// on the agent object so the UI can display version history.
|
|
482
|
+
// Non-blocking: uses .catch() to avoid disrupting the tool pipeline.
|
|
483
|
+
if (result.status === 'completed' && command.toolId === TOOL_IDS.FILESYSTEM) {
|
|
484
|
+
this._trackArtifacts({ ...command, parameters: toolInput }, result, context)
|
|
485
|
+
.catch(e => this.logger?.warn?.('[Artifacts] tracking failed:', e.message));
|
|
486
|
+
}
|
|
487
|
+
|
|
479
488
|
// Store in execution history
|
|
480
489
|
const historyKey = `${context.agentId}-${Date.now()}`;
|
|
481
490
|
this.executionHistory.set(historyKey, {
|
|
@@ -502,6 +511,105 @@ class MessageProcessor {
|
|
|
502
511
|
return results;
|
|
503
512
|
}
|
|
504
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Track filesystem write/append operations as artifacts on the agent.
|
|
516
|
+
* Persists lightweight metadata (path, size, timestamp) and the content
|
|
517
|
+
* so the UI can show version history without re-parsing message content.
|
|
518
|
+
*
|
|
519
|
+
* The agent.artifacts map is persisted via the normal persistAgentState flow
|
|
520
|
+
* that already runs after tool execution in AgentScheduler.
|
|
521
|
+
*
|
|
522
|
+
* @param {Object} command - Tool command with parameters
|
|
523
|
+
* @param {Object} result - Execution result
|
|
524
|
+
* @param {Object} context - Execution context (agentId, projectDir)
|
|
525
|
+
* @private
|
|
526
|
+
*/
|
|
527
|
+
async _trackArtifacts(command, result, context) {
|
|
528
|
+
try {
|
|
529
|
+
const agent = await this.agentPool?.getAgent?.(context.agentId);
|
|
530
|
+
if (!agent) {
|
|
531
|
+
console.log('[Artifacts] No agent found for', context.agentId);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Initialize artifacts map if needed: { [filePath]: { versions: [...] } }
|
|
536
|
+
if (!agent.artifacts) agent.artifacts = {};
|
|
537
|
+
|
|
538
|
+
const toolResult = result.result;
|
|
539
|
+
if (!toolResult?.success) {
|
|
540
|
+
console.log('[Artifacts] Tool result not successful:', { success: toolResult?.success, keys: Object.keys(toolResult || {}) });
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Get the actions from the command parameters
|
|
545
|
+
// The AI may send actions at different levels depending on format:
|
|
546
|
+
// { parameters: { actions: [...] } } — parsed format
|
|
547
|
+
// { actions: [...] } — top-level format (common)
|
|
548
|
+
// { parameters: { type: 'write', ... } } — single action
|
|
549
|
+
const params = command.parameters || {};
|
|
550
|
+
const actions = params.actions || command.actions || (params.type ? [params] : []);
|
|
551
|
+
console.log('[Artifacts] Processing', actions.length, 'actions. Param keys:', Object.keys(params), 'cmd keys:', Object.keys(command));
|
|
552
|
+
const resultActions = toolResult.actions || [toolResult];
|
|
553
|
+
|
|
554
|
+
for (let i = 0; i < actions.length; i++) {
|
|
555
|
+
const action = actions[i];
|
|
556
|
+
const actionResult = resultActions[i] || {};
|
|
557
|
+
const type = action.type || action.action;
|
|
558
|
+
|
|
559
|
+
console.log(`[Artifacts] Action[${i}]:`, { type, filePath: action.filePath || action['file-path'], hasContent: !!action.content, contentLen: action.content?.length, actionKeys: Object.keys(action) });
|
|
560
|
+
|
|
561
|
+
if ((type === 'write' || type === 'append') && actionResult.success !== false) {
|
|
562
|
+
// AI uses various field names: filePath, file-path, outputPath, path
|
|
563
|
+
const rawPath = action.filePath || action['file-path'] || action.outputPath || action.path;
|
|
564
|
+
const content = action.content;
|
|
565
|
+
if (!rawPath || !content) {
|
|
566
|
+
console.log('[Artifacts] Skipped: missing filePath or content', { filePath: !!rawPath, content: !!content, actionKeys: Object.keys(action) });
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Use the resolved absolute path as key (prevents collisions for same-name files in different dirs)
|
|
571
|
+
// Fall back to raw path if fullPath not available
|
|
572
|
+
const absolutePath = actionResult.fullPath || rawPath;
|
|
573
|
+
const artifactKey = absolutePath.replace(/\\/g, '/');
|
|
574
|
+
|
|
575
|
+
// Relative display path (strip working directory prefix)
|
|
576
|
+
const wd = (context.projectDir || '').replace(/\\/g, '/');
|
|
577
|
+
const displayPath = wd && artifactKey.startsWith(wd + '/')
|
|
578
|
+
? artifactKey.slice(wd.length + 1)
|
|
579
|
+
: artifactKey;
|
|
580
|
+
|
|
581
|
+
const version = {
|
|
582
|
+
id: `v-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
583
|
+
content,
|
|
584
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
585
|
+
action: type,
|
|
586
|
+
size: Buffer.byteLength(content, 'utf8'),
|
|
587
|
+
fullPath: absolutePath,
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
if (!agent.artifacts[artifactKey]) {
|
|
591
|
+
agent.artifacts[artifactKey] = { displayPath, versions: [] };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Deduplicate: skip if content identical to latest
|
|
595
|
+
const versions = agent.artifacts[artifactKey].versions;
|
|
596
|
+
const latest = versions[versions.length - 1];
|
|
597
|
+
if (latest && latest.content === content) continue;
|
|
598
|
+
|
|
599
|
+
versions.push(version);
|
|
600
|
+
console.log('[Artifacts] Tracked:', displayPath, 'v' + versions.length, '(' + version.size + ' bytes)');
|
|
601
|
+
|
|
602
|
+
// Cap at 50 versions per file to keep state reasonable
|
|
603
|
+
if (versions.length > 50) {
|
|
604
|
+
versions.splice(0, versions.length - 50);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
} catch (e) {
|
|
609
|
+
this.logger?.warn?.('Artifact tracking failed (non-fatal):', e.message);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
505
613
|
/**
|
|
506
614
|
* Execute async tool
|
|
507
615
|
* @param {Object} command - Tool command
|
package/src/index.js
CHANGED
|
@@ -32,8 +32,6 @@ import { ToolsRegistry } from './tools/baseTool.js';
|
|
|
32
32
|
import AgentDelayTool from './tools/agentDelayTool.js';
|
|
33
33
|
import TerminalTool from './tools/terminalTool.js';
|
|
34
34
|
import FileSystemTool from './tools/fileSystemTool.js';
|
|
35
|
-
// BrowserTool is DEPRECATED - use WebTool instead
|
|
36
|
-
// import BrowserTool from './tools/browserTool.js';
|
|
37
35
|
import JobDoneTool from './tools/jobDoneTool.js';
|
|
38
36
|
import AgentCommunicationTool from './tools/agentCommunicationTool.js';
|
|
39
37
|
import TaskManagerTool from './tools/taskManagerTool.js';
|
|
@@ -486,11 +484,6 @@ class LoxiaApplication {
|
|
|
486
484
|
this.logger.info('ToolsRegistry set for Help Tool');
|
|
487
485
|
}
|
|
488
486
|
|
|
489
|
-
// NOTE: BrowserTool is DEPRECATED as of December 2024
|
|
490
|
-
// Use WebTool (toolId: "web") for all browser automation tasks
|
|
491
|
-
// The Browser tool registration has been removed - WebTool provides
|
|
492
|
-
// equivalent functionality with better architecture (singleton browser instance)
|
|
493
|
-
|
|
494
487
|
// Set AgentPool dependency for AgentDelayTool
|
|
495
488
|
const agentDelayTool = this.toolsRegistry.getTool('agentdelay');
|
|
496
489
|
if (agentDelayTool && typeof agentDelayTool.setAgentPool === 'function') {
|
|
@@ -611,8 +604,8 @@ class LoxiaApplication {
|
|
|
611
604
|
if (interfaceConfig.web?.enabled !== false) {
|
|
612
605
|
// Read port from environment variables (set by CLI) or use config defaults
|
|
613
606
|
const webPort = parseInt(process.env.LOXIA_PORT || process.env.PORT, 10) || 8080;
|
|
614
|
-
// Use env var, then config, then
|
|
615
|
-
const webHost = process.env.LOXIA_HOST || interfaceConfig.web?.host || '
|
|
607
|
+
// Use env var, then config, then 0.0.0.0 (accept connections from all interfaces)
|
|
608
|
+
const webHost = process.env.LOXIA_HOST || interfaceConfig.web?.host || '0.0.0.0';
|
|
616
609
|
|
|
617
610
|
const webConfig = {
|
|
618
611
|
...interfaceConfig.web,
|
|
@@ -848,9 +841,9 @@ class LoxiaApplication {
|
|
|
848
841
|
this.logger?.info('Models service retries cancelled');
|
|
849
842
|
}
|
|
850
843
|
|
|
851
|
-
// Close Puppeteer
|
|
844
|
+
// Close Puppeteer browser (webTool) — it holds DevTools ports
|
|
852
845
|
if (this.toolsRegistry) {
|
|
853
|
-
for (const toolId of ['web'
|
|
846
|
+
for (const toolId of ['web']) {
|
|
854
847
|
try {
|
|
855
848
|
const tool = this.toolsRegistry.getTool(toolId);
|
|
856
849
|
if (tool?.cleanup) {
|
|
@@ -863,6 +856,29 @@ class LoxiaApplication {
|
|
|
863
856
|
}
|
|
864
857
|
}
|
|
865
858
|
|
|
859
|
+
// Kill ALL running terminal processes across all agents
|
|
860
|
+
if (this.toolsRegistry) {
|
|
861
|
+
try {
|
|
862
|
+
const terminalTool = this.toolsRegistry.getTool('terminal');
|
|
863
|
+
if (terminalTool?.commandTracker) {
|
|
864
|
+
let killed = 0;
|
|
865
|
+
for (const [cmdId, cmdInfo] of terminalTool.commandTracker) {
|
|
866
|
+
if (cmdInfo.process && cmdInfo.state === 'RUNNING') {
|
|
867
|
+
try {
|
|
868
|
+
cmdInfo.process.kill('SIGTERM');
|
|
869
|
+
killed++;
|
|
870
|
+
} catch { /* already dead */ }
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (killed > 0) {
|
|
874
|
+
this.logger?.info(`Killed ${killed} running terminal process(es) on shutdown`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} catch (error) {
|
|
878
|
+
this.logger?.warn('Failed to cleanup terminal processes', { error: error.message });
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
866
882
|
// Shutdown interfaces (web server, visual editor, WS connections)
|
|
867
883
|
for (const [type, interface_] of this.interfaces) {
|
|
868
884
|
try {
|
|
@@ -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
|
+
});
|