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.
Files changed (562) hide show
  1. package/package.json +1 -1
  2. package/scripts/bump-version.js +116 -0
  3. package/src/__test-utils__/fixtures/malformedJson.js +31 -0
  4. package/src/__test-utils__/globalSetup.js +9 -0
  5. package/src/__test-utils__/globalTeardown.js +12 -0
  6. package/src/__test-utils__/mockFactories.js +101 -0
  7. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
  8. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
  9. package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
  10. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
  11. package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
  12. package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
  13. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
  14. package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
  15. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
  16. package/src/core/__tests__/agentPool.test.js +601 -0
  17. package/src/core/__tests__/agentScheduler.test.js +576 -0
  18. package/src/core/__tests__/contextManager.test.js +252 -0
  19. package/src/core/__tests__/flowExecutor.test.js +262 -0
  20. package/src/core/__tests__/messageProcessor.test.js +627 -0
  21. package/src/core/__tests__/orchestrator.test.js +257 -0
  22. package/src/core/__tests__/stateManager.test.js +375 -0
  23. package/src/core/agentPool.js +26 -4
  24. package/src/core/agentScheduler.js +79 -21
  25. package/src/core/messageProcessor.js +110 -2
  26. package/src/index.js +27 -11
  27. package/src/interfaces/__tests__/imageServing.test.js +228 -0
  28. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
  29. package/src/interfaces/webServer.js +97 -13
  30. package/src/services/__tests__/agentActivityService.test.js +319 -0
  31. package/src/services/__tests__/apiKeyManager.test.js +206 -0
  32. package/src/services/__tests__/benchmarkService.test.js +184 -0
  33. package/src/services/__tests__/budgetService.test.js +211 -0
  34. package/src/services/__tests__/contextInjectionService.test.js +205 -0
  35. package/src/services/__tests__/conversationCompactionService.test.js +280 -0
  36. package/src/services/__tests__/credentialVault.test.js +469 -0
  37. package/src/services/__tests__/errorHandler.test.js +314 -0
  38. package/src/services/__tests__/fileAttachmentService.test.js +278 -0
  39. package/src/services/__tests__/flowContextService.test.js +199 -0
  40. package/src/services/__tests__/memoryService.test.js +450 -0
  41. package/src/services/__tests__/modelRouterService.test.js +388 -0
  42. package/src/services/__tests__/modelsService.test.js +261 -0
  43. package/src/services/__tests__/portRegistry.test.js +123 -0
  44. package/src/services/__tests__/projectDetector.test.js +34 -0
  45. package/src/services/__tests__/promptService.test.js +242 -0
  46. package/src/services/__tests__/qualityInspector.test.js +97 -0
  47. package/src/services/__tests__/scheduleService.test.js +308 -0
  48. package/src/services/__tests__/serviceRegistry.test.js +74 -0
  49. package/src/services/__tests__/skillsService.test.js +402 -0
  50. package/src/services/__tests__/tokenCountingService.test.js +48 -0
  51. package/src/services/conversationCompactionService.js +2 -2
  52. package/src/services/visualEditorServer.js +26 -7
  53. package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
  54. package/src/tools/__tests__/agentDelayTool.test.js +342 -0
  55. package/src/tools/__tests__/asyncToolManager.test.js +344 -0
  56. package/src/tools/__tests__/baseTool.test.js +420 -0
  57. package/src/tools/__tests__/codeMapTool.test.js +348 -0
  58. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
  59. package/src/tools/__tests__/fileTreeTool.test.js +274 -0
  60. package/src/tools/__tests__/filesystemTool.test.js +717 -0
  61. package/src/tools/__tests__/helpTool.test.js +204 -0
  62. package/src/tools/__tests__/jobDoneTool.test.js +296 -0
  63. package/src/tools/__tests__/memoryTool.test.js +297 -0
  64. package/src/tools/__tests__/seekTool.test.js +282 -0
  65. package/src/tools/__tests__/skillsTool.test.js +226 -0
  66. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
  67. package/src/tools/__tests__/taskManagerTool.test.js +725 -0
  68. package/src/tools/__tests__/terminalTool.test.js +384 -0
  69. package/src/tools/__tests__/userPromptTool.test.js +297 -0
  70. package/src/tools/__tests__/webTool.e2e.test.js +25 -11
  71. package/src/tools/imageTool.js +41 -5
  72. package/src/tools/webTool.js +161 -48
  73. package/src/types/__tests__/agent.test.js +499 -0
  74. package/src/types/__tests__/contextReference.test.js +606 -0
  75. package/src/types/__tests__/conversation.test.js +555 -0
  76. package/src/types/__tests__/toolCommand.test.js +584 -0
  77. package/src/types/contextReference.js +1 -1
  78. package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
  79. package/src/utilities/__tests__/configManager.test.js +397 -0
  80. package/src/utilities/__tests__/constants.test.js +49 -0
  81. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
  82. package/src/utilities/__tests__/fileProcessor.test.js +104 -0
  83. package/src/utilities/__tests__/jsonRepair.test.js +104 -0
  84. package/src/utilities/__tests__/logger.test.js +129 -0
  85. package/src/utilities/__tests__/platformUtils.test.js +87 -0
  86. package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
  87. package/src/utilities/__tests__/tagParser.test.js +887 -0
  88. package/src/utilities/__tests__/toolConstants.test.js +94 -0
  89. package/src/utilities/tagParser.js +2 -2
  90. package/web-ui/build/index.html +2 -2
  91. package/web-ui/build/static/1c-8PZzOTzp.js +1 -0
  92. package/web-ui/build/static/abap-Bcx_Au1F.js +1 -0
  93. package/web-ui/build/static/abnf-BKTLqpWA.js +1 -0
  94. package/web-ui/build/static/abnf-J05BAvJt.js +1 -0
  95. package/web-ui/build/static/accesslog-Cp8_lqVY.js +1 -0
  96. package/web-ui/build/static/actionscript-BK0UaMrm.js +1 -0
  97. package/web-ui/build/static/actionscript-CyqZUddh.js +1 -0
  98. package/web-ui/build/static/ada-BNirS6Nr.js +1 -0
  99. package/web-ui/build/static/ada-BSFWcT1O.js +1 -0
  100. package/web-ui/build/static/agda-D0NJDJg7.js +1 -0
  101. package/web-ui/build/static/al-rWARKtwb.js +1 -0
  102. package/web-ui/build/static/angelscript-fCehtOYk.js +1 -0
  103. package/web-ui/build/static/antlr4-Dn9jrnZN.js +1 -0
  104. package/web-ui/build/static/apache-DaQCsvNW.js +1 -0
  105. package/web-ui/build/static/apacheconf-dY4i0Xvz.js +1 -0
  106. package/web-ui/build/static/apex-vhS4SI46.js +1 -0
  107. package/web-ui/build/static/apl-CKRkxH90.js +1 -0
  108. package/web-ui/build/static/applescript-CWmpQIEB.js +1 -0
  109. package/web-ui/build/static/applescript-DBaX7Uqo.js +1 -0
  110. package/web-ui/build/static/aql-8s41lrIa.js +1 -0
  111. package/web-ui/build/static/arcade-w2_RhAcq.js +1 -0
  112. package/web-ui/build/static/arduino-I7BtZTu6.js +1 -0
  113. package/web-ui/build/static/arduino-h2LZErKQ.js +1 -0
  114. package/web-ui/build/static/arff-C543-5a1.js +1 -0
  115. package/web-ui/build/static/armasm-DyZdFOzz.js +1 -0
  116. package/web-ui/build/static/asciidoc-ZzENlACu.js +1 -0
  117. package/web-ui/build/static/asciidoc-_j9x9bUz.js +1 -0
  118. package/web-ui/build/static/asm6502-CsNsmBfq.js +1 -0
  119. package/web-ui/build/static/asmatmel-CkIVf_tD.js +1 -0
  120. package/web-ui/build/static/aspectj-C6AQLme_.js +1 -0
  121. package/web-ui/build/static/aspnet-5AkdiVyL.js +1 -0
  122. package/web-ui/build/static/autohotkey-BRZVABiS.js +1 -0
  123. package/web-ui/build/static/autohotkey-DVTmfk_f.js +1 -0
  124. package/web-ui/build/static/autoit-3UEcWu5a.js +1 -0
  125. package/web-ui/build/static/autoit-BDByIKSH.js +1 -0
  126. package/web-ui/build/static/avisynth-BHc4uUkP.js +1 -0
  127. package/web-ui/build/static/avrasm-BAPq8_aI.js +1 -0
  128. package/web-ui/build/static/avro-idl-BKEBYUtv.js +1 -0
  129. package/web-ui/build/static/awk-CBCkArRT.js +1 -0
  130. package/web-ui/build/static/axapta-DlOgnXSZ.js +1 -0
  131. package/web-ui/build/static/bash-C6Brp5OE.js +1 -0
  132. package/web-ui/build/static/bash-DkEO7JRq.js +1 -0
  133. package/web-ui/build/static/basic-DG6TYB0R.js +1 -0
  134. package/web-ui/build/static/basic-DRPcNfAn.js +1 -0
  135. package/web-ui/build/static/batch-DdjZ5KC1.js +1 -0
  136. package/web-ui/build/static/bbcode-DCXEEs2w.js +1 -0
  137. package/web-ui/build/static/bicep-CpLhfOwt.js +1 -0
  138. package/web-ui/build/static/birb-DNWkqgQm.js +1 -0
  139. package/web-ui/build/static/bison-DwxbQHJ9.js +1 -0
  140. package/web-ui/build/static/bnf-Cgnt7npj.js +1 -0
  141. package/web-ui/build/static/bnf-DSTq_eu9.js +1 -0
  142. package/web-ui/build/static/brainfuck-Bi8mGutW.js +1 -0
  143. package/web-ui/build/static/brainfuck-DOWfqVtR.js +1 -0
  144. package/web-ui/build/static/brightscript-D95pbP-v.js +1 -0
  145. package/web-ui/build/static/bro-BrDVwXeg.js +1 -0
  146. package/web-ui/build/static/bsl-BMoXI84g.js +1 -0
  147. package/web-ui/build/static/c-CKH4C7-Z.js +1 -0
  148. package/web-ui/build/static/c-Z0txyaeJ.js +1 -0
  149. package/web-ui/build/static/c-like-Dzm9dMmR.js +1 -0
  150. package/web-ui/build/static/cal-DoyAwiUt.js +1 -0
  151. package/web-ui/build/static/capnproto-DeIi9LOH.js +1 -0
  152. package/web-ui/build/static/ceylon-Coim6DIe.js +1 -0
  153. package/web-ui/build/static/cfscript-CwsndC-j.js +1 -0
  154. package/web-ui/build/static/chaiscript-D6Aq-PSv.js +1 -0
  155. package/web-ui/build/static/cil-vi56VRk_.js +1 -0
  156. package/web-ui/build/static/clean-BfpKrTdp.js +1 -0
  157. package/web-ui/build/static/clojure-DUtl6BaB.js +1 -0
  158. package/web-ui/build/static/clojure-DXJHtDlY.js +1 -0
  159. package/web-ui/build/static/clojure-repl-BxwP5C3g.js +1 -0
  160. package/web-ui/build/static/cmake-C9_VZ1vH.js +1 -0
  161. package/web-ui/build/static/cmake-dplO-PGD.js +1 -0
  162. package/web-ui/build/static/cobol-DsZhu02V.js +1 -0
  163. package/web-ui/build/static/coffeescript-Cw9jtGNP.js +1 -0
  164. package/web-ui/build/static/coffeescript-DvDt4T2l.js +1 -0
  165. package/web-ui/build/static/concurnas-Bzc_Dcdd.js +1 -0
  166. package/web-ui/build/static/coq-Cv-5BqGo.js +1 -0
  167. package/web-ui/build/static/coq-DWFe2ssK.js +1 -0
  168. package/web-ui/build/static/cos-D6Lc6Cah.js +1 -0
  169. package/web-ui/build/static/cpp-BFmLjd76.js +1 -0
  170. package/web-ui/build/static/cpp-DVQgbHji.js +1 -0
  171. package/web-ui/build/static/crmsh-Cqveth9p.js +1 -0
  172. package/web-ui/build/static/crystal-0syYaH4Y.js +1 -0
  173. package/web-ui/build/static/crystal-Noptp-kr.js +1 -0
  174. package/web-ui/build/static/csharp-B799cFMH.js +1 -0
  175. package/web-ui/build/static/csharp-_HlvMZzJ.js +1 -0
  176. package/web-ui/build/static/cshtml-CnwOXlhP.js +1 -0
  177. package/web-ui/build/static/csp-1ffxIG_-.js +1 -0
  178. package/web-ui/build/static/csp-Bws60bPu.js +1 -0
  179. package/web-ui/build/static/css-BGdwXzpm.js +1 -0
  180. package/web-ui/build/static/css-extras-DZCECiOa.js +1 -0
  181. package/web-ui/build/static/csv-FMFGT0T4.js +1 -0
  182. package/web-ui/build/static/cypher-DnXoEwRp.js +1 -0
  183. package/web-ui/build/static/d-CBrts1xB.js +1 -0
  184. package/web-ui/build/static/d-qrJLxk2L.js +1 -0
  185. package/web-ui/build/static/dart-3vBSXJVV.js +1 -0
  186. package/web-ui/build/static/dart-Cj5b7BV9.js +1 -0
  187. package/web-ui/build/static/dataweave-BuFf63rk.js +1 -0
  188. package/web-ui/build/static/dax-Cl-se1JI.js +1 -0
  189. package/web-ui/build/static/delphi-CLkRb26y.js +1 -0
  190. package/web-ui/build/static/dhall--TIL2Z--.js +1 -0
  191. package/web-ui/build/static/diff-Bn-XL2om.js +1 -0
  192. package/web-ui/build/static/diff-BsTwly4w.js +1 -0
  193. package/web-ui/build/static/django-BfRtHnTS.js +1 -0
  194. package/web-ui/build/static/django-Dm9O4e3A.js +1 -0
  195. package/web-ui/build/static/dns-BIVEp3uD.js +1 -0
  196. package/web-ui/build/static/dns-zone-file-CO7LnOdh.js +1 -0
  197. package/web-ui/build/static/docker-BhwMip1R.js +1 -0
  198. package/web-ui/build/static/dockerfile-8Tjw9_jF.js +1 -0
  199. package/web-ui/build/static/dos-CRMiAo46.js +1 -0
  200. package/web-ui/build/static/dot-DPpW7LrJ.js +1 -0
  201. package/web-ui/build/static/dsconfig-D0zbYilI.js +1 -0
  202. package/web-ui/build/static/dts-C_-yqWEL.js +1 -0
  203. package/web-ui/build/static/dust-CucJgqnE.js +1 -0
  204. package/web-ui/build/static/ebnf-CCHK0H6j.js +1 -0
  205. package/web-ui/build/static/ebnf-CDdAcveH.js +1 -0
  206. package/web-ui/build/static/editorconfig-f-5b95UM.js +1 -0
  207. package/web-ui/build/static/eiffel-MmghFce7.js +1 -0
  208. package/web-ui/build/static/ejs-CPMN4-jk.js +1 -0
  209. package/web-ui/build/static/elixir-57Ldyw8h.js +1 -0
  210. package/web-ui/build/static/elixir-DTxnmhIt.js +1 -0
  211. package/web-ui/build/static/elm-ClV9zQoT.js +1 -0
  212. package/web-ui/build/static/elm-pX-i6o7U.js +1 -0
  213. package/web-ui/build/static/erb-BPeO9smT.js +1 -0
  214. package/web-ui/build/static/erb-BnZQ4STz.js +1 -0
  215. package/web-ui/build/static/erlang-DPtI7VK_.js +1 -0
  216. package/web-ui/build/static/erlang-KG5mg5wN.js +1 -0
  217. package/web-ui/build/static/erlang-repl-BqPVXZ3Y.js +1 -0
  218. package/web-ui/build/static/etlua-BRc0Qbbs.js +1 -0
  219. package/web-ui/build/static/excel-BAlZ9Hkj.js +1 -0
  220. package/web-ui/build/static/excel-formula-BOW-bnHh.js +1 -0
  221. package/web-ui/build/static/factor-DCCsCpGM.js +1 -0
  222. package/web-ui/build/static/false-CnqnCzBU.js +1 -0
  223. package/web-ui/build/static/firestore-security-rules-DtkQ3uEq.js +1 -0
  224. package/web-ui/build/static/fix-CfPjl4Xr.js +1 -0
  225. package/web-ui/build/static/flix-BCA3BceS.js +1 -0
  226. package/web-ui/build/static/flow-C-5ewqYW.js +1 -0
  227. package/web-ui/build/static/fortran-CfDtl8An.js +1 -0
  228. package/web-ui/build/static/fortran-DQ_knNPt.js +1 -0
  229. package/web-ui/build/static/fsharp-CcVQ3IqX.js +1 -0
  230. package/web-ui/build/static/fsharp-olQ6ojCa.js +1 -0
  231. package/web-ui/build/static/ftl-DIWHDyWt.js +1 -0
  232. package/web-ui/build/static/gams-_NVFTSj5.js +1 -0
  233. package/web-ui/build/static/gap-DIG5TV2b.js +1 -0
  234. package/web-ui/build/static/gauss-C8rjPjTG.js +1 -0
  235. package/web-ui/build/static/gcode-8wJu4gcL.js +1 -0
  236. package/web-ui/build/static/gcode-DjHf417I.js +1 -0
  237. package/web-ui/build/static/gdscript-BZdmRCYu.js +1 -0
  238. package/web-ui/build/static/gedcom-BVFJ8C_Q.js +1 -0
  239. package/web-ui/build/static/gherkin-7NQkoaub.js +1 -0
  240. package/web-ui/build/static/gherkin-bSpNbJ48.js +1 -0
  241. package/web-ui/build/static/git-BRY_UXsc.js +1 -0
  242. package/web-ui/build/static/glsl-Ck6ShGRC.js +1 -0
  243. package/web-ui/build/static/glsl-jwCJ0Pmg.js +1 -0
  244. package/web-ui/build/static/gml-BAjG4Kr2.js +1 -0
  245. package/web-ui/build/static/gml-BnhKb5Da.js +1 -0
  246. package/web-ui/build/static/gn-Dux09iX8.js +1 -0
  247. package/web-ui/build/static/go-BOG-9Cqk.js +1 -0
  248. package/web-ui/build/static/go-BWZB_3b5.js +1 -0
  249. package/web-ui/build/static/go-module-BVLW7KBE.js +1 -0
  250. package/web-ui/build/static/golo-BD_SOfwL.js +1 -0
  251. package/web-ui/build/static/gradle-zSadWOD2.js +1 -0
  252. package/web-ui/build/static/graphql-BQlyj78B.js +1 -0
  253. package/web-ui/build/static/groovy-BurRMqQS.js +1 -0
  254. package/web-ui/build/static/groovy-SP58zwlE.js +1 -0
  255. package/web-ui/build/static/haml-eZ5ah5KY.js +1 -0
  256. package/web-ui/build/static/haml-lFC47IZb.js +1 -0
  257. package/web-ui/build/static/handlebars-B4UXrB-Q.js +1 -0
  258. package/web-ui/build/static/handlebars-CQ-Q5HnC.js +1 -0
  259. package/web-ui/build/static/haskell-AQrRyTSy.js +1 -0
  260. package/web-ui/build/static/haskell-BZTSbaB_.js +1 -0
  261. package/web-ui/build/static/haxe-5l1X6ESp.js +1 -0
  262. package/web-ui/build/static/haxe-DBn90muG.js +1 -0
  263. package/web-ui/build/static/hcl-CnMewPLM.js +1 -0
  264. package/web-ui/build/static/hlsl-RAtuBzr5.js +1 -0
  265. package/web-ui/build/static/hoon-CSqRU_4M.js +1 -0
  266. package/web-ui/build/static/hpkp-_gNbXcHt.js +1 -0
  267. package/web-ui/build/static/hsp-DY1V4au3.js +1 -0
  268. package/web-ui/build/static/hsts-j5z2jJo8.js +1 -0
  269. package/web-ui/build/static/htmlbars-C5EHvatr.js +1 -0
  270. package/web-ui/build/static/http-DgWgQrZh.js +1 -0
  271. package/web-ui/build/static/http-DphJL0q2.js +1 -0
  272. package/web-ui/build/static/hy-DnBqjPsB.js +1 -0
  273. package/web-ui/build/static/ichigojam-DeiCOKyF.js +1 -0
  274. package/web-ui/build/static/icon-CWANFWY5.js +1 -0
  275. package/web-ui/build/static/icu-message-format-C7w3vpgC.js +1 -0
  276. package/web-ui/build/static/idris-BeD8eULz.js +1 -0
  277. package/web-ui/build/static/iecst-BIznHXqY.js +1 -0
  278. package/web-ui/build/static/ignore-BcFgcNaS.js +1 -0
  279. package/web-ui/build/static/index-D8uVofpo.js +13 -0
  280. package/web-ui/build/static/index-DPFadqM6.css +1 -0
  281. package/web-ui/build/static/index-SkOgWEAU.js +1 -0
  282. package/web-ui/build/static/index-Vd3WlhtC.js +747 -0
  283. package/web-ui/build/static/inform7-CkQD_jz-.js +1 -0
  284. package/web-ui/build/static/inform7-phQiuDty.js +1 -0
  285. package/web-ui/build/static/ini-Bw_QAbzV.js +1 -0
  286. package/web-ui/build/static/ini-CB8ZxX7y.js +1 -0
  287. package/web-ui/build/static/io-D6IgpCmL.js +1 -0
  288. package/web-ui/build/static/irpf90-Ctj0koST.js +1 -0
  289. package/web-ui/build/static/isbl-D2mGcH87.js +1 -0
  290. package/web-ui/build/static/j-KvHmDBWH.js +1 -0
  291. package/web-ui/build/static/java-BeBIdo5i.js +1 -0
  292. package/web-ui/build/static/java-llFZkHLe.js +1 -0
  293. package/web-ui/build/static/javadoc-DXvQGu0s.js +1 -0
  294. package/web-ui/build/static/javadoclike-B5qdA9KZ.js +1 -0
  295. package/web-ui/build/static/javascript-De6HzHvc.js +1 -0
  296. package/web-ui/build/static/javastacktrace-C5MolKiP.js +1 -0
  297. package/web-ui/build/static/jboss-cli-2TXd54zo.js +1 -0
  298. package/web-ui/build/static/jexl-W4AVA9fi.js +1 -0
  299. package/web-ui/build/static/jolie-DTJKRMZN.js +1 -0
  300. package/web-ui/build/static/jq-BYg-QQKh.js +1 -0
  301. package/web-ui/build/static/js-extras-BrYWd2VE.js +1 -0
  302. package/web-ui/build/static/js-templates-DorYpbHq.js +1 -0
  303. package/web-ui/build/static/jsdoc-CRF8n9pZ.js +1 -0
  304. package/web-ui/build/static/json-Dlcd7rla.js +1 -0
  305. package/web-ui/build/static/json-rhOJZzzZ.js +1 -0
  306. package/web-ui/build/static/json5-hTq1nNIW.js +1 -0
  307. package/web-ui/build/static/jsonp-CMg9Xvol.js +1 -0
  308. package/web-ui/build/static/jsstacktrace-hEeYxHtB.js +1 -0
  309. package/web-ui/build/static/jsx-B7PtA8WD.js +1 -0
  310. package/web-ui/build/static/julia-CNiEEY-n.js +1 -0
  311. package/web-ui/build/static/julia-eE0_SJlc.js +1 -0
  312. package/web-ui/build/static/julia-repl-CUJTTT4C.js +1 -0
  313. package/web-ui/build/static/keepalived-GUWJBqyD.js +1 -0
  314. package/web-ui/build/static/keyman-Bdf9MJyu.js +1 -0
  315. package/web-ui/build/static/kotlin-Ch6Bej5W.js +1 -0
  316. package/web-ui/build/static/kotlin-DFJ7D7Lw.js +1 -0
  317. package/web-ui/build/static/kumir-DJLIjcCs.js +1 -0
  318. package/web-ui/build/static/kusto-BM0YTwU4.js +1 -0
  319. package/web-ui/build/static/lasso-Z1DVS84K.js +1 -0
  320. package/web-ui/build/static/latex-BWbw71RK.js +1 -0
  321. package/web-ui/build/static/latex-CMzqmbhw.js +1 -0
  322. package/web-ui/build/static/latte-C1g8_grc.js +1 -0
  323. package/web-ui/build/static/ldif-C6QH3OIL.js +1 -0
  324. package/web-ui/build/static/leaf-CbR--ZsH.js +1 -0
  325. package/web-ui/build/static/less-ClVrKh2Z.js +1 -0
  326. package/web-ui/build/static/less-DNSxm8UA.js +1 -0
  327. package/web-ui/build/static/lilypond-lTzf_sWt.js +1 -0
  328. package/web-ui/build/static/liquid-Bn91mVfC.js +1 -0
  329. package/web-ui/build/static/lisp-CU3bHohQ.js +1 -0
  330. package/web-ui/build/static/lisp-DbgzE9W8.js +1 -0
  331. package/web-ui/build/static/livecodeserver-Cse1Uz3H.js +1 -0
  332. package/web-ui/build/static/livescript-BaxbgzWP.js +1 -0
  333. package/web-ui/build/static/livescript-nJt61DBy.js +1 -0
  334. package/web-ui/build/static/llvm-DBboo6UI.js +1 -0
  335. package/web-ui/build/static/llvm-vIy7XYVy.js +1 -0
  336. package/web-ui/build/static/log-CT7nfoDW.js +1 -0
  337. package/web-ui/build/static/lolcode-CUKVytZh.js +1 -0
  338. package/web-ui/build/static/lsl-CsAOlGF2.js +1 -0
  339. package/web-ui/build/static/lua-BuU2FFxP.js +1 -0
  340. package/web-ui/build/static/lua-CiDuKQaa.js +1 -0
  341. package/web-ui/build/static/magma-7vR0zcmS.js +1 -0
  342. package/web-ui/build/static/makefile-Buiz-Msh.js +1 -0
  343. package/web-ui/build/static/makefile-DXW_-6OY.js +1 -0
  344. package/web-ui/build/static/markdown-Bk5DUoGY.js +1 -0
  345. package/web-ui/build/static/markdown-CRS5W_Ai.js +1 -0
  346. package/web-ui/build/static/markup-templating-24odpmF3.js +1 -0
  347. package/web-ui/build/static/mathematica-BxcwhJUp.js +1 -0
  348. package/web-ui/build/static/matlab-3pJYx_Fb.js +1 -0
  349. package/web-ui/build/static/matlab-BqlRrzMf.js +1 -0
  350. package/web-ui/build/static/maxima-DlCfUpcj.js +1 -0
  351. package/web-ui/build/static/maxscript-Cu_gCaFU.js +1 -0
  352. package/web-ui/build/static/mel-D7iQ-5Up.js +1 -0
  353. package/web-ui/build/static/mel-DzBKNpoN.js +1 -0
  354. package/web-ui/build/static/mercury-Dfrb-i8A.js +1 -0
  355. package/web-ui/build/static/mermaid-WN7V2_Eq.js +1 -0
  356. package/web-ui/build/static/mipsasm-CcijzL0q.js +1 -0
  357. package/web-ui/build/static/mizar-Bk68zACP.js +1 -0
  358. package/web-ui/build/static/mizar-Twc2-iZ4.js +1 -0
  359. package/web-ui/build/static/mojolicious-DBbo2S7X.js +1 -0
  360. package/web-ui/build/static/mongodb-2RsFIjgg.js +1 -0
  361. package/web-ui/build/static/monkey-CPXtQ0Bf.js +1 -0
  362. package/web-ui/build/static/monkey-DjV7Wcek.js +1 -0
  363. package/web-ui/build/static/moonscript-B5M5as70.js +1 -0
  364. package/web-ui/build/static/moonscript-D1BHW4Il.js +1 -0
  365. package/web-ui/build/static/n1ql-D0heNDBD.js +1 -0
  366. package/web-ui/build/static/n1ql-DfHqXQD7.js +1 -0
  367. package/web-ui/build/static/n4js-CaPf44Dz.js +1 -0
  368. package/web-ui/build/static/nand2tetris-hdl-D1nf9mn4.js +1 -0
  369. package/web-ui/build/static/naniscript-DnCnu5ZX.js +1 -0
  370. package/web-ui/build/static/nasm-BZrSaMsK.js +1 -0
  371. package/web-ui/build/static/neon-D29Grm2v.js +1 -0
  372. package/web-ui/build/static/nevod-DgSNbQkE.js +1 -0
  373. package/web-ui/build/static/nginx-BAaDGDfT.js +1 -0
  374. package/web-ui/build/static/nginx-BvJ1lrHX.js +1 -0
  375. package/web-ui/build/static/nim--9zzVe5F.js +1 -0
  376. package/web-ui/build/static/nim-Br1relpU.js +1 -0
  377. package/web-ui/build/static/nix--0ftErCy.js +1 -0
  378. package/web-ui/build/static/nix-104ztQqr.js +1 -0
  379. package/web-ui/build/static/node-repl-BUMAf7_p.js +1 -0
  380. package/web-ui/build/static/nsis-BaeKybNA.js +1 -0
  381. package/web-ui/build/static/nsis-CdZEv2iA.js +1 -0
  382. package/web-ui/build/static/objectivec-DBB4ymdg.js +1 -0
  383. package/web-ui/build/static/objectivec-kFYXC6g4.js +1 -0
  384. package/web-ui/build/static/ocaml-D1GXvN7c.js +1 -0
  385. package/web-ui/build/static/ocaml-D80jRMPE.js +1 -0
  386. package/web-ui/build/static/opencl-fb7BfRdO.js +1 -0
  387. package/web-ui/build/static/openqasm-CWUBrR2w.js +1 -0
  388. package/web-ui/build/static/openscad-Dim7ILSL.js +1 -0
  389. package/web-ui/build/static/oxygene-BSwApkwz.js +1 -0
  390. package/web-ui/build/static/oz-CMtRoi5F.js +1 -0
  391. package/web-ui/build/static/parigp-AH8cZ38D.js +1 -0
  392. package/web-ui/build/static/parser-bBNjuhG3.js +1 -0
  393. package/web-ui/build/static/parser3-DUtoWEAd.js +1 -0
  394. package/web-ui/build/static/pascal-Cr3DPIYT.js +1 -0
  395. package/web-ui/build/static/pascaligo-pWW12jfs.js +1 -0
  396. package/web-ui/build/static/pcaxis-DBw9rxmr.js +1 -0
  397. package/web-ui/build/static/peoplecode-aCpMPm_s.js +1 -0
  398. package/web-ui/build/static/perl-BpZ7GmJ3.js +1 -0
  399. package/web-ui/build/static/perl-fnHTrqJL.js +1 -0
  400. package/web-ui/build/static/pf-Dz352ty7.js +1 -0
  401. package/web-ui/build/static/pgsql-CHPUdlI_.js +1 -0
  402. package/web-ui/build/static/php-BRwItjmS.js +1 -0
  403. package/web-ui/build/static/php-CrX_kswO.js +1 -0
  404. package/web-ui/build/static/php-extras-BmeRXDSO.js +1 -0
  405. package/web-ui/build/static/php-template-B0MFJ9RR.js +1 -0
  406. package/web-ui/build/static/phpdoc-wAPkJj9X.js +1 -0
  407. package/web-ui/build/static/plaintext-CmPk1gvP.js +1 -0
  408. package/web-ui/build/static/plsql-pWVw0sCJ.js +1 -0
  409. package/web-ui/build/static/pony-B4SXhyDS.js +1 -0
  410. package/web-ui/build/static/powerquery-ZJ28bdRR.js +1 -0
  411. package/web-ui/build/static/powershell-CWg1ca6z.js +1 -0
  412. package/web-ui/build/static/powershell-Dnl0aBXc.js +1 -0
  413. package/web-ui/build/static/processing-CbYVU7hZ.js +1 -0
  414. package/web-ui/build/static/processing-DnroirEw.js +1 -0
  415. package/web-ui/build/static/profile-DLNc-MTA.js +1 -0
  416. package/web-ui/build/static/prolog-4KlPFQus.js +1 -0
  417. package/web-ui/build/static/prolog-CtUicY87.js +1 -0
  418. package/web-ui/build/static/promql-C_i6OJVg.js +1 -0
  419. package/web-ui/build/static/properties-Cj0lBOSP.js +1 -0
  420. package/web-ui/build/static/properties-vGFibcz9.js +1 -0
  421. package/web-ui/build/static/protobuf-BOIGxbSP.js +1 -0
  422. package/web-ui/build/static/protobuf-CQ3su-J7.js +1 -0
  423. package/web-ui/build/static/psl-DeG5_YUF.js +1 -0
  424. package/web-ui/build/static/pug-BieVVXYz.js +1 -0
  425. package/web-ui/build/static/puppet-Ba40SVKU.js +1 -0
  426. package/web-ui/build/static/puppet-D7BzrcGt.js +1 -0
  427. package/web-ui/build/static/pure-DZnkz1iT.js +1 -0
  428. package/web-ui/build/static/purebasic-CLLZW_6G.js +1 -0
  429. package/web-ui/build/static/purebasic-CYPZo_H6.js +1 -0
  430. package/web-ui/build/static/purescript-Dyjfu5Id.js +1 -0
  431. package/web-ui/build/static/python-BdIWKxdN.js +1 -0
  432. package/web-ui/build/static/python-ofKsqxv7.js +1 -0
  433. package/web-ui/build/static/python-repl-DiTYb1xK.js +1 -0
  434. package/web-ui/build/static/q-B4P0If_I.js +1 -0
  435. package/web-ui/build/static/q-t_17xfY8.js +1 -0
  436. package/web-ui/build/static/qml-B5WhiN48.js +1 -0
  437. package/web-ui/build/static/qml-Dq0cESXJ.js +1 -0
  438. package/web-ui/build/static/qore-DCx30XRf.js +1 -0
  439. package/web-ui/build/static/qsharp-UrBScekp.js +1 -0
  440. package/web-ui/build/static/r-B0Ty1RKQ.js +1 -0
  441. package/web-ui/build/static/r-B0za8QKS.js +1 -0
  442. package/web-ui/build/static/racket-Dj6WEyhS.js +1 -0
  443. package/web-ui/build/static/reason-dj9hJSfr.js +1 -0
  444. package/web-ui/build/static/reasonml-B-q5_wag.js +1 -0
  445. package/web-ui/build/static/regex-4HEc5C1m.js +1 -0
  446. package/web-ui/build/static/rego-BdQe18RK.js +1 -0
  447. package/web-ui/build/static/renpy-CVMA2llL.js +1 -0
  448. package/web-ui/build/static/rest-9B4JWVGr.js +1 -0
  449. package/web-ui/build/static/rib-DR-U8OaT.js +1 -0
  450. package/web-ui/build/static/rip-Bu2t_rFZ.js +1 -0
  451. package/web-ui/build/static/roboconf-CJeXD5-I.js +1 -0
  452. package/web-ui/build/static/roboconf-DzDTVrdM.js +1 -0
  453. package/web-ui/build/static/robotframework-CR7KyPpN.js +1 -0
  454. package/web-ui/build/static/routeros-B2741z2k.js +1 -0
  455. package/web-ui/build/static/rsl-B9F_ZCgv.js +1 -0
  456. package/web-ui/build/static/ruby-I2JTNgyY.js +1 -0
  457. package/web-ui/build/static/ruby-QGDPOmJX.js +1 -0
  458. package/web-ui/build/static/ruleslanguage-CGzXEUCO.js +1 -0
  459. package/web-ui/build/static/rust-BxW5-WOm.js +1 -0
  460. package/web-ui/build/static/rust-CSOA43di.js +1 -0
  461. package/web-ui/build/static/sas-Bclfx4g3.js +1 -0
  462. package/web-ui/build/static/sas-xbQaiYjT.js +1 -0
  463. package/web-ui/build/static/sass-DJPbdNwd.js +1 -0
  464. package/web-ui/build/static/scala-Bo18NtHQ.js +1 -0
  465. package/web-ui/build/static/scala-Cy0JH-SG.js +1 -0
  466. package/web-ui/build/static/scheme-BjcWWjIx.js +1 -0
  467. package/web-ui/build/static/scheme-DQdj8PzN.js +1 -0
  468. package/web-ui/build/static/scilab-Bn1KHdK-.js +1 -0
  469. package/web-ui/build/static/scss-B1twkZBz.js +1 -0
  470. package/web-ui/build/static/scss-DmOuMI4v.js +1 -0
  471. package/web-ui/build/static/shell-BUlkJG0S.js +1 -0
  472. package/web-ui/build/static/shell-session-Bke-svxA.js +1 -0
  473. package/web-ui/build/static/smali-Ch9S16HV.js +1 -0
  474. package/web-ui/build/static/smali-D_yDr_Aj.js +1 -0
  475. package/web-ui/build/static/smalltalk-B9TfQ5Md.js +1 -0
  476. package/web-ui/build/static/smalltalk-EwbZxZsR.js +1 -0
  477. package/web-ui/build/static/smarty-9kDPpeSm.js +1 -0
  478. package/web-ui/build/static/sml-2fEfT7rd.js +1 -0
  479. package/web-ui/build/static/sml-BiwoLNk7.js +1 -0
  480. package/web-ui/build/static/solidity-n_x8Oe0h.js +1 -0
  481. package/web-ui/build/static/solution-file-B2mvjI3e.js +1 -0
  482. package/web-ui/build/static/soy-DPkgKBIS.js +1 -0
  483. package/web-ui/build/static/sparql-Cy95tds0.js +1 -0
  484. package/web-ui/build/static/splunk-spl-Ym3z9ouN.js +1 -0
  485. package/web-ui/build/static/sqf-CXZTG8WE.js +1 -0
  486. package/web-ui/build/static/sqf-Cwi3yg7f.js +1 -0
  487. package/web-ui/build/static/sql-DPxSQY4S.js +1 -0
  488. package/web-ui/build/static/sql-peh7ijGj.js +1 -0
  489. package/web-ui/build/static/sql_more-0YAbAuPw.js +1 -0
  490. package/web-ui/build/static/squirrel-CphzjV0e.js +1 -0
  491. package/web-ui/build/static/stan-0-xZ95-O.js +1 -0
  492. package/web-ui/build/static/stan-CaI4__2g.js +1 -0
  493. package/web-ui/build/static/stata-BrbzrGSs.js +1 -0
  494. package/web-ui/build/static/step21-C_qeyVLw.js +1 -0
  495. package/web-ui/build/static/stylus-Btycb2sZ.js +1 -0
  496. package/web-ui/build/static/stylus-FoBJ7jki.js +1 -0
  497. package/web-ui/build/static/subunit-Dpg-m04-.js +1 -0
  498. package/web-ui/build/static/swift-Cr9uZmgb.js +1 -0
  499. package/web-ui/build/static/swift-hGLFtD7e.js +1 -0
  500. package/web-ui/build/static/systemd-Bls2D9Vj.js +1 -0
  501. package/web-ui/build/static/t4-cs-C4qDO-jJ.js +1 -0
  502. package/web-ui/build/static/t4-templating-BbCFPMPO.js +1 -0
  503. package/web-ui/build/static/t4-vb-D1zoEccT.js +1 -0
  504. package/web-ui/build/static/taggerscript-CWHk9Gih.js +1 -0
  505. package/web-ui/build/static/tap-Bjt0UnzV.js +1 -0
  506. package/web-ui/build/static/tap-BnHKwLQs.js +1 -0
  507. package/web-ui/build/static/tcl-Zo9kx4y-.js +1 -0
  508. package/web-ui/build/static/tcl-fzLmefkt.js +1 -0
  509. package/web-ui/build/static/textile-9lIlUPH5.js +1 -0
  510. package/web-ui/build/static/thrift-M3K6r5Cy.js +1 -0
  511. package/web-ui/build/static/toml-HpaKqckc.js +1 -0
  512. package/web-ui/build/static/tp-DFKuxrKj.js +1 -0
  513. package/web-ui/build/static/tremor-D4_bUtMB.js +1 -0
  514. package/web-ui/build/static/tsx-o1RT-T90.js +1 -0
  515. package/web-ui/build/static/tt2-1xDqcN_2.js +1 -0
  516. package/web-ui/build/static/turtle-Dlt-aGky.js +1 -0
  517. package/web-ui/build/static/twig-CJ_BnGSR.js +1 -0
  518. package/web-ui/build/static/twig-CjsiSQb6.js +1 -0
  519. package/web-ui/build/static/typescript-B8B9zUn-.js +1 -0
  520. package/web-ui/build/static/typescript-D0Jgo8O7.js +1 -0
  521. package/web-ui/build/static/typoscript-C8Qke4ZB.js +1 -0
  522. package/web-ui/build/static/unrealscript-YxJdDNZ3.js +1 -0
  523. package/web-ui/build/static/uorazor-CtEVnqBv.js +1 -0
  524. package/web-ui/build/static/uri-YdaiQl4c.js +1 -0
  525. package/web-ui/build/static/v-CIyttMDD.js +1 -0
  526. package/web-ui/build/static/vala-DGslcym_.js +1 -0
  527. package/web-ui/build/static/vala-GFPx3uEJ.js +1 -0
  528. package/web-ui/build/static/vbnet-B20itab-.js +1 -0
  529. package/web-ui/build/static/vbnet-BdoN6egk.js +1 -0
  530. package/web-ui/build/static/vbscript-PHVh6Fp_.js +1 -0
  531. package/web-ui/build/static/vbscript-html-woH1VZ7U.js +1 -0
  532. package/web-ui/build/static/velocity-DtVfCZeg.js +1 -0
  533. package/web-ui/build/static/verilog-Bt6edXvM.js +1 -0
  534. package/web-ui/build/static/verilog-k_7lr9Zq.js +1 -0
  535. package/web-ui/build/static/vhdl-BMzOgOeK.js +1 -0
  536. package/web-ui/build/static/vhdl-BcAbtPG6.js +1 -0
  537. package/web-ui/build/static/vim-DrinG9a4.js +1 -0
  538. package/web-ui/build/static/vim-WihLATJL.js +1 -0
  539. package/web-ui/build/static/visual-basic-CJnvgPjM.js +1 -0
  540. package/web-ui/build/static/warpscript-zMlbUoZs.js +1 -0
  541. package/web-ui/build/static/wasm-GUnfTBUL.js +1 -0
  542. package/web-ui/build/static/web-idl-CfaLTG_r.js +1 -0
  543. package/web-ui/build/static/wiki-13AlLoOc.js +1 -0
  544. package/web-ui/build/static/wolfram-zHocYNXW.js +1 -0
  545. package/web-ui/build/static/wren-Byq862Iu.js +1 -0
  546. package/web-ui/build/static/x86asm-CLcOnePY.js +1 -0
  547. package/web-ui/build/static/xeora-BVHqWOFS.js +1 -0
  548. package/web-ui/build/static/xl-lXi8OYfr.js +1 -0
  549. package/web-ui/build/static/xml-KZjGBKxi.js +1 -0
  550. package/web-ui/build/static/xml-doc-DrQSDcEW.js +1 -0
  551. package/web-ui/build/static/xojo-DosHeFXU.js +1 -0
  552. package/web-ui/build/static/xquery-BZN1F14Q.js +1 -0
  553. package/web-ui/build/static/xquery-Cnz7ZLFr.js +1 -0
  554. package/web-ui/build/static/yaml-BzXOcy9u.js +1 -0
  555. package/web-ui/build/static/yaml-C207y5bt.js +1 -0
  556. package/web-ui/build/static/yang-ByrBdDIg.js +1 -0
  557. package/web-ui/build/static/zephir-bahTa7of.js +1 -0
  558. package/web-ui/build/static/zig-BlFYhdtC.js +1 -0
  559. package/src/tools/browserTool.js +0 -897
  560. package/src/utilities/platformUtils.test.js +0 -98
  561. package/web-ui/build/static/index-SmQFfvBs.js +0 -746
  562. package/web-ui/build/static/index-V2ySwjHp.css +0 -1
