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
@@ -0,0 +1,887 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import TagParser from '../tagParser.js';
3
+
4
+ describe('TagParser', () => {
5
+ let parser;
6
+
7
+ beforeEach(() => {
8
+ parser = new TagParser();
9
+ // Silence console.log from the parser during tests
10
+ jest.spyOn(console, 'log').mockImplementation(() => {});
11
+ jest.spyOn(console, 'warn').mockImplementation(() => {});
12
+ jest.spyOn(console, 'error').mockImplementation(() => {});
13
+ });
14
+
15
+ // ─── extractToolCommands() ───────────────────────────────────────────
16
+
17
+ describe('extractToolCommands', () => {
18
+ test('returns empty array for plain text with no commands', () => {
19
+ const result = parser.extractToolCommands('Hello, this is just plain text with no JSON.');
20
+ expect(result).toEqual([]);
21
+ });
22
+
23
+ test('extracts command from JSON code block', () => {
24
+ const content = 'Here is a command:\n```json\n{"toolId":"terminal","parameters":{"command":"ls"}}\n```';
25
+ const result = parser.extractToolCommands(content);
26
+ expect(result).toHaveLength(1);
27
+ expect(result[0].toolId).toBe('terminal');
28
+ expect(result[0].parameters).toEqual({ command: 'ls' });
29
+ expect(result[0].type).toBe('json');
30
+ });
31
+
32
+ test('extracts multiple commands from multiple JSON blocks', () => {
33
+ const content = [
34
+ 'First command:',
35
+ '```json',
36
+ '{"toolId":"terminal","parameters":{"command":"pwd"}}',
37
+ '```',
38
+ 'Second command:',
39
+ '```json',
40
+ '{"toolId":"filesystem","parameters":{"path":"/tmp"}}',
41
+ '```'
42
+ ].join('\n');
43
+
44
+ const result = parser.extractToolCommands(content);
45
+ expect(result).toHaveLength(2);
46
+ expect(result[0].toolId).toBe('terminal');
47
+ expect(result[1].toolId).toBe('filesystem');
48
+ });
49
+
50
+ test('handles malformed JSON in code block via jsonrepair', () => {
51
+ const content = '```json\n{"toolId":"terminal","parameters":{"command":"ls"}\n```';
52
+ const result = parser.extractToolCommands(content);
53
+ expect(Array.isArray(result)).toBe(true);
54
+ });
55
+
56
+ test('skips JSON code blocks that are not tool commands', () => {
57
+ const content = '```json\n{"name":"test","version":"1.0.0"}\n```';
58
+ const result = parser.extractToolCommands(content);
59
+ expect(result).toHaveLength(0);
60
+ });
61
+
62
+ test('extracts plain JSON tool command not in code block', () => {
63
+ const content = '{"toolId":"filesystem","actions":[{"type":"read-file","path":"/tmp/x.txt"}]}';
64
+ const result = parser.extractToolCommands(content);
65
+ expect(result).toHaveLength(1);
66
+ expect(result[0].toolId).toBe('filesystem');
67
+ expect(result[0].type).toBe('json-plain');
68
+ });
69
+
70
+ test('does NOT double-extract JSON that is in a code block', () => {
71
+ const content = '```json\n{"toolId":"terminal","parameters":{"command":"echo hi"}}\n```';
72
+ const result = parser.extractToolCommands(content);
73
+ expect(result).toHaveLength(1);
74
+ expect(result[0].type).toBe('json');
75
+ });
76
+
77
+ test('handles actions array format with toolId at top level', () => {
78
+ const content = '```json\n{"toolId":"terminal","actions":[{"type":"run-command","command":"pwd"}]}\n```';
79
+ const result = parser.extractToolCommands(content);
80
+ expect(result).toHaveLength(1);
81
+ expect(result[0].toolId).toBe('terminal');
82
+ expect(result[0].actions).toEqual([{ type: 'run-command', command: 'pwd' }]);
83
+ });
84
+
85
+ test('handles toolCommands array format with multiple tools in one block', () => {
86
+ const content = [
87
+ '```json',
88
+ JSON.stringify({
89
+ toolCommands: [
90
+ { toolId: 'terminal', parameters: { command: 'ls' } },
91
+ { toolId: 'filesystem', parameters: { path: '/tmp' } }
92
+ ]
93
+ }),
94
+ '```'
95
+ ].join('\n');
96
+
97
+ const result = parser.extractToolCommands(content);
98
+ expect(result).toHaveLength(2);
99
+ expect(result[0].toolId).toBe('terminal');
100
+ expect(result[1].toolId).toBe('filesystem');
101
+ });
102
+
103
+ test('decodes HTML entities before parsing', () => {
104
+ const content = '```json\n{"toolId":"terminal","parameters":{"command":"ls"}}\n```';
105
+ const result = parser.extractToolCommands(content);
106
+ expect(result).toHaveLength(1);
107
+ expect(result[0].toolId).toBe('terminal');
108
+ });
109
+ });
110
+
111
+ // ─── _extractTopLevelParams ─────────────────────────────────────────
112
+
113
+ describe('_extractTopLevelParams', () => {
114
+ test('extracts all params except toolId and tool', () => {
115
+ const data = { toolId: 'terminal', tool: 'terminal', command: 'ls', path: '/tmp', flag: true };
116
+ const result = parser._extractTopLevelParams(data);
117
+ expect(result).toEqual({ command: 'ls', path: '/tmp', flag: true });
118
+ expect(result.toolId).toBeUndefined();
119
+ expect(result.tool).toBeUndefined();
120
+ });
121
+
122
+ test('returns empty object if only toolId/tool present', () => {
123
+ const result = parser._extractTopLevelParams({ toolId: 'x', tool: 'y' });
124
+ expect(result).toEqual({});
125
+ });
126
+ });
127
+
128
+ // ─── extractJSONCodeBlocks ──────────────────────────────────────────
129
+
130
+ describe('extractJSONCodeBlocks', () => {
131
+ test('skips block without explicit toolId', () => {
132
+ const content = '```json\n{"parameters":{"command":"ls"}}\n```';
133
+ const result = parser.extractJSONCodeBlocks(content);
134
+ expect(result).toHaveLength(0);
135
+ });
136
+
137
+ test('skips block with toolId "unknown"', () => {
138
+ const content = '```json\n{"toolId":"unknown","parameters":{"command":"ls"}}\n```';
139
+ const result = parser.extractJSONCodeBlocks(content);
140
+ expect(result).toHaveLength(0);
141
+ });
142
+
143
+ test('uses top-level params when no parameters wrapper', () => {
144
+ const content = '```json\n{"toolId":"terminal","command":"ls","path":"/tmp"}\n```';
145
+ const result = parser.extractJSONCodeBlocks(content);
146
+ expect(result).toHaveLength(1);
147
+ expect(result[0].parameters).toEqual({ command: 'ls', path: '/tmp' });
148
+ });
149
+
150
+ test('marks repaired/truncated JSON appropriately', () => {
151
+ // Provide truncated JSON that repair can fix
152
+ const content = '```json\n{"toolId":"terminal","parameters":{"command":"ls"}\n```';
153
+ const result = parser.extractJSONCodeBlocks(content);
154
+ // Whether it repairs or not depends on jsonRepair, just assert it does not crash
155
+ expect(Array.isArray(result)).toBe(true);
156
+ });
157
+
158
+ test('skips toolCommands entries without toolId', () => {
159
+ const content = [
160
+ '```json',
161
+ JSON.stringify({
162
+ toolCommands: [
163
+ { parameters: { command: 'ls' } },
164
+ { toolId: 'terminal', parameters: { command: 'pwd' } }
165
+ ]
166
+ }),
167
+ '```'
168
+ ].join('\n');
169
+ const result = parser.extractJSONCodeBlocks(content);
170
+ // Only the second entry with toolId should be extracted
171
+ expect(result.length).toBeGreaterThanOrEqual(1);
172
+ const terminalCmd = result.find(c => c.toolId === 'terminal');
173
+ expect(terminalCmd).toBeDefined();
174
+ });
175
+
176
+ test('toolCommands entries use parameters wrapper when present', () => {
177
+ const content = [
178
+ '```json',
179
+ JSON.stringify({
180
+ toolCommands: [
181
+ { toolId: 'terminal', parameters: { command: 'ls' } }
182
+ ]
183
+ }),
184
+ '```'
185
+ ].join('\n');
186
+ const result = parser.extractJSONCodeBlocks(content);
187
+ const cmd = result.find(c => c.toolId === 'terminal');
188
+ expect(cmd).toBeDefined();
189
+ expect(cmd.parameters).toEqual({ command: 'ls' });
190
+ });
191
+
192
+ test('toolCommands entries extract top-level params when no parameters wrapper', () => {
193
+ const content = [
194
+ '```json',
195
+ JSON.stringify({
196
+ toolCommands: [
197
+ { toolId: 'terminal', command: 'pwd', headless: true }
198
+ ]
199
+ }),
200
+ '```'
201
+ ].join('\n');
202
+ const result = parser.extractJSONCodeBlocks(content);
203
+ const cmd = result.find(c => c.toolId === 'terminal');
204
+ expect(cmd).toBeDefined();
205
+ expect(cmd.parameters.command).toBe('pwd');
206
+ expect(cmd.parameters.headless).toBe(true);
207
+ });
208
+ });
209
+
210
+ // ─── extractPlainJSON ───────────────────────────────────────────────
211
+
212
+ describe('extractPlainJSON', () => {
213
+ test('extracts plain JSON tool command with toolId and parameters', () => {
214
+ const content = '{"toolId":"terminal","parameters":{"command":"ls"}}';
215
+ const result = parser.extractPlainJSON(content);
216
+ expect(result).toHaveLength(1);
217
+ expect(result[0].toolId).toBe('terminal');
218
+ expect(result[0].type).toBe('json-plain');
219
+ expect(result[0].warning).toBeDefined();
220
+ });
221
+
222
+ test('extracts multi-line plain JSON', () => {
223
+ const content = [
224
+ '{',
225
+ ' "toolId": "filesystem",',
226
+ ' "actions": [{"type": "read-file", "path": "/tmp/test.txt"}]',
227
+ '}'
228
+ ].join('\n');
229
+ const result = parser.extractPlainJSON(content);
230
+ expect(result).toHaveLength(1);
231
+ expect(result[0].toolId).toBe('filesystem');
232
+ });
233
+
234
+ test('skips lines not starting with {', () => {
235
+ const content = 'some text\nanother line\nno json here';
236
+ const result = parser.extractPlainJSON(content);
237
+ expect(result).toEqual([]);
238
+ });
239
+
240
+ test('skips plain JSON without identifiable toolId', () => {
241
+ const content = '{"randomField":"value","otherField":123}';
242
+ const result = parser.extractPlainJSON(content);
243
+ expect(result).toEqual([]);
244
+ });
245
+
246
+ test('skips plain JSON where toolId resolves to unknown', () => {
247
+ const content = '{"type":"some-unknown-action","data":"val"}';
248
+ const result = parser.extractPlainJSON(content);
249
+ expect(result).toEqual([]);
250
+ });
251
+
252
+ test('handles incomplete JSON (no closing brace found)', () => {
253
+ const content = '{"toolId":"terminal","parameters":{"command":"ls"}';
254
+ const result = parser.extractPlainJSON(content);
255
+ // Either repaired or skipped, but should not throw
256
+ expect(Array.isArray(result)).toBe(true);
257
+ });
258
+
259
+ test('extracts plain JSON with actions array', () => {
260
+ const content = '{"toolId":"filesystem","actions":[{"type":"read-file","path":"/x"}]}';
261
+ const result = parser.extractPlainJSON(content);
262
+ expect(result).toHaveLength(1);
263
+ expect(result[0].actions).toBeDefined();
264
+ });
265
+
266
+ test('uses _extractTopLevelParams when no parameters wrapper', () => {
267
+ const content = '{"toolId":"terminal","command":"pwd","flag":true}';
268
+ const result = parser.extractPlainJSON(content);
269
+ expect(result).toHaveLength(1);
270
+ expect(result[0].parameters.command).toBe('pwd');
271
+ expect(result[0].parameters.flag).toBe(true);
272
+ });
273
+ });
274
+
275
+ // ─── removeJsonBlocks ──────────────────────────────────────────────
276
+
277
+ describe('removeJsonBlocks', () => {
278
+ test('removes JSON code blocks from content', () => {
279
+ const content = 'before\n```json\n{"key":"value"}\n```\nafter';
280
+ const result = parser.removeJsonBlocks(content);
281
+ expect(result).toContain('before');
282
+ expect(result).toContain('after');
283
+ expect(result).toContain('[JSON_BLOCK_REMOVED]');
284
+ expect(result).not.toContain('"key"');
285
+ });
286
+
287
+ test('handles multiple JSON code blocks', () => {
288
+ const content = '```json\n{}\n```\nmiddle\n```json\n{}\n```';
289
+ const result = parser.removeJsonBlocks(content);
290
+ const count = (result.match(/\[JSON_BLOCK_REMOVED\]/g) || []).length;
291
+ expect(count).toBe(2);
292
+ });
293
+
294
+ test('returns content unchanged when no code blocks', () => {
295
+ const content = 'no code blocks here';
296
+ expect(parser.removeJsonBlocks(content)).toBe(content);
297
+ });
298
+
299
+ test('handles unclosed code block (no closing ```)', () => {
300
+ const content = 'before\n```json\n{"key":"value"}';
301
+ const result = parser.removeJsonBlocks(content);
302
+ // Should return content as-is since no closing marker found
303
+ expect(result).toBe(content);
304
+ });
305
+ });
306
+
307
+ // ─── inferToolFromActions ──────────────────────────────────────────
308
+
309
+ describe('inferToolFromActions', () => {
310
+ test('returns terminal for run-command action type', () => {
311
+ const jsonData = { actions: [{ type: 'run-command' }] };
312
+ const result = parser.inferToolFromActions(jsonData);
313
+ expect(result).toBe('terminal');
314
+ });
315
+
316
+ test('returns toolId directly when present (standard structure)', () => {
317
+ const jsonData = { toolId: 'filesystem', parameters: { path: '/' } };
318
+ const result = parser.inferToolFromActions(jsonData);
319
+ expect(result).toBe('filesystem');
320
+ });
321
+
322
+ test('returns unknown for unrecognized structure', () => {
323
+ const jsonData = { randomField: 'value' };
324
+ const result = parser.inferToolFromActions(jsonData);
325
+ expect(result).toBe('unknown');
326
+ });
327
+
328
+ test('handles direct action type', () => {
329
+ const jsonData = { type: 'run-command', command: 'ls' };
330
+ const result = parser.inferToolFromActions(jsonData);
331
+ expect(result).toBe('terminal');
332
+ });
333
+
334
+ test('handles empty actions array', () => {
335
+ const jsonData = { actions: [] };
336
+ const result = parser.inferToolFromActions(jsonData);
337
+ expect(result).toBe('unknown');
338
+ });
339
+
340
+ test('handles toolCommands structure', () => {
341
+ const jsonData = {
342
+ toolCommands: [
343
+ { toolId: 'terminal', parameters: { command: 'ls' } }
344
+ ]
345
+ };
346
+ const result = parser.inferToolFromActions(jsonData);
347
+ expect(result).toBe('terminal');
348
+ });
349
+
350
+ test('handles empty toolCommands array', () => {
351
+ const jsonData = { toolCommands: [] };
352
+ const result = parser.inferToolFromActions(jsonData);
353
+ expect(result).toBe('unknown');
354
+ });
355
+ });
356
+
357
+ // ─── _findMatchingCodeBlockEnd ──────────────────────────────────────
358
+
359
+ describe('_findMatchingCodeBlockEnd', () => {
360
+ test('finds closing ``` outside of JSON strings', () => {
361
+ const content = '```json\n{"key":"value"}\n```';
362
+ const startPos = '```json\n'.length;
363
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
364
+ expect(result).toBe(content.indexOf('```', startPos));
365
+ });
366
+
367
+ test('skips ``` inside JSON string values', () => {
368
+ const jsonContent = '{"readme":"Use ```bash\\necho hello\\n``` for commands"}';
369
+ const content = '```json\n' + jsonContent + '\n```';
370
+ const startPos = '```json\n'.length;
371
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
372
+ // Should find the actual closing ```, not the ones inside the string
373
+ expect(result).toBeGreaterThan(startPos + jsonContent.length - 5);
374
+ });
375
+
376
+ test('handles escaped quotes inside strings', () => {
377
+ const content = '```json\n{"key":"val\\"ue"}\n```';
378
+ const startPos = '```json\n'.length;
379
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
380
+ expect(result).toBeGreaterThan(startPos);
381
+ });
382
+
383
+ test('uses fallback when state tracking fails to find closing marker', () => {
384
+ // Content where no clean closing ``` exists outside strings but lastIndexOf finds one
385
+ const content = '```json\n{"key":"value"}\n```';
386
+ const startPos = '```json\n'.length;
387
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
388
+ expect(result).toBeGreaterThan(-1);
389
+ });
390
+
391
+ test('returns -1 when no closing ``` found at all', () => {
392
+ const content = '```json\n{"key":"value"}\n';
393
+ const startPos = '```json\n'.length;
394
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
395
+ expect(result).toBe(-1);
396
+ });
397
+
398
+ test('does not match ``` followed by language name as closing marker', () => {
399
+ const content = '```json\n{"readme":"```bash\\ncode\\n```"}\n```';
400
+ const startPos = '```json\n'.length;
401
+ const result = parser._findMatchingCodeBlockEnd(content, startPos);
402
+ expect(result).toBeGreaterThan(startPos);
403
+ });
404
+ });
405
+
406
+ // ─── parseXMLParameters ────────────────────────────────────────────
407
+
408
+ describe('parseXMLParameters', () => {
409
+ // Note: parseXMLParameters calls this.isValidXmlTagName which is not defined;
410
+ // we mock it to allow testing
411
+ beforeEach(() => {
412
+ parser.isValidXmlTagName = jest.fn().mockReturnValue(true);
413
+ });
414
+
415
+ test('parses simple XML parameter tags', () => {
416
+ const content = '<path>/src/index.js</path>';
417
+ const result = parser.parseXMLParameters(content);
418
+ expect(result.path).toBeDefined();
419
+ expect(result.path.value).toBe('/src/index.js');
420
+ });
421
+
422
+ test('parses multiple tags', () => {
423
+ const content = '<path>/src/index.js</path><content>hello world</content>';
424
+ const result = parser.parseXMLParameters(content);
425
+ expect(result.path.value).toBe('/src/index.js');
426
+ expect(result.content.value).toBe('hello world');
427
+ });
428
+
429
+ test('handles empty tag content', () => {
430
+ const content = '<flag></flag>';
431
+ const result = parser.parseXMLParameters(content);
432
+ expect(result.flag).toBeDefined();
433
+ expect(result.flag.value).toBe('');
434
+ });
435
+
436
+ test('handles tags with attributes', () => {
437
+ const content = '<write path="/tmp/out.txt" mode="overwrite">file content</write>';
438
+ const result = parser.parseXMLParameters(content);
439
+ expect(result.write).toBeDefined();
440
+ expect(result.write.value).toBe('file content');
441
+ expect(result.write.attributes.path).toBe('/tmp/out.txt');
442
+ expect(result.write.attributes.mode).toBe('overwrite');
443
+ });
444
+
445
+ test('handles duplicate tag names by converting to array', () => {
446
+ const content = '<write path="/a.txt">content A</write><write path="/b.txt">content B</write>';
447
+ const result = parser.parseXMLParameters(content);
448
+ expect(Array.isArray(result.write)).toBe(true);
449
+ expect(result.write).toHaveLength(2);
450
+ expect(result.write[0].value).toBe('content A');
451
+ expect(result.write[1].value).toBe('content B');
452
+ });
453
+
454
+ test('handles triple duplicate tags (appends to existing array)', () => {
455
+ const content = '<item>1</item><item>2</item><item>3</item>';
456
+ const result = parser.parseXMLParameters(content);
457
+ expect(Array.isArray(result.item)).toBe(true);
458
+ expect(result.item).toHaveLength(3);
459
+ });
460
+
461
+ test('skips tags with / in name', () => {
462
+ parser.isValidXmlTagName = jest.fn().mockReturnValue(true);
463
+ const content = '</closing>text';
464
+ const result = parser.parseXMLParameters(content);
465
+ // Should not parse /closing as a param
466
+ expect(result['/closing']).toBeUndefined();
467
+ });
468
+
469
+ test('skips invalid XML tag names', () => {
470
+ parser.isValidXmlTagName = jest.fn().mockReturnValue(false);
471
+ const content = '<123invalid>value</123invalid>';
472
+ const result = parser.parseXMLParameters(content);
473
+ expect(Object.keys(result)).toHaveLength(0);
474
+ });
475
+
476
+ test('skips tag without closing tag', () => {
477
+ const content = '<open>value without closing';
478
+ const result = parser.parseXMLParameters(content);
479
+ expect(result.open).toBeUndefined();
480
+ });
481
+
482
+ test('handles self-closing tags with attributes', () => {
483
+ const content = '<config key="value" enabled="true"/>';
484
+ const result = parser.parseXMLParameters(content);
485
+ expect(result.config).toBeDefined();
486
+ expect(result.config.value).toBe('');
487
+ expect(result.config.attributes.key).toBe('value');
488
+ expect(result.config.attributes.enabled).toBe('true');
489
+ });
490
+
491
+ test('returns empty object for empty content', () => {
492
+ const result = parser.parseXMLParameters('');
493
+ expect(Object.keys(result)).toHaveLength(0);
494
+ });
495
+ });
496
+
497
+ // ─── extractAgentRedirects() ─────────────────────────────────────────
498
+
499
+ describe('extractAgentRedirects', () => {
500
+ test('returns empty array for text with no redirects', () => {
501
+ const result = parser.extractAgentRedirects('Just normal text, nothing to redirect.');
502
+ expect(result).toEqual([]);
503
+ });
504
+
505
+ test('parses basic agent-redirect with to and content', () => {
506
+ const content = '[agent-redirect to="agent-1"]hello world[/agent-redirect]';
507
+ const result = parser.extractAgentRedirects(content);
508
+ expect(result).toHaveLength(1);
509
+ expect(result[0].to).toBe('agent-1');
510
+ expect(result[0].content).toBe('hello world');
511
+ });
512
+
513
+ test('parses urgent and requires-response attributes', () => {
514
+ const content = '[agent-redirect to="agent-2" urgent="true" requiresResponse="true"]check this[/agent-redirect]';
515
+ const result = parser.extractAgentRedirects(content);
516
+ expect(result).toHaveLength(1);
517
+ expect(result[0].to).toBe('agent-2');
518
+ expect(result[0].urgent).toBe(true);
519
+ expect(result[0].requiresResponse).toBe(true);
520
+ expect(result[0].content).toBe('check this');
521
+ });
522
+
523
+ test('handles multiple redirects in same content', () => {
524
+ const content = [
525
+ '[agent-redirect to="agent-1"]first message[/agent-redirect]',
526
+ 'Some text in between.',
527
+ '[agent-redirect to="agent-2"]second message[/agent-redirect]'
528
+ ].join('\n');
529
+
530
+ const result = parser.extractAgentRedirects(content);
531
+ expect(result).toHaveLength(2);
532
+ expect(result[0].to).toBe('agent-1');
533
+ expect(result[1].to).toBe('agent-2');
534
+ });
535
+ });
536
+
537
+ // ─── Static methods ──────────────────────────────────────────────────
538
+
539
+ describe('TagParser.extractContent (static)', () => {
540
+ test('extracts text between matching tags', () => {
541
+ const content = '<summary>This is the summary</summary>';
542
+ const result = TagParser.extractContent(content, 'summary');
543
+ expect(result).toEqual(['This is the summary']);
544
+ });
545
+
546
+ test('returns empty array when tag not found', () => {
547
+ const result = TagParser.extractContent('No tags here.', 'nonexistent');
548
+ expect(result).toEqual([]);
549
+ });
550
+
551
+ test('handles multiple occurrences of the same tag', () => {
552
+ const content = '<item>first</item> middle <item>second</item> end <item>third</item>';
553
+ const result = TagParser.extractContent(content, 'item');
554
+ expect(result).toEqual(['first', 'second', 'third']);
555
+ });
556
+ });
557
+
558
+ describe('TagParser.extractTagsWithAttributes (static)', () => {
559
+ test('extracts tags with attributes', () => {
560
+ const content = '<file path="/tmp/x.txt" mode="read">content here</file>';
561
+ const result = TagParser.extractTagsWithAttributes(content, 'file');
562
+ expect(result).toHaveLength(1);
563
+ expect(result[0].content).toBe('content here');
564
+ expect(result[0].attributes.path).toBe('/tmp/x.txt');
565
+ expect(result[0].attributes.mode).toBe('read');
566
+ expect(result[0].rawMatch).toContain('<file');
567
+ });
568
+
569
+ test('returns empty array when no matching tags', () => {
570
+ const result = TagParser.extractTagsWithAttributes('no tags', 'file');
571
+ expect(result).toEqual([]);
572
+ });
573
+
574
+ test('handles multiple tags', () => {
575
+ const content = '<item id="1">first</item> <item id="2">second</item>';
576
+ const result = TagParser.extractTagsWithAttributes(content, 'item');
577
+ expect(result).toHaveLength(2);
578
+ expect(result[0].attributes.id).toBe('1');
579
+ expect(result[1].attributes.id).toBe('2');
580
+ });
581
+ });
582
+
583
+ describe('TagParser.extractBetweenTags (static)', () => {
584
+ test('extracts content between custom start and end tags', () => {
585
+ const content = '<<START>>inner content<<END>> trailing';
586
+ const result = TagParser.extractBetweenTags(content, '<<START>>', '<<END>>');
587
+ expect(result).toHaveLength(1);
588
+ expect(result[0].content).toBe('inner content');
589
+ expect(result[0].startIndex).toBe(0);
590
+ });
591
+
592
+ test('returns empty array when tags not found', () => {
593
+ const result = TagParser.extractBetweenTags('no tags here', '<<A>>', '<<B>>');
594
+ expect(result).toEqual([]);
595
+ });
596
+
597
+ test('handles multiple blocks between tags', () => {
598
+ const content = '[BEGIN]alpha[END] gap [BEGIN]beta[END]';
599
+ const result = TagParser.extractBetweenTags(content, '[BEGIN]', '[END]');
600
+ expect(result).toHaveLength(2);
601
+ expect(result[0].content).toBe('alpha');
602
+ expect(result[1].content).toBe('beta');
603
+ });
604
+
605
+ test('includes fullMatch and endIndex', () => {
606
+ const content = '<s>data</s>';
607
+ const result = TagParser.extractBetweenTags(content, '<s>', '</s>');
608
+ expect(result[0].fullMatch).toBe('<s>data</s>');
609
+ expect(result[0].endIndex).toBe(content.length);
610
+ });
611
+
612
+ test('returns empty when only start tag found', () => {
613
+ const result = TagParser.extractBetweenTags('<s>data', '<s>', '</s>');
614
+ expect(result).toEqual([]);
615
+ });
616
+ });
617
+
618
+ // ─── Utility methods ─────────────────────────────────────────────────
619
+
620
+ describe('decodeHtmlEntities', () => {
621
+ test('decodes common HTML entities', () => {
622
+ const input = '&lt;div&gt; &amp; &quot;hello&quot;';
623
+ const result = parser.decodeHtmlEntities(input);
624
+ expect(result).toBe('<div> & "hello"');
625
+ });
626
+
627
+ test('returns text unchanged when no entities present', () => {
628
+ const input = 'plain text no entities';
629
+ expect(parser.decodeHtmlEntities(input)).toBe(input);
630
+ });
631
+
632
+ test('decodes single-quote entities', () => {
633
+ expect(parser.decodeHtmlEntities('&#x27;')).toBe("'");
634
+ expect(parser.decodeHtmlEntities('&#39;')).toBe("'");
635
+ });
636
+
637
+ test('decodes slash entities', () => {
638
+ expect(parser.decodeHtmlEntities('&#x2F;')).toBe('/');
639
+ expect(parser.decodeHtmlEntities('&#47;')).toBe('/');
640
+ });
641
+ });
642
+
643
+ describe('isToolCommandJSON', () => {
644
+ test('returns truthy for object with toolId and parameters', () => {
645
+ expect(parser.isToolCommandJSON({ toolId: 'terminal', parameters: { cmd: 'ls' } })).toBeTruthy();
646
+ });
647
+
648
+ test('returns truthy for object with tool and actions', () => {
649
+ expect(parser.isToolCommandJSON({ tool: 'fs', actions: [] })).toBeTruthy();
650
+ });
651
+
652
+ test('returns truthy for object with toolId and files', () => {
653
+ expect(parser.isToolCommandJSON({ toolId: 'filesystem', files: [] })).toBeTruthy();
654
+ });
655
+
656
+ test('returns truthy for object with toolId and extra params', () => {
657
+ expect(parser.isToolCommandJSON({ toolId: 'terminal', command: 'ls' })).toBeTruthy();
658
+ });
659
+
660
+ test('returns falsy for null', () => {
661
+ expect(parser.isToolCommandJSON(null)).toBeFalsy();
662
+ });
663
+
664
+ test('returns falsy for non-object', () => {
665
+ expect(parser.isToolCommandJSON('string')).toBeFalsy();
666
+ });
667
+
668
+ test('returns falsy for object without toolId or tool', () => {
669
+ expect(parser.isToolCommandJSON({ parameters: {} })).toBeFalsy();
670
+ });
671
+ });
672
+
673
+ describe('_hasToolParams', () => {
674
+ test('returns true if object has keys other than toolId/tool', () => {
675
+ expect(parser._hasToolParams({ toolId: 'x', command: 'ls' })).toBe(true);
676
+ });
677
+
678
+ test('returns false if object only has toolId', () => {
679
+ expect(parser._hasToolParams({ toolId: 'x' })).toBe(false);
680
+ });
681
+
682
+ test('returns false for null', () => {
683
+ expect(parser._hasToolParams(null)).toBe(false);
684
+ });
685
+
686
+ test('returns false for non-object', () => {
687
+ expect(parser._hasToolParams('string')).toBe(false);
688
+ });
689
+ });
690
+
691
+ describe('matchAll', () => {
692
+ test('returns all matches with groups and indices', () => {
693
+ const content = 'key1="val1" key2="val2"';
694
+ const pattern = /([\w-]+)=["']([^"']*)["']/g;
695
+ const results = parser.matchAll(content, pattern);
696
+ expect(results).toHaveLength(2);
697
+ expect(results[0].groups[0]).toBe('key1');
698
+ expect(results[0].groups[1]).toBe('val1');
699
+ expect(results[1].groups[0]).toBe('key2');
700
+ expect(results[1].groups[1]).toBe('val2');
701
+ expect(typeof results[0].index).toBe('number');
702
+ });
703
+
704
+ test('returns empty array for no matches', () => {
705
+ const results = parser.matchAll('no matches', /xyz/g);
706
+ expect(results).toEqual([]);
707
+ });
708
+
709
+ test('resets lastIndex before matching', () => {
710
+ const pattern = /test/g;
711
+ pattern.lastIndex = 999;
712
+ const results = parser.matchAll('test test', pattern);
713
+ expect(results).toHaveLength(2);
714
+ });
715
+ });
716
+
717
+ describe('validateToolCommand', () => {
718
+ test('rejects command without toolId', () => {
719
+ const result = parser.validateToolCommand({ parameters: { path: '/tmp' } });
720
+ expect(result.valid).toBe(false);
721
+ expect(result.errors).toContain('Missing toolId');
722
+ });
723
+
724
+ test('rejects command without parameters or actions', () => {
725
+ const result = parser.validateToolCommand({ toolId: 'terminal' });
726
+ expect(result.valid).toBe(false);
727
+ expect(result.errors).toContain('Missing parameters or actions');
728
+ });
729
+
730
+ test('accepts valid command with toolId and parameters', () => {
731
+ const command = {
732
+ toolId: 'terminal',
733
+ parameters: { command: 'ls' },
734
+ type: 'json',
735
+ jsonData: { toolId: 'terminal', parameters: { command: 'ls' } }
736
+ };
737
+ const result = parser.validateToolCommand(command);
738
+ expect(result.valid).toBe(true);
739
+ expect(result.errors).toHaveLength(0);
740
+ });
741
+
742
+ test('accepts valid command with toolId and actions', () => {
743
+ const command = {
744
+ toolId: 'filesystem',
745
+ actions: [{ type: 'read-file' }],
746
+ type: 'json',
747
+ jsonData: { toolId: 'filesystem', actions: [] }
748
+ };
749
+ const result = parser.validateToolCommand(command);
750
+ expect(result.valid).toBe(true);
751
+ });
752
+
753
+ test('rejects JSON command without jsonData', () => {
754
+ const command = {
755
+ toolId: 'terminal',
756
+ parameters: { command: 'ls' },
757
+ type: 'json'
758
+ };
759
+ const result = parser.validateToolCommand(command);
760
+ expect(result.valid).toBe(false);
761
+ expect(result.errors).toContain('Missing jsonData for JSON command');
762
+ });
763
+
764
+ test('rejects json-plain command without jsonData', () => {
765
+ const command = {
766
+ toolId: 'terminal',
767
+ parameters: { command: 'ls' },
768
+ type: 'json-plain'
769
+ };
770
+ const result = parser.validateToolCommand(command);
771
+ expect(result.valid).toBe(false);
772
+ expect(result.errors).toContain('Missing jsonData for JSON command');
773
+ });
774
+ });
775
+
776
+ describe('normalizeToolCommand', () => {
777
+ test('normalizes simple JSON command', () => {
778
+ const command = {
779
+ toolId: 'terminal',
780
+ type: 'json',
781
+ parameters: { command: 'ls' },
782
+ rawContent: '...'
783
+ };
784
+ const result = parser.normalizeToolCommand(command);
785
+ expect(result.toolId).toBe('terminal');
786
+ expect(result.parameters.command).toBe('ls');
787
+ });
788
+
789
+ test('normalizes command with actions array', () => {
790
+ const command = {
791
+ toolId: 'terminal',
792
+ type: 'json',
793
+ parameters: {},
794
+ actions: [{ type: 'run-command', command: 'pwd' }],
795
+ rawContent: '...'
796
+ };
797
+ const result = parser.normalizeToolCommand(command);
798
+ expect(result.parameters.actions).toEqual([{ type: 'run-command', command: 'pwd' }]);
799
+ });
800
+
801
+ test('normalizes agentcommunication command with actions', () => {
802
+ const command = {
803
+ toolId: 'agentcommunication',
804
+ type: 'json',
805
+ parameters: {},
806
+ actions: [{ type: 'send-message', to: 'agent-1', content: 'hello' }],
807
+ rawContent: '...'
808
+ };
809
+ const result = parser.normalizeToolCommand(command);
810
+ expect(result.parameters.action).toBe('send-message');
811
+ expect(result.parameters.to).toBe('agent-1');
812
+ });
813
+ });
814
+
815
+ describe('parseAttributes', () => {
816
+ test('parses key-value attribute pairs', () => {
817
+ const result = parser.parseAttributes('to="agent-1" mode="write"');
818
+ expect(result.to).toBe('agent-1');
819
+ expect(result.mode).toBe('write');
820
+ });
821
+
822
+ test('returns empty object for empty string', () => {
823
+ expect(parser.parseAttributes('')).toEqual({});
824
+ });
825
+
826
+ test('returns empty object for null/undefined', () => {
827
+ expect(parser.parseAttributes(null)).toEqual({});
828
+ expect(parser.parseAttributes(undefined)).toEqual({});
829
+ });
830
+
831
+ test('handles single-quoted attributes', () => {
832
+ const result = parser.parseAttributes("key='value'");
833
+ expect(result.key).toBe('value');
834
+ });
835
+ });
836
+
837
+ describe('_toCamelCase', () => {
838
+ test('converts kebab-case to camelCase', () => {
839
+ expect(parser._toCamelCase('my-property')).toBe('myProperty');
840
+ });
841
+
842
+ test('converts snake_case to camelCase', () => {
843
+ expect(parser._toCamelCase('my_property')).toBe('myProperty');
844
+ });
845
+
846
+ test('leaves camelCase unchanged', () => {
847
+ expect(parser._toCamelCase('myProperty')).toBe('myProperty');
848
+ });
849
+ });
850
+
851
+ describe('cleanContent', () => {
852
+ test('removes agent redirects from content', () => {
853
+ const content = 'Hello [agent-redirect to="a1"]message[/agent-redirect] world';
854
+ const result = parser.cleanContent(content);
855
+ expect(result).not.toContain('[agent-redirect');
856
+ expect(result).toContain('Hello');
857
+ expect(result).toContain('world');
858
+ });
859
+
860
+ test('removes JSON tool command blocks from content', () => {
861
+ const content = 'Before\n```json\n{"toolId":"terminal","parameters":{"command":"ls"}}\n```\nAfter';
862
+ const result = parser.cleanContent(content);
863
+ expect(result).not.toContain('toolId');
864
+ expect(result).toContain('Before');
865
+ expect(result).toContain('After');
866
+ });
867
+
868
+ test('preserves non-tool JSON code blocks', () => {
869
+ const content = 'Config:\n```json\n{"name":"test","version":"1.0"}\n```\nDone';
870
+ const result = parser.cleanContent(content);
871
+ expect(result).toContain('"name":"test"');
872
+ });
873
+
874
+ test('cleans up excessive whitespace', () => {
875
+ const content = 'line1\n\n\n\nline2';
876
+ const result = parser.cleanContent(content);
877
+ expect(result).not.toMatch(/\n\s*\n\s*\n/);
878
+ });
879
+
880
+ test('removes toolCommands JSON blocks', () => {
881
+ const json = JSON.stringify({ toolCommands: [{ toolId: 'terminal', parameters: {} }] });
882
+ const content = `before\n\`\`\`json\n${json}\n\`\`\`\nafter`;
883
+ const result = parser.cleanContent(content);
884
+ expect(result).not.toContain('toolCommands');
885
+ });
886
+ });
887
+ });