@@ -92,12 +92,10 @@ describe('Terminal UI Infrastructure - Imports', () => {
92
92
  });
93
93
 
94
94
  test('useConnection hook can be imported', async () => {
95
- const { useConnection, useConnectionStatus } = await import('../../state/useConnection.js');
95
+ const mod = await import('../../state/useConnection.js');
96
96
 
97
- expect(useConnection).toBeDefined();
98
- expect(typeof useConnection).toBe('function');
99
- expect(useConnectionStatus).toBeDefined();
100
- expect(typeof useConnectionStatus).toBe('function');
97
+ expect(mod.useConnection).toBeDefined();
98
+ expect(typeof mod.useConnection).toBe('function');
101
99
  });
102
100
  });
103
101
 
@@ -1946,15 +1946,6 @@ class WebServer {
1946
1946
  try {
1947
1947
  const { sessionId, filename } = req.params;
1948
1948
 
1949
- // Security validation: Check if sessionId exists
1950
- const session = this.sessions.get(sessionId);
1951
- if (!session) {
1952
- return res.status(HTTP_STATUS.FORBIDDEN).json({
1953
- success: false,
1954
- error: 'Invalid session'
1955
- });
1956
- }
1957
-
1958
1949
  // Security validation: Check filename for path traversal attempts
1959
1950
  const normalizedFilename = path.basename(filename);
1960
1951
  if (normalizedFilename !== filename || filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
@@ -1964,19 +1955,32 @@ class WebServer {
1964
1955
  });
1965
1956
  }
1966
1957
 
1958
+ // Session may or may not still exist (server restarts lose session map)
1959
+ const session = this.sessions.get(sessionId);
1960
+ const projectDir = session?.projectDir || process.cwd();
1961
+
1967
1962
  // Try to locate the image in multiple possible locations
1968
1963
  let imagePath = null;
1969
1964
  const searchPaths = [
1970
1965
  // 1. Session's project directory images folder
1971
- path.join(session.projectDir || process.cwd(), 'images', normalizedFilename),
1966
+ path.join(projectDir, 'images', normalizedFilename),
1972
1967
  // 2. Temp directory for this session
1973
1968
  path.join('/tmp/loxia-images', sessionId, normalizedFilename),
1974
1969
  // 3. General temp images directory
1975
- path.join('/tmp/loxia-images', normalizedFilename)
1970
+ path.join('/tmp/loxia-images', normalizedFilename),
1971
+ // 4. OS temp dir (Windows: %TEMP%, Linux: /tmp)
1972
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', sessionId, normalizedFilename),
1973
+ path.join(process.env.TEMP || process.env.TMP || '/tmp', 'loxia-images', normalizedFilename)
1976
1974
  ];
1977
1975
 
1978
- // 4. Search in agent working directories for this session
1979
- // Get all agents and check their workingDirectory
1976
+ // 5. Search ALL known project dirs from active sessions (not just current)
1977
+ for (const [, sess] of this.sessions) {
1978
+ if (sess?.projectDir && sess.projectDir !== projectDir) {
1979
+ searchPaths.push(path.join(sess.projectDir, 'images', normalizedFilename));
1980
+ }
1981
+ }
1982
+
1983
+ // 6. Search in agent working directories AND agent projectDirs
1980
1984
  if (this.orchestrator?.agentPool) {
1981
1985
  try {
1982
1986
  const agents = await this.orchestrator.agentPool.getAllAgents();
@@ -1986,6 +1990,10 @@ class WebServer {
1986
1990
  path.join(agent.directoryAccess.workingDirectory, 'images', normalizedFilename)
1987
1991
  );
1988
1992
  }
1993
+ // Also check the agent's own projectDir (may differ from session projectDir)
1994
+ if (agent.projectDir) {
1995
+ searchPaths.push(path.join(agent.projectDir, 'images', normalizedFilename));
1996
+ }
1989
1997
  }
1990
1998
  } catch (error) {
1991
1999
  this.logger.warn('Failed to get agent working directories for image search', {
@@ -1994,6 +2002,12 @@ class WebServer {
1994
2002
  }
1995
2003
  }
1996
2004
 
2005
+ // 7. Common user project directory (fallback for when session is gone)
2006
+ const homeDir = process.env.USERPROFILE || process.env.HOME || '';
2007
+ if (homeDir) {
2008
+ searchPaths.push(path.join(homeDir, 'Loxia', 'images', normalizedFilename));
2009
+ }
2010
+
1997
2011
  // Find the first existing file
1998
2012
  for (const searchPath of searchPaths) {
1999
2013
  try {
@@ -2576,6 +2590,76 @@ class WebServer {
2576
2590
  }
2577
2591
  });
2578
2592
 
2593
+ // ============================================
2594
+ // Artifacts API Endpoint
2595
+ // ============================================
2596
+
2597
+ // DEBUG: Inject a test artifact into an agent (remove after testing)
2598
+ this.app.post('/api/agents/:agentId/artifacts/test', async (req, res) => {
2599
+ try {
2600
+ const { agentId } = req.params;
2601
+ const agent = await this.orchestrator.agentPool.getAgent(agentId);
2602
+ if (!agent) return res.status(404).json({ success: false, error: 'Agent not found' });
2603
+
2604
+ if (!agent.artifacts) agent.artifacts = {};
2605
+ const testPath = (agent.directoryAccess?.workingDirectory || '/test') + '/test-artifact.js';
2606
+ const displayPath = 'test-artifact.js';
2607
+
2608
+ agent.artifacts[testPath] = {
2609
+ displayPath,
2610
+ versions: [{
2611
+ id: `v-${Date.now()}`,
2612
+ content: '// Test artifact\nconsole.log("Hello from artifacts!");',
2613
+ timestamp: new Date().toISOString(),
2614
+ action: 'write',
2615
+ size: 50,
2616
+ fullPath: testPath
2617
+ }]
2618
+ };
2619
+
2620
+ // Broadcast via WebSocket
2621
+ const sessionIds = this.orchestrator.webSocketManager?.getSessionsForAgent?.(agentId) || [];
2622
+ for (const sid of sessionIds) {
2623
+ this.orchestrator.webSocketManager.broadcastToSession(sid, {
2624
+ type: 'artifacts_updated',
2625
+ data: { agentId, artifacts: agent.artifacts, workingDirectory: agent.directoryAccess?.workingDirectory || '' }
2626
+ });
2627
+ }
2628
+
2629
+ res.json({ success: true, artifactCount: Object.keys(agent.artifacts).length });
2630
+ } catch (error) {
2631
+ res.status(500).json({ success: false, error: error.message });
2632
+ }
2633
+ });
2634
+
2635
+ // Get artifacts (files written by the agent) with version history
2636
+ this.app.get('/api/agents/:agentId/artifacts', async (req, res) => {
2637
+ try {
2638
+ const { agentId } = req.params;
2639
+ const agent = await this.orchestrator.agentPool.getAgent(agentId);
2640
+
2641
+ if (!agent) {
2642
+ return res.status(404).json({ success: false, error: 'Agent not found' });
2643
+ }
2644
+
2645
+ // Return the artifacts map (or empty if none yet)
2646
+ // { [filePath]: { displayPath, versions: [{ id, content, timestamp, action, size }] } }
2647
+ res.json({
2648
+ success: true,
2649
+ agentId,
2650
+ artifacts: agent.artifacts || {},
2651
+ workingDirectory: agent.directoryAccess?.workingDirectory || ''
2652
+ });
2653
+
2654
+ } catch (error) {
2655
+ this.logger.error('Failed to get agent artifacts', {
2656
+ agentId: req.params.agentId,
2657
+ error: error.message
2658
+ });
2659
+ res.status(500).json({ success: false, error: error.message });
2660
+ }
2661
+ });
2662
+
2579
2663
  // ============================================
2580
2664
  // Terminal Tasks API Endpoints
2581
2665
  // ============================================
@@ -0,0 +1,319 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger } from '../../__test-utils__/mockFactories.js';
3
+ import {
4
+ shouldAgentBeActive,
5
+ getActiveAgents,
6
+ getAllAgentActivityStatus,
7
+ shouldSkipIteration,
8
+ hasPendingTasks,
9
+ getMessageQueueStatus,
10
+ isAgentDelayed,
11
+ isAgentPaused,
12
+ isExecutingTools
13
+ } from '../agentActivityService.js';
14
+
15
+ // Helper to create a base active agent
16
+ function makeAgent(overrides = {}) {
17
+ return {
18
+ id: 'agent-1',
19
+ name: 'Test Agent',
20
+ sessionId: 'session-1',
21
+ status: 'active',
22
+ mode: 'agent',
23
+ taskList: { tasks: [] },
24
+ messageQueues: {},
25
+ delayEndTime: null,
26
+ pausedUntil: null,
27
+ awaitingUserInput: null,
28
+ stopRequested: false,
29
+ toolExecutionInProgress: false,
30
+ ttl: null,
31
+ ...overrides
32
+ };
33
+ }
34
+
35
+ describe('agentActivityService', () => {
36
+ describe('hasPendingTasks', () => {
37
+ test('returns false when taskList is null or missing', () => {
38
+ expect(hasPendingTasks({})).toBe(false);
39
+ expect(hasPendingTasks({ taskList: null })).toBe(false);
40
+ expect(hasPendingTasks({ taskList: {} })).toBe(false);
41
+ expect(hasPendingTasks({ taskList: { tasks: 'not-array' } })).toBe(false);
42
+ });
43
+
44
+ test('returns true when there are pending tasks', () => {
45
+ const agent = makeAgent({
46
+ taskList: { tasks: [{ status: 'pending' }] }
47
+ });
48
+ expect(hasPendingTasks(agent)).toBe(true);
49
+ });
50
+
51
+ test('returns true when there are in_progress tasks', () => {
52
+ const agent = makeAgent({
53
+ taskList: { tasks: [{ status: 'completed' }, { status: 'in_progress' }] }
54
+ });
55
+ expect(hasPendingTasks(agent)).toBe(true);
56
+ });
57
+
58
+ test('returns false when all tasks are completed', () => {
59
+ const agent = makeAgent({
60
+ taskList: { tasks: [{ status: 'completed' }, { status: 'failed' }] }
61
+ });
62
+ expect(hasPendingTasks(agent)).toBe(false);
63
+ });
64
+ });
65
+
66
+ describe('getMessageQueueStatus', () => {
67
+ test('returns zero counts for empty queues', () => {
68
+ const result = getMessageQueueStatus({});
69
+ expect(result.hasMessages).toBe(false);
70
+ expect(result.counts.total).toBe(0);
71
+ });
72
+
73
+ test('counts messages across all queues', () => {
74
+ const agent = makeAgent({
75
+ messageQueues: {
76
+ toolResults: [{ id: 1 }],
77
+ interAgentMessages: [{ id: 2 }, { id: 3 }],
78
+ userMessages: [{ id: 4 }]
79
+ }
80
+ });
81
+ const result = getMessageQueueStatus(agent);
82
+ expect(result.hasMessages).toBe(true);
83
+ expect(result.hasUserMessages).toBe(true);
84
+ expect(result.hasInterAgentMessages).toBe(true);
85
+ expect(result.hasToolResults).toBe(true);
86
+ expect(result.counts.total).toBe(4);
87
+ });
88
+
89
+ test('handles non-array queue values', () => {
90
+ const agent = makeAgent({
91
+ messageQueues: { toolResults: 'not-array', userMessages: null }
92
+ });
93
+ const result = getMessageQueueStatus(agent);
94
+ expect(result.counts.toolResults).toBe(0);
95
+ expect(result.counts.userMessages).toBe(0);
96
+ });
97
+ });
98
+
99
+ describe('isAgentDelayed', () => {
100
+ test('returns false when no delayEndTime', () => {
101
+ expect(isAgentDelayed(makeAgent())).toBe(false);
102
+ });
103
+
104
+ test('returns true when delay is in the future', () => {
105
+ const future = new Date(Date.now() + 60000).toISOString();
106
+ expect(isAgentDelayed(makeAgent({ delayEndTime: future }))).toBe(true);
107
+ });
108
+
109
+ test('returns false when delay is in the past', () => {
110
+ const past = new Date(Date.now() - 60000).toISOString();
111
+ expect(isAgentDelayed(makeAgent({ delayEndTime: past }))).toBe(false);
112
+ });
113
+ });
114
+
115
+ describe('isAgentPaused', () => {
116
+ test('returns false for active, non-paused agent', () => {
117
+ expect(isAgentPaused(makeAgent())).toBe(false);
118
+ });
119
+
120
+ test('returns true for paused status without expiry', () => {
121
+ expect(isAgentPaused(makeAgent({ status: 'paused' }))).toBe(true);
122
+ });
123
+
124
+ test('returns true for paused status with future expiry', () => {
125
+ const future = new Date(Date.now() + 60000).toISOString();
126
+ expect(isAgentPaused(makeAgent({ status: 'paused', pausedUntil: future }))).toBe(true);
127
+ });
128
+
129
+ test('returns false for paused status with past expiry', () => {
130
+ const past = new Date(Date.now() - 60000).toISOString();
131
+ expect(isAgentPaused(makeAgent({ status: 'paused', pausedUntil: past }))).toBe(false);
132
+ });
133
+
134
+ test('returns true when pausedUntil is in future even without paused status', () => {
135
+ const future = new Date(Date.now() + 60000).toISOString();
136
+ expect(isAgentPaused(makeAgent({ pausedUntil: future }))).toBe(true);
137
+ });
138
+ });
139
+
140
+ describe('isExecutingTools', () => {
141
+ test('returns true when toolExecutionInProgress is true', () => {
142
+ expect(isExecutingTools({ toolExecutionInProgress: true })).toBe(true);
143
+ });
144
+
145
+ test('returns false otherwise', () => {
146
+ expect(isExecutingTools({ toolExecutionInProgress: false })).toBe(false);
147
+ expect(isExecutingTools({})).toBe(false);
148
+ });
149
+ });
150
+
151
+ describe('shouldAgentBeActive', () => {
152
+ test('returns inactive for null agent', () => {
153
+ const result = shouldAgentBeActive(null);
154
+ expect(result.active).toBe(false);
155
+ expect(result.reason).toBe('agent-not-found');
156
+ });
157
+
158
+ test('returns inactive for non-active status', () => {
159
+ const result = shouldAgentBeActive(makeAgent({ status: 'idle' }));
160
+ expect(result.active).toBe(false);
161
+ expect(result.reason).toBe('agent-inactive-status');
162
+ });
163
+
164
+ test('returns inactive when delayed', () => {
165
+ const future = new Date(Date.now() + 60000).toISOString();
166
+ const result = shouldAgentBeActive(makeAgent({ delayEndTime: future }));
167
+ expect(result.active).toBe(false);
168
+ expect(result.reason).toBe('agent-delayed');
169
+ });
170
+
171
+ test('returns inactive when paused', () => {
172
+ const result = shouldAgentBeActive(makeAgent({ status: 'active', pausedUntil: new Date(Date.now() + 60000).toISOString() }));
173
+ expect(result.active).toBe(false);
174
+ expect(result.reason).toBe('agent-paused');
175
+ });
176
+
177
+ test('returns inactive when awaiting user input', () => {
178
+ const result = shouldAgentBeActive(makeAgent({ awaitingUserInput: { type: 'credentials' } }));
179
+ expect(result.active).toBe(false);
180
+ expect(result.reason).toBe('awaiting-user-input');
181
+ });
182
+
183
+ test('returns inactive when stop requested', () => {
184
+ const result = shouldAgentBeActive(makeAgent({ stopRequested: true }));
185
+ expect(result.active).toBe(false);
186
+ expect(result.reason).toBe('stop-requested');
187
+ });
188
+
189
+ test('returns active when TTL remaining', () => {
190
+ const result = shouldAgentBeActive(makeAgent({ ttl: 3 }));
191
+ expect(result.active).toBe(true);
192
+ expect(result.reason).toBe('has-ttl-remaining');
193
+ });
194
+
195
+ test('AGENT mode: active when has pending tasks', () => {
196
+ const result = shouldAgentBeActive(makeAgent({
197
+ taskList: { tasks: [{ status: 'pending' }] }
198
+ }));
199
+ expect(result.active).toBe(true);
200
+ expect(result.reason).toBe('has-pending-tasks');
201
+ });
202
+
203
+ test('AGENT mode: inactive when no pending tasks', () => {
204
+ const result = shouldAgentBeActive(makeAgent({
205
+ taskList: { tasks: [{ status: 'completed' }] }
206
+ }));
207
+ expect(result.active).toBe(false);
208
+ expect(result.reason).toBe('no-pending-work');
209
+ });
210
+
211
+ test('CHAT mode: active when has user messages', () => {
212
+ const result = shouldAgentBeActive(makeAgent({
213
+ mode: 'chat',
214
+ messageQueues: { userMessages: [{ id: 1 }] }
215
+ }));
216
+ expect(result.active).toBe(true);
217
+ expect(result.reason).toBe('has-user-messages');
218
+ });
219
+
220
+ test('CHAT mode: active when has inter-agent messages', () => {
221
+ const result = shouldAgentBeActive(makeAgent({
222
+ mode: 'chat',
223
+ messageQueues: { interAgentMessages: [{ id: 1 }] }
224
+ }));
225
+ expect(result.active).toBe(true);
226
+ expect(result.reason).toBe('has-inter-agent-messages');
227
+ });
228
+
229
+ test('CHAT mode: inactive with only tool results', () => {
230
+ const result = shouldAgentBeActive(makeAgent({
231
+ mode: 'chat',
232
+ messageQueues: { toolResults: [{ id: 1 }] }
233
+ }));
234
+ expect(result.active).toBe(false);
235
+ expect(result.reason).toBe('chat-mode-no-messages');
236
+ expect(result.details).toContain('tool results');
237
+ });
238
+
239
+ test('CHAT mode: inactive with no messages', () => {
240
+ const result = shouldAgentBeActive(makeAgent({ mode: 'chat' }));
241
+ expect(result.active).toBe(false);
242
+ expect(result.reason).toBe('chat-mode-no-messages');
243
+ });
244
+
245
+ test('returns unknown mode for unrecognized mode', () => {
246
+ const result = shouldAgentBeActive(makeAgent({ mode: 'weird' }));
247
+ expect(result.active).toBe(false);
248
+ expect(result.reason).toBe('unknown-mode');
249
+ });
250
+ });
251
+
252
+ describe('getActiveAgents', () => {
253
+ test('filters active agents from an array', () => {
254
+ const agents = [
255
+ makeAgent({ id: 'a1', taskList: { tasks: [{ status: 'pending' }] } }),
256
+ makeAgent({ id: 'a2', status: 'idle' }),
257
+ makeAgent({ id: 'a3', taskList: { tasks: [{ status: 'pending' }] } })
258
+ ];
259
+ const active = getActiveAgents(agents);
260
+ expect(active).toHaveLength(2);
261
+ expect(active.map(a => a.agentId)).toEqual(['a1', 'a3']);
262
+ });
263
+
264
+ test('works with a Map input', () => {
265
+ const agents = new Map();
266
+ agents.set('a1', makeAgent({ id: 'a1', taskList: { tasks: [{ status: 'pending' }] } }));
267
+ agents.set('a2', makeAgent({ id: 'a2', status: 'idle' }));
268
+ const active = getActiveAgents(agents);
269
+ expect(active).toHaveLength(1);
270
+ });
271
+ });
272
+
273
+ describe('getAllAgentActivityStatus', () => {
274
+ test('returns detailed status for all agents', () => {
275
+ const agents = [
276
+ makeAgent({ id: 'a1', name: 'Agent 1', taskList: { tasks: [{ status: 'pending' }] } })
277
+ ];
278
+ const statuses = getAllAgentActivityStatus(agents);
279
+ expect(statuses).toHaveLength(1);
280
+ expect(statuses[0].agentId).toBe('a1');
281
+ expect(statuses[0].active).toBe(true);
282
+ expect(statuses[0]).toHaveProperty('queueCounts');
283
+ expect(statuses[0]).toHaveProperty('hasPendingTasks');
284
+ expect(statuses[0]).toHaveProperty('isExecutingTools');
285
+ });
286
+
287
+ test('works with Map input', () => {
288
+ const map = new Map();
289
+ map.set('a1', makeAgent({ id: 'a1' }));
290
+ const statuses = getAllAgentActivityStatus(map);
291
+ expect(statuses).toHaveLength(1);
292
+ });
293
+ });
294
+
295
+ describe('shouldSkipIteration', () => {
296
+ test('returns skip for null agent', () => {
297
+ const result = shouldSkipIteration(null);
298
+ expect(result.skip).toBe(true);
299
+ });
300
+
301
+ test('returns skip when delayed', () => {
302
+ const future = new Date(Date.now() + 60000).toISOString();
303
+ const result = shouldSkipIteration(makeAgent({ delayEndTime: future }));
304
+ expect(result.skip).toBe(true);
305
+ });
306
+
307
+ test('returns skip when paused with future expiry', () => {
308
+ const future = new Date(Date.now() + 60000).toISOString();
309
+ const result = shouldSkipIteration(makeAgent({ pausedUntil: future }));
310
+ expect(result.skip).toBe(true);
311
+ });
312
+
313
+ test('returns no skip for normal agent', () => {
314
+ const result = shouldSkipIteration(makeAgent());
315
+ expect(result.skip).toBe(false);
316
+ expect(result.reason).toBeNull();
317
+ });
318
+ });
319
+ });
@@ -0,0 +1,206 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger } from '../../__test-utils__/mockFactories.js';
3
+
4
+ // Mock fs/promises, crypto, os, and userDataDir before importing
5
+ const mockFs = {
6
+ readFile: jest.fn(),
7
+ writeFile: jest.fn()
8
+ };
9
+ const mockCrypto = {
10
+ randomBytes: jest.fn(() => Buffer.alloc(32, 'a')),
11
+ pbkdf2Sync: jest.fn(() => Buffer.alloc(32, 'k')),
12
+ createCipheriv: jest.fn(),
13
+ createDecipheriv: jest.fn()
14
+ };
15
+ const mockOs = {
16
+ hostname: jest.fn(() => 'test-host'),
17
+ homedir: jest.fn(() => '/home/test'),
18
+ userInfo: jest.fn(() => ({ username: 'testuser' }))
19
+ };
20
+
21
+ jest.unstable_mockModule('fs', () => ({
22
+ promises: mockFs
23
+ }));
24
+
25
+ jest.unstable_mockModule('crypto', () => ({
26
+ default: mockCrypto,
27
+ ...mockCrypto
28
+ }));
29
+
30
+ jest.unstable_mockModule('os', () => ({
31
+ default: mockOs,
32
+ ...mockOs
33
+ }));
34
+
35
+ jest.unstable_mockModule('../../utilities/userDataDir.js', () => ({
36
+ getUserDataPaths: jest.fn(() => ({ settings: '/fake/settings', attachments: '/fake/attachments' })),
37
+ ensureUserDataDirs: jest.fn(async () => {})
38
+ }));
39
+
40
+ const { default: ApiKeyManager } = await import('../apiKeyManager.js');
41
+
42
+ describe('ApiKeyManager', () => {
43
+ let manager;
44
+ let logger;
45
+
46
+ beforeEach(() => {
47
+ jest.clearAllMocks();
48
+ logger = createMockLogger();
49
+ manager = new ApiKeyManager(logger);
50
+ });
51
+
52
+ test('constructor initializes with empty keys', () => {
53
+ expect(manager.keys.loxiaApiKey).toBeNull();
54
+ expect(manager.keys.vendorKeys).toEqual({});
55
+ expect(manager.initialized).toBe(false);
56
+ });
57
+
58
+ test('initialize sets up persistence and loads keys', async () => {
59
+ // Mock salt file read failure (new salt generation)
60
+ mockFs.readFile.mockRejectedValueOnce(new Error('ENOENT'));
61
+ // Mock writeFile for salt
62
+ mockFs.writeFile.mockResolvedValue(undefined);
63
+ // Mock readFile for loadFromDisk - ENOENT (no existing keys)
64
+ const enoent = new Error('No file');
65
+ enoent.code = 'ENOENT';
66
+ mockFs.readFile.mockRejectedValueOnce(enoent);
67
+
68
+ await manager.initialize();
69
+ expect(manager.initialized).toBe(true);
70
+ expect(manager.persistenceFile).toContain('api-keys.enc');
71
+ });
72
+
73
+ test('initialize only runs once', async () => {
74
+ manager.initialized = true;
75
+ await manager.initialize();
76
+ // Should not call ensureUserDataDirs if already initialized
77
+ expect(logger.info).not.toHaveBeenCalled();
78
+ });
79
+
80
+ test('initialize handles errors gracefully', async () => {
81
+ const { ensureUserDataDirs } = await import('../../utilities/userDataDir.js');
82
+ ensureUserDataDirs.mockRejectedValueOnce(new Error('disk full'));
83
+
84
+ await manager.initialize();
85
+ expect(logger.warn).toHaveBeenCalled();
86
+ });
87
+
88
+ test('_getMachineIdentifier returns colon-separated string', () => {
89
+ const id = manager._getMachineIdentifier();
90
+ expect(id).toContain('test-host');
91
+ expect(id).toContain('testuser');
92
+ expect(id).toContain('loxia-api-key-encryption-v1');
93
+ });
94
+
95
+ test('_encrypt throws when no encryption key', () => {
96
+ expect(() => manager._encrypt('test')).toThrow('Encryption key not initialized');
97
+ });
98
+
99
+ test('_decrypt throws when no encryption key', () => {
100
+ expect(() => manager._decrypt('test')).toThrow('Encryption key not initialized');
101
+ });
102
+
103
+ test('setSessionKeys stores loxia key and vendor keys', async () => {
104
+ manager.persistenceFile = null; // skip persist
105
+ await manager.setSessionKeys('sess-1', {
106
+ loxiaApiKey: 'loxia-key-123',
107
+ vendorKeys: { anthropic: 'anth-key' }
108
+ });
109
+ expect(manager.keys.loxiaApiKey).toBe('loxia-key-123');
110
+ expect(manager.keys.vendorKeys.anthropic).toBe('anth-key');
111
+ });
112
+
113
+ test('setSessionKeys merges vendor keys', async () => {
114
+ manager.persistenceFile = null;
115
+ manager.keys.vendorKeys = { openai: 'oai-key' };
116
+ await manager.setSessionKeys('sess-1', {
117
+ vendorKeys: { anthropic: 'anth-key' }
118
+ });
119
+ expect(manager.keys.vendorKeys.openai).toBe('oai-key');
120
+ expect(manager.keys.vendorKeys.anthropic).toBe('anth-key');
121
+ });
122
+
123
+ test('getSessionKeys returns keys regardless of sessionId', () => {
124
+ manager.keys.loxiaApiKey = 'test-key';
125
+ const keys = manager.getSessionKeys('any-session');
126
+ expect(keys.loxiaApiKey).toBe('test-key');
127
+ });
128
+
129
+ test('getKeysForRequest returns loxia key for platform-provided', () => {
130
+ manager.keys.loxiaApiKey = 'loxia-123';
131
+ const result = manager.getKeysForRequest('sess', { platformProvided: true, vendor: 'anthropic' });
132
+ expect(result.loxiaApiKey).toBe('loxia-123');
133
+ expect(result.vendorApiKey).toBeNull();
134
+ });
135
+
136
+ test('getKeysForRequest returns vendor key for direct access', () => {
137
+ manager.keys.loxiaApiKey = 'loxia-123';
138
+ manager.keys.vendorKeys = { anthropic: 'anth-key' };
139
+ const result = manager.getKeysForRequest('sess', { platformProvided: false, vendor: 'anthropic' });
140
+ expect(result.loxiaApiKey).toBe('loxia-123');
141
+ expect(result.vendorApiKey).toBe('anth-key');
142
+ });
143
+
144
+ test('getKeysForRequest returns null vendor key when no vendor specified', () => {
145
+ const result = manager.getKeysForRequest('sess', {});
146
+ expect(result.vendorApiKey).toBeNull();
147
+ });
148
+
149
+ test('removeSessionKeys clears all keys and returns true if had keys', async () => {
150
+ manager.persistenceFile = null;
151
+ manager.keys.loxiaApiKey = 'some-key';
152
+ const hadKeys = await manager.removeSessionKeys('sess');
153
+ expect(hadKeys).toBe(true);
154
+ expect(manager.keys.loxiaApiKey).toBeNull();
155
+ expect(manager.keys.vendorKeys).toEqual({});
156
+ });
157
+
158
+ test('removeSessionKeys returns false if no keys existed', async () => {
159
+ const hadKeys = await manager.removeSessionKeys('sess');
160
+ expect(hadKeys).toBe(false);
161
+ });
162
+
163
+ test('setGlobalKeys delegates to setSessionKeys', async () => {
164
+ manager.persistenceFile = null;
165
+ await manager.setGlobalKeys({ loxiaApiKey: 'global-key' });
166
+ expect(manager.keys.loxiaApiKey).toBe('global-key');
167
+ });
168
+
169
+ test('getActiveSessions returns empty array', () => {
170
+ expect(manager.getActiveSessions()).toEqual([]);
171
+ });
172
+
173
+ test('cleanupExpiredSessions returns 0', () => {
174
+ expect(manager.cleanupExpiredSessions()).toBe(0);
175
+ });
176
+
177
+ test('persist does nothing when not initialized', async () => {
178
+ manager.persistenceFile = null;
179
+ await manager.persist();
180
+ expect(mockFs.writeFile).not.toHaveBeenCalled();
181
+ });
182
+
183
+ test('loadFromDisk does nothing when not initialized', async () => {
184
+ manager.persistenceFile = null;
185
+ await manager.loadFromDisk();
186
+ // No errors expected
187
+ });
188
+
189
+ test('loadFromDisk handles ENOENT gracefully', async () => {
190
+ manager.persistenceFile = '/fake/file';
191
+ manager.encryptionKey = Buffer.alloc(32);
192
+ const err = new Error('not found');
193
+ err.code = 'ENOENT';
194
+ mockFs.readFile.mockRejectedValueOnce(err);
195
+ await manager.loadFromDisk();
196
+ expect(logger.debug).toHaveBeenCalled();
197
+ });
198
+
199
+ test('loadFromDisk handles decrypt errors gracefully', async () => {
200
+ manager.persistenceFile = '/fake/file';
201
+ manager.encryptionKey = Buffer.alloc(32);
202
+ mockFs.readFile.mockRejectedValueOnce(new Error('bad data'));
203
+ await manager.loadFromDisk();
204
+ expect(logger.warn).toHaveBeenCalled();
205
+ });
206
+ });