minionsai 0.1.0

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 (361) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/bin/minions.mjs +19 -0
  4. package/dist/server/client/dist/assets/abap-BdImnpbu.js +1 -0
  5. package/dist/server/client/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
  6. package/dist/server/client/dist/assets/ada-bCR0ucgS.js +1 -0
  7. package/dist/server/client/dist/assets/andromeeda-C4gqWexZ.js +1 -0
  8. package/dist/server/client/dist/assets/angular-html-CU67Zn6k.js +1 -0
  9. package/dist/server/client/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  10. package/dist/server/client/dist/assets/apache-Pmp26Uib.js +1 -0
  11. package/dist/server/client/dist/assets/apex-D8_7TLub.js +1 -0
  12. package/dist/server/client/dist/assets/apl-dKokRX4l.js +1 -0
  13. package/dist/server/client/dist/assets/applescript-Co6uUVPk.js +1 -0
  14. package/dist/server/client/dist/assets/ara-BRHolxvo.js +1 -0
  15. package/dist/server/client/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
  16. package/dist/server/client/dist/assets/asm-D_Q5rh1f.js +1 -0
  17. package/dist/server/client/dist/assets/astro-CbQHKStN.js +1 -0
  18. package/dist/server/client/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  19. package/dist/server/client/dist/assets/awk-DMzUqQB5.js +1 -0
  20. package/dist/server/client/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
  21. package/dist/server/client/dist/assets/ayu-light-BA47KaF1.js +1 -0
  22. package/dist/server/client/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
  23. package/dist/server/client/dist/assets/ballerina-BFfxhgS-.js +1 -0
  24. package/dist/server/client/dist/assets/bat-BkioyH1T.js +1 -0
  25. package/dist/server/client/dist/assets/beancount-k_qm7-4y.js +1 -0
  26. package/dist/server/client/dist/assets/berry-uYugtg8r.js +1 -0
  27. package/dist/server/client/dist/assets/bibtex-CHM0blh-.js +1 -0
  28. package/dist/server/client/dist/assets/bicep-Bmn6On1c.js +1 -0
  29. package/dist/server/client/dist/assets/bird2-DPOp833l.js +1 -0
  30. package/dist/server/client/dist/assets/blade-D4QpJJKB.js +1 -0
  31. package/dist/server/client/dist/assets/bsl-BO_Y6i37.js +1 -0
  32. package/dist/server/client/dist/assets/c-BIGW1oBm.js +1 -0
  33. package/dist/server/client/dist/assets/c3-eo99z4R2.js +1 -0
  34. package/dist/server/client/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  35. package/dist/server/client/dist/assets/cairo-KRGpt6FW.js +1 -0
  36. package/dist/server/client/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  37. package/dist/server/client/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  38. package/dist/server/client/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  39. package/dist/server/client/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  40. package/dist/server/client/dist/assets/clarity-D53aC0YG.js +1 -0
  41. package/dist/server/client/dist/assets/clojure-P80f7IUj.js +1 -0
  42. package/dist/server/client/dist/assets/cmake-D1j8_8rp.js +1 -0
  43. package/dist/server/client/dist/assets/cobol-nwyudZeR.js +1 -0
  44. package/dist/server/client/dist/assets/codeowners-Bp6g37R7.js +1 -0
  45. package/dist/server/client/dist/assets/codeql-DsOJ9woJ.js +1 -0
  46. package/dist/server/client/dist/assets/coffee-Ch7k5sss.js +1 -0
  47. package/dist/server/client/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  48. package/dist/server/client/dist/assets/coq-DkFqJrB1.js +1 -0
  49. package/dist/server/client/dist/assets/cpp-CofmeUqb.js +1 -0
  50. package/dist/server/client/dist/assets/crystal-tKQVLTB8.js +1 -0
  51. package/dist/server/client/dist/assets/csharp-COcwbKMJ.js +1 -0
  52. package/dist/server/client/dist/assets/css-DPfMkruS.js +1 -0
  53. package/dist/server/client/dist/assets/csv-fuZLfV_i.js +1 -0
  54. package/dist/server/client/dist/assets/cue-D82EKSYY.js +1 -0
  55. package/dist/server/client/dist/assets/cypher-COkxafJQ.js +1 -0
  56. package/dist/server/client/dist/assets/d-85-TOEBH.js +1 -0
  57. package/dist/server/client/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  58. package/dist/server/client/dist/assets/dart-CF10PKvl.js +1 -0
  59. package/dist/server/client/dist/assets/dax-CEL-wOlO.js +1 -0
  60. package/dist/server/client/dist/assets/desktop-BmXAJ9_W.js +1 -0
  61. package/dist/server/client/dist/assets/diff-D97Zzqfu.js +1 -0
  62. package/dist/server/client/dist/assets/docker-BcOcwvcX.js +1 -0
  63. package/dist/server/client/dist/assets/dotenv-Da5cRb03.js +1 -0
  64. package/dist/server/client/dist/assets/dracula-BzJJZx-M.js +1 -0
  65. package/dist/server/client/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  66. package/dist/server/client/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  67. package/dist/server/client/dist/assets/edge-BkV0erSs.js +1 -0
  68. package/dist/server/client/dist/assets/elixir-CDX3lj18.js +1 -0
  69. package/dist/server/client/dist/assets/elm-DbKCFpqz.js +1 -0
  70. package/dist/server/client/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  71. package/dist/server/client/dist/assets/erb-B12qg9BL.js +1 -0
  72. package/dist/server/client/dist/assets/erlang-DsQrWhSR.js +1 -0
  73. package/dist/server/client/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  74. package/dist/server/client/dist/assets/everforest-light-C8M2exoo.js +1 -0
  75. package/dist/server/client/dist/assets/fennel-BYunw83y.js +1 -0
  76. package/dist/server/client/dist/assets/fish-BvzEVeQv.js +1 -0
  77. package/dist/server/client/dist/assets/fluent-C4IJs8-o.js +1 -0
  78. package/dist/server/client/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  79. package/dist/server/client/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
  80. package/dist/server/client/dist/assets/fsharp-CXgrBDvD.js +1 -0
  81. package/dist/server/client/dist/assets/gdresource-BOOCDP_w.js +1 -0
  82. package/dist/server/client/dist/assets/gdscript-C5YyOfLZ.js +1 -0
  83. package/dist/server/client/dist/assets/gdshader-DkwncUOv.js +1 -0
  84. package/dist/server/client/dist/assets/genie-D0YGMca9.js +1 -0
  85. package/dist/server/client/dist/assets/gherkin-DyxjwDmM.js +1 -0
  86. package/dist/server/client/dist/assets/git-commit-F4YmCXRG.js +1 -0
  87. package/dist/server/client/dist/assets/git-rebase-r7XF79zn.js +1 -0
  88. package/dist/server/client/dist/assets/github-dark-DHJKELXO.js +1 -0
  89. package/dist/server/client/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  90. package/dist/server/client/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  91. package/dist/server/client/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  92. package/dist/server/client/dist/assets/github-light-DAi9KRSo.js +1 -0
  93. package/dist/server/client/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  94. package/dist/server/client/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  95. package/dist/server/client/dist/assets/gleam-BspZqrRM.js +1 -0
  96. package/dist/server/client/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  97. package/dist/server/client/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  98. package/dist/server/client/dist/assets/glsl-DplSGwfg.js +1 -0
  99. package/dist/server/client/dist/assets/gn-n2N0HUVH.js +1 -0
  100. package/dist/server/client/dist/assets/gnuplot-DdkO51Og.js +1 -0
  101. package/dist/server/client/dist/assets/go-CxLEBnE3.js +1 -0
  102. package/dist/server/client/dist/assets/graphql-ChdNCCLP.js +1 -0
  103. package/dist/server/client/dist/assets/groovy-gcz8RCvz.js +1 -0
  104. package/dist/server/client/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  105. package/dist/server/client/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  106. package/dist/server/client/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  107. package/dist/server/client/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  108. package/dist/server/client/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  109. package/dist/server/client/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  110. package/dist/server/client/dist/assets/hack-CaT9iCJl.js +1 -0
  111. package/dist/server/client/dist/assets/haml-B8DHNrY2.js +1 -0
  112. package/dist/server/client/dist/assets/handlebars-BL8al0AC.js +1 -0
  113. package/dist/server/client/dist/assets/haskell-Df6bDoY_.js +1 -0
  114. package/dist/server/client/dist/assets/haxe-CzTSHFRz.js +1 -0
  115. package/dist/server/client/dist/assets/hcl-BWvSN4gD.js +1 -0
  116. package/dist/server/client/dist/assets/highlighted-body-TPN3WLV5-Dmyr2DoJ.js +1 -0
  117. package/dist/server/client/dist/assets/hjson-D5-asLiD.js +1 -0
  118. package/dist/server/client/dist/assets/hlsl-D3lLCCz7.js +1 -0
  119. package/dist/server/client/dist/assets/horizon-BUw7H-hv.js +1 -0
  120. package/dist/server/client/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
  121. package/dist/server/client/dist/assets/houston-DnULxvSX.js +1 -0
  122. package/dist/server/client/dist/assets/html-GMplVEZG.js +1 -0
  123. package/dist/server/client/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  124. package/dist/server/client/dist/assets/http-jrhK8wxY.js +1 -0
  125. package/dist/server/client/dist/assets/hurl-irOxFIW8.js +1 -0
  126. package/dist/server/client/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  127. package/dist/server/client/dist/assets/hy-DFXneXwc.js +1 -0
  128. package/dist/server/client/dist/assets/imba-DGztddWO.js +1 -0
  129. package/dist/server/client/dist/assets/index-BB7507W7.css +1 -0
  130. package/dist/server/client/dist/assets/index-hLQDnL9J.js +694 -0
  131. package/dist/server/client/dist/assets/ini-BEwlwnbL.js +1 -0
  132. package/dist/server/client/dist/assets/java-CylS5w8V.js +1 -0
  133. package/dist/server/client/dist/assets/javascript-wDzz0qaB.js +1 -0
  134. package/dist/server/client/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  135. package/dist/server/client/dist/assets/jison-wvAkD_A8.js +1 -0
  136. package/dist/server/client/dist/assets/json-Cp-IABpG.js +1 -0
  137. package/dist/server/client/dist/assets/json5-C9tS-k6U.js +1 -0
  138. package/dist/server/client/dist/assets/jsonc-Des-eS-w.js +1 -0
  139. package/dist/server/client/dist/assets/jsonl-DcaNXYhu.js +1 -0
  140. package/dist/server/client/dist/assets/jsonnet-DFQXde-d.js +1 -0
  141. package/dist/server/client/dist/assets/jssm-C2t-YnRu.js +1 -0
  142. package/dist/server/client/dist/assets/jsx-g9-lgVsj.js +1 -0
  143. package/dist/server/client/dist/assets/julia-CxzCAyBv.js +1 -0
  144. package/dist/server/client/dist/assets/just-Cw27pwNe.js +1 -0
  145. package/dist/server/client/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  146. package/dist/server/client/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  147. package/dist/server/client/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  148. package/dist/server/client/dist/assets/kdl-DV7GczEv.js +1 -0
  149. package/dist/server/client/dist/assets/kotlin-BdnUsdx6.js +1 -0
  150. package/dist/server/client/dist/assets/kusto-DZf3V79B.js +1 -0
  151. package/dist/server/client/dist/assets/laserwave-DUszq2jm.js +1 -0
  152. package/dist/server/client/dist/assets/latex-CWtU0Tv5.js +1 -0
  153. package/dist/server/client/dist/assets/lean-BZvkOJ9d.js +1 -0
  154. package/dist/server/client/dist/assets/less-B1dDrJ26.js +1 -0
  155. package/dist/server/client/dist/assets/light-plus-B7mTdjB0.js +1 -0
  156. package/dist/server/client/dist/assets/liquid-DYVedYrR.js +1 -0
  157. package/dist/server/client/dist/assets/llvm-DjAJT7YJ.js +1 -0
  158. package/dist/server/client/dist/assets/log-2UxHyX5q.js +1 -0
  159. package/dist/server/client/dist/assets/logo-BtOb2qkB.js +1 -0
  160. package/dist/server/client/dist/assets/lua-BaeVxFsk.js +1 -0
  161. package/dist/server/client/dist/assets/luau-C-HG3fhB.js +1 -0
  162. package/dist/server/client/dist/assets/make-CHLpvVh8.js +1 -0
  163. package/dist/server/client/dist/assets/markdown-Cvjx9yec.js +1 -0
  164. package/dist/server/client/dist/assets/marko-CnJfTvn9.js +1 -0
  165. package/dist/server/client/dist/assets/material-theme-D5KoaKCx.js +1 -0
  166. package/dist/server/client/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  167. package/dist/server/client/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  168. package/dist/server/client/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  169. package/dist/server/client/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  170. package/dist/server/client/dist/assets/matlab-D7o27uSR.js +1 -0
  171. package/dist/server/client/dist/assets/mdc-BMNejdWA.js +1 -0
  172. package/dist/server/client/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  173. package/dist/server/client/dist/assets/mermaid-mWjccvbQ.js +1 -0
  174. package/dist/server/client/dist/assets/min-dark-CafNBF8u.js +1 -0
  175. package/dist/server/client/dist/assets/min-light-CTRr51gU.js +1 -0
  176. package/dist/server/client/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  177. package/dist/server/client/dist/assets/mojo-rZm6bMo-.js +1 -0
  178. package/dist/server/client/dist/assets/monokai-D4h5O-jR.js +1 -0
  179. package/dist/server/client/dist/assets/moonbit-_H4v1dQx.js +1 -0
  180. package/dist/server/client/dist/assets/move-IF9eRakj.js +1 -0
  181. package/dist/server/client/dist/assets/narrat-DRg8JJMk.js +1 -0
  182. package/dist/server/client/dist/assets/nextflow-Zz6hmt5N.js +1 -0
  183. package/dist/server/client/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  184. package/dist/server/client/dist/assets/nginx-BpAMiNFr.js +1 -0
  185. package/dist/server/client/dist/assets/night-owl-C39BiMTA.js +1 -0
  186. package/dist/server/client/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
  187. package/dist/server/client/dist/assets/nim-CVrawwO9.js +1 -0
  188. package/dist/server/client/dist/assets/nix-CwoSXNpI.js +1 -0
  189. package/dist/server/client/dist/assets/nord-Ddv68eIx.js +1 -0
  190. package/dist/server/client/dist/assets/nushell-Cz2AlsmD.js +1 -0
  191. package/dist/server/client/dist/assets/objective-c-DXmwc3jG.js +1 -0
  192. package/dist/server/client/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  193. package/dist/server/client/dist/assets/ocaml-C0hk2d4L.js +1 -0
  194. package/dist/server/client/dist/assets/odin-BBf5iR-q.js +1 -0
  195. package/dist/server/client/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  196. package/dist/server/client/dist/assets/one-light-C3Wv6jpd.js +1 -0
  197. package/dist/server/client/dist/assets/openscad-C4EeE6gA.js +1 -0
  198. package/dist/server/client/dist/assets/pascal-D93ZcfNL.js +1 -0
  199. package/dist/server/client/dist/assets/perl-C0TMdlhV.js +1 -0
  200. package/dist/server/client/dist/assets/php-Dhbhpdrm.js +1 -0
  201. package/dist/server/client/dist/assets/pkl-u5AG7uiY.js +1 -0
  202. package/dist/server/client/dist/assets/plastic-3e1v2bzS.js +1 -0
  203. package/dist/server/client/dist/assets/plsql-ChMvpjG-.js +1 -0
  204. package/dist/server/client/dist/assets/po-BTJTHyun.js +1 -0
  205. package/dist/server/client/dist/assets/poimandres-CS3Unz2-.js +1 -0
  206. package/dist/server/client/dist/assets/polar-C0HS_06l.js +1 -0
  207. package/dist/server/client/dist/assets/postcss-CXtECtnM.js +1 -0
  208. package/dist/server/client/dist/assets/powerquery-CEu0bR-o.js +1 -0
  209. package/dist/server/client/dist/assets/powershell-Dpen1YoG.js +1 -0
  210. package/dist/server/client/dist/assets/prisma-Dd19v3D-.js +1 -0
  211. package/dist/server/client/dist/assets/prolog-CbFg5uaA.js +1 -0
  212. package/dist/server/client/dist/assets/proto-C7zT0LnQ.js +1 -0
  213. package/dist/server/client/dist/assets/pug-CGlum2m_.js +1 -0
  214. package/dist/server/client/dist/assets/puppet-BMWR74SV.js +1 -0
  215. package/dist/server/client/dist/assets/purescript-CklMAg4u.js +1 -0
  216. package/dist/server/client/dist/assets/python-B6aJPvgy.js +1 -0
  217. package/dist/server/client/dist/assets/qml-3beO22l8.js +1 -0
  218. package/dist/server/client/dist/assets/qmldir-C8lEn-DE.js +1 -0
  219. package/dist/server/client/dist/assets/qss-IeuSbFQv.js +1 -0
  220. package/dist/server/client/dist/assets/r-Dspwwk_N.js +1 -0
  221. package/dist/server/client/dist/assets/racket-BqYA7rlc.js +1 -0
  222. package/dist/server/client/dist/assets/raku-DXvB9xmW.js +1 -0
  223. package/dist/server/client/dist/assets/razor-Uh8Bk_45.js +1 -0
  224. package/dist/server/client/dist/assets/red-bN70gL4F.js +1 -0
  225. package/dist/server/client/dist/assets/reg-C-SQnVFl.js +1 -0
  226. package/dist/server/client/dist/assets/regexp-CDVJQ6XC.js +1 -0
  227. package/dist/server/client/dist/assets/rel-C3B-1QV4.js +1 -0
  228. package/dist/server/client/dist/assets/riscv-BM1_JUlF.js +1 -0
  229. package/dist/server/client/dist/assets/ron-D8l8udqQ.js +1 -0
  230. package/dist/server/client/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  231. package/dist/server/client/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  232. package/dist/server/client/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  233. package/dist/server/client/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  234. package/dist/server/client/dist/assets/rst-BrH8l1NY.js +1 -0
  235. package/dist/server/client/dist/assets/ruby-Dw2BHqvy.js +1 -0
  236. package/dist/server/client/dist/assets/rust-B1yitclQ.js +1 -0
  237. package/dist/server/client/dist/assets/sas-cz2c8ADy.js +1 -0
  238. package/dist/server/client/dist/assets/sass-Cj5Yp3dK.js +1 -0
  239. package/dist/server/client/dist/assets/scala-C151Ov-r.js +1 -0
  240. package/dist/server/client/dist/assets/scheme-C98Dy4si.js +1 -0
  241. package/dist/server/client/dist/assets/scss-OYdSNvt2.js +1 -0
  242. package/dist/server/client/dist/assets/sdbl-DVxCFoDh.js +1 -0
  243. package/dist/server/client/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  244. package/dist/server/client/dist/assets/shellscript-Yzrsuije.js +1 -0
  245. package/dist/server/client/dist/assets/shellsession-BADoaaVG.js +1 -0
  246. package/dist/server/client/dist/assets/slack-dark-BthQWCQV.js +1 -0
  247. package/dist/server/client/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  248. package/dist/server/client/dist/assets/smalltalk-BERRCDM3.js +1 -0
  249. package/dist/server/client/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  250. package/dist/server/client/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  251. package/dist/server/client/dist/assets/solarized-light-L9t79GZl.js +1 -0
  252. package/dist/server/client/dist/assets/solidity-rGO070M0.js +1 -0
  253. package/dist/server/client/dist/assets/soy-Brmx7dQM.js +1 -0
  254. package/dist/server/client/dist/assets/sparql-rVzFXLq3.js +1 -0
  255. package/dist/server/client/dist/assets/splunk-BtCnVYZw.js +1 -0
  256. package/dist/server/client/dist/assets/sql-BLtJtn59.js +1 -0
  257. package/dist/server/client/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  258. package/dist/server/client/dist/assets/stata-BH5u7GGu.js +1 -0
  259. package/dist/server/client/dist/assets/stylus-BEDo0Tqx.js +1 -0
  260. package/dist/server/client/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
  261. package/dist/server/client/dist/assets/svelte-C_ipcX3V.js +1 -0
  262. package/dist/server/client/dist/assets/swift-D82vCrfD.js +1 -0
  263. package/dist/server/client/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  264. package/dist/server/client/dist/assets/system-verilog-CnnmHF94.js +1 -0
  265. package/dist/server/client/dist/assets/systemd-4A_iFExJ.js +1 -0
  266. package/dist/server/client/dist/assets/talonscript-CkByrt1z.js +1 -0
  267. package/dist/server/client/dist/assets/tasl-QIJgUcNo.js +1 -0
  268. package/dist/server/client/dist/assets/tcl-dwOrl1Do.js +1 -0
  269. package/dist/server/client/dist/assets/templ-P3uqSqPl.js +1 -0
  270. package/dist/server/client/dist/assets/terraform-BETggiCN.js +1 -0
  271. package/dist/server/client/dist/assets/tex-idrVyKtj.js +1 -0
  272. package/dist/server/client/dist/assets/tokyo-night-hegEt444.js +1 -0
  273. package/dist/server/client/dist/assets/toml-vGWfd6FD.js +1 -0
  274. package/dist/server/client/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
  275. package/dist/server/client/dist/assets/tsv-B_m7g4N7.js +1 -0
  276. package/dist/server/client/dist/assets/tsx-COt5Ahok.js +1 -0
  277. package/dist/server/client/dist/assets/turtle-BsS91CYL.js +1 -0
  278. package/dist/server/client/dist/assets/twig-DNn4PbVi.js +1 -0
  279. package/dist/server/client/dist/assets/typescript-BPQ3VLAy.js +1 -0
  280. package/dist/server/client/dist/assets/typespec-BGHnOYBU.js +1 -0
  281. package/dist/server/client/dist/assets/typst-DHCkPAjA.js +1 -0
  282. package/dist/server/client/dist/assets/v-BcVCzyr7.js +1 -0
  283. package/dist/server/client/dist/assets/vala-CsfeWuGM.js +1 -0
  284. package/dist/server/client/dist/assets/vb-D17OF-Vu.js +1 -0
  285. package/dist/server/client/dist/assets/verilog-BQ8w6xss.js +1 -0
  286. package/dist/server/client/dist/assets/vesper-DU1UobuO.js +1 -0
  287. package/dist/server/client/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  288. package/dist/server/client/dist/assets/viml-CJc9bBzg.js +1 -0
  289. package/dist/server/client/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  290. package/dist/server/client/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  291. package/dist/server/client/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  292. package/dist/server/client/dist/assets/vue-DN_0RTcg.js +1 -0
  293. package/dist/server/client/dist/assets/vue-html-AaS7Mt5G.js +1 -0
  294. package/dist/server/client/dist/assets/vue-vine-CQOfvN7w.js +1 -0
  295. package/dist/server/client/dist/assets/vyper-CDx5xZoG.js +1 -0
  296. package/dist/server/client/dist/assets/wasm-CG6Dc4jp.js +1 -0
  297. package/dist/server/client/dist/assets/wasm-MzD3tlZU.js +1 -0
  298. package/dist/server/client/dist/assets/wenyan-BV7otONQ.js +1 -0
  299. package/dist/server/client/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  300. package/dist/server/client/dist/assets/wikitext-BhOHFoWU.js +1 -0
  301. package/dist/server/client/dist/assets/wit-5i3qLPDT.js +1 -0
  302. package/dist/server/client/dist/assets/wolfram-lXgVvXCa.js +1 -0
  303. package/dist/server/client/dist/assets/xml-sdJ4AIDG.js +1 -0
  304. package/dist/server/client/dist/assets/xsl-CtQFsRM5.js +1 -0
  305. package/dist/server/client/dist/assets/yaml-Buea-lGh.js +1 -0
  306. package/dist/server/client/dist/assets/zenscript-DVFEvuxE.js +1 -0
  307. package/dist/server/client/dist/assets/zig-VOosw3JB.js +1 -0
  308. package/dist/server/client/dist/favicon.ico +0 -0
  309. package/dist/server/client/dist/index.html +17 -0
  310. package/dist/server/client/dist/logo.png +0 -0
  311. package/dist/server/server/adapters/hermes-worker.d.ts +37 -0
  312. package/dist/server/server/adapters/hermes-worker.js +525 -0
  313. package/dist/server/server/adapters/types.d.ts +39 -0
  314. package/dist/server/server/adapters/types.js +1 -0
  315. package/dist/server/server/adapters/worker-protocol.d.ts +141 -0
  316. package/dist/server/server/adapters/worker-protocol.js +1 -0
  317. package/dist/server/server/agent-settings.d.ts +12 -0
  318. package/dist/server/server/agent-settings.js +56 -0
  319. package/dist/server/server/app.d.ts +5 -0
  320. package/dist/server/server/app.js +41 -0
  321. package/dist/server/server/db/index.d.ts +2 -0
  322. package/dist/server/server/db/index.js +14 -0
  323. package/dist/server/server/db/queries.d.ts +22 -0
  324. package/dist/server/server/db/queries.js +116 -0
  325. package/dist/server/server/db/schema.sql +16 -0
  326. package/dist/server/server/errors.d.ts +3 -0
  327. package/dist/server/server/errors.js +12 -0
  328. package/dist/server/server/events.d.ts +7 -0
  329. package/dist/server/server/events.js +50 -0
  330. package/dist/server/server/frontend.d.ts +4 -0
  331. package/dist/server/server/frontend.js +32 -0
  332. package/dist/server/server/index.d.ts +2 -0
  333. package/dist/server/server/index.js +63 -0
  334. package/dist/server/server/live-chat.d.ts +17 -0
  335. package/dist/server/server/live-chat.js +217 -0
  336. package/dist/server/server/paths.d.ts +7 -0
  337. package/dist/server/server/paths.js +39 -0
  338. package/dist/server/server/prompts/task-agent.d.ts +1 -0
  339. package/dist/server/server/prompts/task-agent.js +26 -0
  340. package/dist/server/server/routes/agent.d.ts +4 -0
  341. package/dist/server/server/routes/agent.js +94 -0
  342. package/dist/server/server/routes/chat.d.ts +1 -0
  343. package/dist/server/server/routes/chat.js +177 -0
  344. package/dist/server/server/routes/cron.d.ts +4 -0
  345. package/dist/server/server/routes/cron.js +109 -0
  346. package/dist/server/server/routes/files.d.ts +1 -0
  347. package/dist/server/server/routes/files.js +543 -0
  348. package/dist/server/server/routes/skills.d.ts +1 -0
  349. package/dist/server/server/routes/skills.js +13 -0
  350. package/dist/server/server/routes/tasks.d.ts +1 -0
  351. package/dist/server/server/routes/tasks.js +107 -0
  352. package/dist/server/server/skills/catalog.d.ts +21 -0
  353. package/dist/server/server/skills/catalog.js +160 -0
  354. package/dist/server/server/workers/hermes_cron.py +241 -0
  355. package/dist/server/server/workers/hermes_sessions.py +264 -0
  356. package/dist/server/server/workers/hermes_worker.py +1270 -0
  357. package/dist/server/server/workers/hermes_worker_utils.py +39 -0
  358. package/dist/server/shared/types.d.ts +211 -0
  359. package/dist/server/shared/types.js +2 -0
  360. package/package.json +74 -0
  361. package/skills/lead-generation/SKILL.md +41 -0
@@ -0,0 +1,1270 @@
1
+ #!/usr/bin/env python3
2
+ """JSONL bridge between Minions and Hermes AIAgent."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import dataclasses
8
+ import inspect
9
+ import json
10
+ import os
11
+ import re
12
+ import sys
13
+ import threading
14
+ import time
15
+ import traceback
16
+ import uuid
17
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
18
+ from pathlib import Path
19
+ from typing import Any, Callable
20
+
21
+ # Alias so submodules importing `hermes_worker` see this module even when run as `__main__`.
22
+ sys.modules.setdefault("hermes_worker", sys.modules[__name__])
23
+
24
+ from hermes_worker_utils import (
25
+ WorkerError,
26
+ string_or_none,
27
+ truncate_with_ellipsis,
28
+ )
29
+ from hermes_sessions import (
30
+ load_agent_history,
31
+ open_session,
32
+ project_session_messages,
33
+ project_session_metadata,
34
+ )
35
+ from hermes_cron import (
36
+ get_cron_job,
37
+ get_cron_run_content,
38
+ list_cron_jobs,
39
+ list_cron_runs,
40
+ pause_cron_job,
41
+ remove_cron_job,
42
+ resume_cron_job,
43
+ start_cron_ticker,
44
+ tick_cron,
45
+ trigger_cron_job,
46
+ )
47
+
48
+ PROTOCOL_OUT = sys.stdout
49
+ PROTOCOL_LOCK = threading.Lock()
50
+
51
+ # Cap on concurrent AIAgent.run_conversation calls (chat + judge).
52
+ AGENT_RUN_LIMIT = int(os.environ.get("HERMES_AGENT_RUN_LIMIT", "10"))
53
+ AGENT_SEMAPHORE = threading.BoundedSemaphore(AGENT_RUN_LIMIT)
54
+ ACTIVE_TASKS: dict[str, str] = {}
55
+ ACTIVE_TASKS_LOCK = threading.Lock()
56
+
57
+ ALLOWED_REASONING = {"none", "minimal", "low", "medium", "high", "xhigh"}
58
+ KNOWN_PROVIDER_PREFIXES = {
59
+ "anthropic",
60
+ "openai",
61
+ "openai-codex",
62
+ "copilot",
63
+ "deepseek",
64
+ "gemini",
65
+ "google",
66
+ "kimi",
67
+ "kimi-coding",
68
+ "minimax",
69
+ "mistral",
70
+ "mistralai",
71
+ "moonshotai",
72
+ "nous",
73
+ "ollama",
74
+ "ollama-cloud",
75
+ "openrouter",
76
+ "qwen",
77
+ "x-ai",
78
+ "xai",
79
+ "z-ai",
80
+ "zai",
81
+ }
82
+
83
+ _AGENT_DIR: Path | None = None
84
+ _IMPORTS_READY = False
85
+ _IMPORTS_LOCK = threading.Lock()
86
+ _AIAgent: Any = None
87
+ _AIAgent_PARAMS: set[str] = set()
88
+ _SessionDB: Any = None
89
+ _CONFIG_CACHE: dict[str, Any] | None = None
90
+ _CONFIG_MTIME: float = 0.0
91
+ _MODEL_EXECUTOR = ThreadPoolExecutor(max_workers=1)
92
+ try:
93
+ _MODEL_LIST_CACHE_TTL_SECONDS = max(0.0, float(os.environ.get("MINIONS_MODEL_LIST_CACHE_TTL_SECONDS", "60")))
94
+ except ValueError:
95
+ _MODEL_LIST_CACHE_TTL_SECONDS = 60.0
96
+
97
+
98
+ @dataclasses.dataclass
99
+ class _ModelListCache:
100
+ data: dict[str, Any]
101
+ config_mtime: float
102
+ expires_at: float
103
+
104
+
105
+ _MODEL_LIST_CACHE: _ModelListCache | None = None
106
+ _MODEL_LIST_CACHE_LOCK = threading.Lock()
107
+
108
+
109
+ def _send(payload: dict[str, Any]) -> None:
110
+ with PROTOCOL_LOCK:
111
+ PROTOCOL_OUT.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n")
112
+ PROTOCOL_OUT.flush()
113
+
114
+
115
+ def _result(request_id: str, data: dict[str, Any]) -> None:
116
+ _send({"id": request_id, "type": "result", "data": data})
117
+
118
+
119
+ def _error_payload(exc: BaseException) -> dict[str, str]:
120
+ if isinstance(exc, WorkerError):
121
+ payload = {"message": str(exc), "code": exc.code}
122
+ if exc.hint:
123
+ payload["hint"] = exc.hint
124
+ return payload
125
+
126
+ message = str(exc) or exc.__class__.__name__
127
+ lower = message.lower()
128
+ code = "worker_error"
129
+ hint = None
130
+
131
+ if isinstance(exc, ImportError) or "no module named" in lower:
132
+ code = "import_error"
133
+ hint = "Use HERMES_PYTHON=~/.hermes/hermes-agent/venv/bin/python."
134
+ elif "unauthorized" in lower or "authentication" in lower or "401" in lower or "api key" in lower:
135
+ code = "auth_error"
136
+ hint = "Run hermes model or update ~/.hermes/config.yaml credentials."
137
+ elif "rate limit" in lower or "429" in lower:
138
+ code = "rate_limit"
139
+ hint = "Retry later or switch provider/model."
140
+ elif "quota" in lower or "credit" in lower or "insufficient" in lower:
141
+ code = "quota_exhausted"
142
+ hint = "Top up provider account or switch provider/model."
143
+ elif "model" in lower and ("not found" in lower or "rejected" in lower or "invalid" in lower):
144
+ code = "model_error"
145
+ hint = "Pick another model from the model menu."
146
+
147
+ payload = {"message": message, "code": code}
148
+ if hint:
149
+ payload["hint"] = hint
150
+ return payload
151
+
152
+
153
+ def _send_error(request_id: str, exc: BaseException) -> None:
154
+ _send({"id": request_id, "type": "error", "error": _error_payload(exc)})
155
+
156
+
157
+ def _resolve_agent_dir_from_hermes_cli() -> Path | None:
158
+ import shutil
159
+
160
+ hermes_bin = shutil.which("hermes")
161
+ if not hermes_bin:
162
+ return None
163
+ try:
164
+ real = Path(hermes_bin).resolve()
165
+ # Typical layout: <agent-dir>/venv/bin/hermes
166
+ candidate = real.parent.parent.parent
167
+ if (candidate / "run_agent.py").exists():
168
+ return candidate
169
+ except OSError:
170
+ pass
171
+ return None
172
+
173
+
174
+ def _discover_agent_dir() -> Path:
175
+ candidates: list[Path] = []
176
+
177
+ env_dir = os.environ.get("HERMES_AGENT_DIR", "").strip()
178
+ if env_dir:
179
+ candidates.append(Path(env_dir).expanduser())
180
+
181
+ hermes_home = Path(os.environ.get("HERMES_HOME", str(Path.home() / ".hermes"))).expanduser()
182
+ candidates.append(hermes_home / "hermes-agent")
183
+ candidates.append(Path.home() / ".hermes" / "hermes-agent")
184
+
185
+ seen: set[str] = set()
186
+ for candidate in candidates:
187
+ try:
188
+ resolved = candidate.resolve()
189
+ except OSError:
190
+ resolved = candidate
191
+ key = str(resolved)
192
+ if key in seen:
193
+ continue
194
+ seen.add(key)
195
+ if (resolved / "run_agent.py").exists():
196
+ return resolved
197
+
198
+ cli_dir = _resolve_agent_dir_from_hermes_cli()
199
+ if cli_dir:
200
+ return cli_dir
201
+
202
+ raise WorkerError(
203
+ "Hermes agent source not found.",
204
+ code="hermes_not_found",
205
+ hint="Set HERMES_AGENT_DIR or install Hermes into ~/.hermes/hermes-agent.",
206
+ )
207
+
208
+
209
+ def _ensure_imports() -> None:
210
+ if _IMPORTS_READY:
211
+ return
212
+ with _IMPORTS_LOCK:
213
+ if _IMPORTS_READY:
214
+ return
215
+ _ensure_imports_unlocked()
216
+
217
+
218
+ def _ensure_imports_unlocked() -> None:
219
+ global _AGENT_DIR, _IMPORTS_READY, _AIAgent, _AIAgent_PARAMS, _SessionDB
220
+
221
+ _AGENT_DIR = _discover_agent_dir()
222
+ agent_dir_str = str(_AGENT_DIR)
223
+ if agent_dir_str not in sys.path:
224
+ sys.path.append(agent_dir_str)
225
+
226
+ try:
227
+ from run_agent import AIAgent
228
+ except ImportError as exc:
229
+ raise WorkerError(
230
+ f"Could not import Hermes AIAgent: {exc}",
231
+ code="import_error",
232
+ hint="Use HERMES_PYTHON=~/.hermes/hermes-agent/venv/bin/python.",
233
+ ) from exc
234
+
235
+ _AIAgent = AIAgent
236
+ _AIAgent_PARAMS = set(inspect.signature(AIAgent.__init__).parameters)
237
+ try:
238
+ from hermes_state import SessionDB
239
+ _SessionDB = SessionDB
240
+ except Exception:
241
+ _SessionDB = None
242
+
243
+ _IMPORTS_READY = True
244
+
245
+
246
+ def _load_config() -> dict[str, Any]:
247
+ global _CONFIG_CACHE, _CONFIG_MTIME
248
+ _ensure_imports()
249
+
250
+ config_path = Path(os.environ.get("HERMES_HOME", str(Path.home() / ".hermes"))) / "config.yaml"
251
+ try:
252
+ mtime = config_path.stat().st_mtime
253
+ except OSError:
254
+ mtime = 0.0
255
+
256
+ if _CONFIG_CACHE is not None and mtime == _CONFIG_MTIME:
257
+ return _CONFIG_CACHE
258
+
259
+ try:
260
+ from hermes_cli.config import load_config
261
+
262
+ cfg = load_config()
263
+ result = cfg if isinstance(cfg, dict) else {}
264
+ except Exception:
265
+ result = {}
266
+
267
+ _CONFIG_CACHE = result
268
+ _CONFIG_MTIME = mtime
269
+ return result
270
+
271
+
272
+ def _clear_model_list_cache() -> None:
273
+ global _MODEL_LIST_CACHE
274
+ with _MODEL_LIST_CACHE_LOCK:
275
+ _MODEL_LIST_CACHE = None
276
+
277
+
278
+ def _model_section(cfg: dict[str, Any]) -> dict[str, Any]:
279
+ model_cfg = cfg.get("model")
280
+ if isinstance(model_cfg, dict):
281
+ data = dict(model_cfg)
282
+ if not data.get("default") and data.get("model"):
283
+ data["default"] = data.get("model")
284
+ return data
285
+ if isinstance(model_cfg, str) and model_cfg.strip():
286
+ return {"default": model_cfg.strip()}
287
+ return {}
288
+
289
+
290
+ def _normalize_reasoning(value: Any) -> str | None:
291
+ if not isinstance(value, str):
292
+ return None
293
+ normalized = value.strip().lower()
294
+ return normalized if normalized in ALLOWED_REASONING else None
295
+
296
+
297
+ def _default_reasoning(cfg: dict[str, Any]) -> str | None:
298
+ agent_cfg = cfg.get("agent")
299
+ raw = agent_cfg.get("reasoning_effort") if isinstance(agent_cfg, dict) else None
300
+ return _normalize_reasoning(raw) or "medium"
301
+
302
+
303
+ def _set_defaults(request: dict[str, Any]) -> dict[str, Any]:
304
+ global _CONFIG_CACHE
305
+ _ensure_imports()
306
+ from hermes_cli.config import load_config as _load_full_config, save_config
307
+
308
+ cfg = _load_full_config()
309
+
310
+ if not isinstance(cfg.get("model"), dict):
311
+ cfg["model"] = {}
312
+
313
+ if "model" in request:
314
+ raw_model = request["model"]
315
+ if isinstance(raw_model, str) and raw_model.strip():
316
+ model_val = raw_model.strip()
317
+ parsed = _parse_provider_model(model_val)
318
+ if parsed:
319
+ provider_hint, bare_model = parsed
320
+ if provider_hint and not _provider_hint_is_available(provider_hint):
321
+ _raise_invalid_provider(provider_hint)
322
+ cfg["model"]["default"] = bare_model
323
+ cfg["model"]["provider"] = provider_hint
324
+ else:
325
+ cfg["model"]["default"] = model_val
326
+ cfg["model"].pop("provider", None)
327
+
328
+ if "reasoningEffort" in request:
329
+ normalized = _normalize_reasoning(request["reasoningEffort"])
330
+ if normalized:
331
+ if not isinstance(cfg.get("agent"), dict):
332
+ cfg["agent"] = {}
333
+ cfg["agent"]["reasoning_effort"] = normalized
334
+
335
+ save_config(cfg)
336
+ _CONFIG_CACHE = None
337
+ _clear_model_list_cache()
338
+
339
+ return _defaults_from_config(cfg)
340
+
341
+
342
+ def _defaults_from_config(cfg: dict[str, Any] | None = None) -> dict[str, Any]:
343
+ cfg = cfg if cfg is not None else _load_config()
344
+ model_cfg = _model_section(cfg)
345
+ display_cfg = cfg.get("display")
346
+
347
+ return {
348
+ "provider": string_or_none(model_cfg.get("provider")),
349
+ "model": string_or_none(model_cfg.get("default")),
350
+ "baseUrl": string_or_none(model_cfg.get("base_url")),
351
+ "apiMode": string_or_none(model_cfg.get("api_mode")),
352
+ "reasoningEffort": _default_reasoning(cfg),
353
+ "showReasoning": bool(display_cfg.get("show_reasoning")) if isinstance(display_cfg, dict) and isinstance(display_cfg.get("show_reasoning"), bool) else True,
354
+ }
355
+
356
+
357
+ def _task_key_for(request: dict[str, Any]) -> str:
358
+ return (
359
+ string_or_none(request.get("taskId"))
360
+ or string_or_none(request.get("sessionId"))
361
+ or str(request.get("id"))
362
+ )
363
+
364
+
365
+ def _try_mark_task_active(task_key: str, request_id: str) -> bool:
366
+ with ACTIVE_TASKS_LOCK:
367
+ if task_key in ACTIVE_TASKS:
368
+ return False
369
+ ACTIVE_TASKS[task_key] = request_id
370
+ return True
371
+
372
+
373
+ def _clear_task_active(task_key: str, request_id: str) -> None:
374
+ with ACTIVE_TASKS_LOCK:
375
+ if ACTIVE_TASKS.get(task_key) == request_id:
376
+ ACTIVE_TASKS.pop(task_key, None)
377
+
378
+
379
+ def _custom_providers(cfg: dict[str, Any]) -> list[dict[str, Any]]:
380
+ try:
381
+ from hermes_cli.config import get_compatible_custom_providers
382
+
383
+ providers = get_compatible_custom_providers(cfg)
384
+ return providers if isinstance(providers, list) else []
385
+ except Exception:
386
+ raw = cfg.get("custom_providers")
387
+ return raw if isinstance(raw, list) else []
388
+
389
+
390
+ def _custom_provider_models(entry: dict[str, Any]) -> list[str]:
391
+ models: list[str] = []
392
+ for key in ("model", "default_model"):
393
+ value = entry.get(key)
394
+ if isinstance(value, str) and value.strip():
395
+ models.append(value.strip())
396
+
397
+ raw_models = entry.get("models")
398
+ if isinstance(raw_models, dict):
399
+ models.extend(str(k).strip() for k in raw_models.keys() if str(k).strip())
400
+ elif isinstance(raw_models, list):
401
+ for item in raw_models:
402
+ if isinstance(item, str) and item.strip():
403
+ models.append(item.strip())
404
+ elif isinstance(item, dict):
405
+ mid = item.get("id") or item.get("model") or item.get("name")
406
+ if isinstance(mid, str) and mid.strip():
407
+ models.append(mid.strip())
408
+
409
+ return _dedupe(models)
410
+
411
+
412
+ def _parse_provider_model(raw: str) -> tuple[str, str] | None:
413
+ if raw.startswith("@") and ":" in raw:
414
+ provider, model = raw[1:].split(":", 1)
415
+ return provider, model
416
+ return None
417
+
418
+
419
+ def _provider_hint_is_available(provider: str) -> bool:
420
+ if provider.startswith("custom:"):
421
+ return True
422
+ try:
423
+ from hermes_cli.runtime_provider import resolve_runtime_provider # type: ignore
424
+
425
+ resolve_runtime_provider(requested=provider)
426
+ return True
427
+ except ImportError:
428
+ return True
429
+ except Exception:
430
+ return False
431
+
432
+
433
+ def _raise_invalid_provider(provider: str) -> None:
434
+ raise WorkerError(
435
+ f"Provider '{provider}' is not configured or runnable by this Hermes install.",
436
+ code="invalid_provider",
437
+ hint="Choose a configured provider, or configure this provider in Hermes first.",
438
+ )
439
+
440
+
441
+ def _dedupe(values: list[str]) -> list[str]:
442
+ seen: set[str] = set()
443
+ out: list[str] = []
444
+ for value in values:
445
+ if value in seen:
446
+ continue
447
+ seen.add(value)
448
+ out.append(value)
449
+ return out
450
+
451
+
452
+ def _add_model(
453
+ groups: dict[str, list[dict[str, Any]]],
454
+ provider: str,
455
+ model_id: str,
456
+ source: str,
457
+ default_model: str | None,
458
+ label: str | None = None,
459
+ ) -> None:
460
+ if not model_id:
461
+ return
462
+ bucket = groups.setdefault(provider or "configured", [])
463
+ if any(item["id"] == model_id for item in bucket):
464
+ return
465
+ bucket.append({
466
+ "id": model_id,
467
+ "label": label or model_id,
468
+ "source": source,
469
+ "isCurrentDefault": bool(default_model and model_id == default_model),
470
+ })
471
+
472
+
473
+ def _provider_model_ids_with_timeout(provider: str, timeout: float = 4.0) -> list[str]:
474
+ try:
475
+ future = _MODEL_EXECUTOR.submit(_provider_model_ids, provider)
476
+ return future.result(timeout=timeout)
477
+ except TimeoutError:
478
+ return []
479
+ except Exception:
480
+ return []
481
+
482
+
483
+ def _provider_model_ids(provider: str) -> list[str]:
484
+ from hermes_cli.models import provider_model_ids
485
+
486
+ models = provider_model_ids(provider)
487
+ return [str(model).strip() for model in models or [] if str(model).strip()]
488
+
489
+
490
+ def _groups_have_model(groups: dict[str, list[dict[str, Any]]], model_id: str) -> bool:
491
+ return any(
492
+ item.get("id") == model_id
493
+ for models in groups.values()
494
+ for item in models
495
+ )
496
+
497
+
498
+ def _model_option_id(provider: str | None, model_id: str, active_provider: str | None) -> str:
499
+ if not provider or provider == active_provider:
500
+ return model_id
501
+ if provider.startswith("custom:"):
502
+ return model_id
503
+ return f"@{provider}:{model_id}"
504
+
505
+
506
+ def _list_authenticated_model_groups(
507
+ cfg: dict[str, Any],
508
+ defaults: dict[str, Any],
509
+ ) -> dict[str, list[dict[str, Any]]] | None:
510
+ try:
511
+ from hermes_cli.model_switch import list_authenticated_providers
512
+ except Exception:
513
+ return None
514
+
515
+ providers_cfg = cfg.get("providers")
516
+ user_providers = providers_cfg if isinstance(providers_cfg, dict) else {}
517
+ custom_providers = _custom_providers(cfg)
518
+ active_provider = defaults["provider"]
519
+ default_model = defaults["model"]
520
+ groups: dict[str, list[dict[str, Any]]] = {}
521
+
522
+ try:
523
+ providers = list_authenticated_providers(
524
+ current_provider=active_provider or "",
525
+ current_base_url=defaults.get("baseUrl") or "",
526
+ current_model=default_model or "",
527
+ user_providers=user_providers,
528
+ custom_providers=custom_providers,
529
+ max_models=500,
530
+ )
531
+ except Exception:
532
+ return None
533
+
534
+ for provider_info in providers:
535
+ if not isinstance(provider_info, dict):
536
+ continue
537
+ slug = string_or_none(provider_info.get("slug"))
538
+ group_name = string_or_none(provider_info.get("name")) or slug or "configured"
539
+ is_user_defined = bool(provider_info.get("is_user_defined"))
540
+ if not is_user_defined and slug and not _provider_hint_is_available(slug):
541
+ continue
542
+ source = "custom" if is_user_defined else "catalog"
543
+ models = provider_info.get("models")
544
+ if not isinstance(models, list):
545
+ continue
546
+ for raw_model in models:
547
+ model_id = string_or_none(raw_model)
548
+ if not model_id:
549
+ continue
550
+ option_id = model_id if is_user_defined else _model_option_id(slug, model_id, active_provider)
551
+ _add_model(groups, group_name, option_id, source, default_model, label=model_id)
552
+
553
+ return groups
554
+
555
+
556
+ def _list_models() -> dict[str, Any]:
557
+ global _MODEL_LIST_CACHE
558
+
559
+ cfg = _load_config()
560
+ config_mtime = _CONFIG_MTIME
561
+ now = time.monotonic()
562
+ with _MODEL_LIST_CACHE_LOCK:
563
+ cached = _MODEL_LIST_CACHE
564
+ if cached is not None and cached.config_mtime == config_mtime and now < cached.expires_at:
565
+ return cached.data
566
+
567
+ defaults = _defaults_from_config(cfg)
568
+ default_model = defaults["model"]
569
+ active_provider = defaults["provider"]
570
+ authenticated_groups = _list_authenticated_model_groups(cfg, defaults)
571
+ groups = authenticated_groups or {}
572
+
573
+ if default_model and not _groups_have_model(groups, default_model):
574
+ _add_model(groups, active_provider or "current", default_model, "current", default_model)
575
+
576
+ if active_provider and authenticated_groups is None:
577
+ for model_id in _provider_model_ids_with_timeout(active_provider):
578
+ _add_model(groups, active_provider, model_id, "catalog", default_model)
579
+
580
+ for entry in _custom_providers(cfg):
581
+ if not isinstance(entry, dict):
582
+ continue
583
+ name = str(entry.get("name") or "custom").strip() or "custom"
584
+ provider = f"custom:{name.lower().replace(' ', '-')}"
585
+ for model_id in _custom_provider_models(entry):
586
+ _add_model(groups, provider, model_id, "custom", default_model)
587
+
588
+ aliases = cfg.get("model_aliases")
589
+ if isinstance(aliases, dict):
590
+ for alias, target in aliases.items():
591
+ if isinstance(alias, str) and alias.strip():
592
+ label = f"{alias.strip()} -> {target}" if target else alias.strip()
593
+ bucket = groups.setdefault("aliases", [])
594
+ if not any(item["id"] == alias.strip() for item in bucket):
595
+ bucket.append({
596
+ "id": alias.strip(),
597
+ "label": label,
598
+ "source": "alias",
599
+ "isCurrentDefault": bool(default_model and alias.strip() == default_model),
600
+ })
601
+
602
+ result = {
603
+ "defaultModel": default_model,
604
+ "activeProvider": active_provider,
605
+ "groups": [{"provider": provider, "models": models} for provider, models in groups.items()],
606
+ }
607
+
608
+ with _MODEL_LIST_CACHE_LOCK:
609
+ _MODEL_LIST_CACHE = _ModelListCache(
610
+ data=result,
611
+ config_mtime=config_mtime,
612
+ expires_at=time.monotonic() + _MODEL_LIST_CACHE_TTL_SECONDS,
613
+ )
614
+
615
+ return result
616
+
617
+
618
+ def _resolve_model_provider(requested_model: str | None, cfg: dict[str, Any] | None = None) -> tuple[str, str | None, str | None]:
619
+ cfg = cfg if cfg is not None else _load_config()
620
+ model_cfg = _model_section(cfg)
621
+ config_provider = string_or_none(model_cfg.get("provider"))
622
+ config_base_url = string_or_none(model_cfg.get("base_url"))
623
+ default_model = string_or_none(model_cfg.get("default"))
624
+ model_id = (requested_model or default_model or "").strip()
625
+
626
+ if not model_id:
627
+ return model_id, config_provider, config_base_url
628
+
629
+ for entry in _custom_providers(cfg):
630
+ if not isinstance(entry, dict):
631
+ continue
632
+ name = str(entry.get("name") or "").strip()
633
+ if not name:
634
+ continue
635
+ if model_id in _custom_provider_models(entry):
636
+ return model_id, f"custom:{name.lower().replace(' ', '-')}", string_or_none(entry.get("base_url"))
637
+
638
+ parsed = _parse_provider_model(model_id)
639
+ if parsed:
640
+ provider_hint, bare_model = parsed
641
+ return bare_model, provider_hint or config_provider, None
642
+
643
+ if "/" in model_id:
644
+ prefix, bare = model_id.split("/", 1)
645
+ prefix_normalized = prefix.lower()
646
+ if config_provider == "openrouter":
647
+ return model_id, "openrouter", config_base_url
648
+ if config_provider and prefix_normalized == config_provider:
649
+ return bare, config_provider, config_base_url
650
+ if config_provider in {"nous", "opencode-zen", "opencode-go"}:
651
+ return model_id, config_provider, config_base_url
652
+ if config_base_url:
653
+ if prefix_normalized in KNOWN_PROVIDER_PREFIXES:
654
+ return bare, config_provider, config_base_url
655
+ return model_id, config_provider, config_base_url
656
+ if prefix_normalized in KNOWN_PROVIDER_PREFIXES and prefix_normalized != config_provider:
657
+ return model_id, "openrouter", None
658
+
659
+ return model_id, config_provider, config_base_url
660
+
661
+
662
+ def _resolve_toolsets(cfg: dict[str, Any]) -> list[str] | None:
663
+ try:
664
+ from hermes_cli.tools_config import _get_platform_tools
665
+
666
+ toolsets = _get_platform_tools(cfg, "cli")
667
+ return list(toolsets) if toolsets else None
668
+ except Exception:
669
+ platform_toolsets = cfg.get("platform_toolsets")
670
+ if isinstance(platform_toolsets, dict) and isinstance(platform_toolsets.get("cli"), list):
671
+ return list(platform_toolsets["cli"])
672
+ return None
673
+
674
+
675
+ def _fallback_model(cfg: dict[str, Any]) -> dict[str, Any] | None:
676
+ raw = cfg.get("fallback_model")
677
+ if not isinstance(raw, dict):
678
+ return None
679
+ model = string_or_none(raw.get("model"))
680
+ if not model:
681
+ return None
682
+ return {
683
+ "model": model,
684
+ "provider": string_or_none(raw.get("provider")),
685
+ "base_url": string_or_none(raw.get("base_url")),
686
+ }
687
+
688
+
689
+ def _parse_reasoning(effort: str | None) -> dict[str, Any] | None:
690
+ if not effort:
691
+ return None
692
+ try:
693
+ from hermes_constants import parse_reasoning_effort
694
+
695
+ return parse_reasoning_effort(effort)
696
+ except Exception:
697
+ if effort == "none":
698
+ return {"enabled": False}
699
+ if effort in ALLOWED_REASONING:
700
+ return {"enabled": True, "effort": effort}
701
+ return None
702
+
703
+
704
+ def _create_agent(
705
+ *,
706
+ session_id: str,
707
+ requested_model: str | None,
708
+ reasoning_effort: str | None,
709
+ callbacks: dict[str, Any] | None = None,
710
+ ) -> Any:
711
+ _ensure_imports()
712
+ cfg = _load_config()
713
+ defaults = _defaults_from_config(cfg)
714
+ resolved_reasoning_effort = reasoning_effort or defaults.get("reasoningEffort")
715
+ resolved_model, resolved_provider, resolved_base_url = _resolve_model_provider(requested_model, cfg)
716
+
717
+ try:
718
+ from hermes_cli.runtime_provider import resolve_runtime_provider # type: ignore
719
+
720
+ runtime = resolve_runtime_provider(
721
+ requested=resolved_provider,
722
+ explicit_base_url=resolved_base_url,
723
+ target_model=resolved_model,
724
+ )
725
+ except Exception as exc:
726
+ err = _error_payload(exc)
727
+ raise WorkerError(str(exc), code=err.get("code", "worker_error"), hint=err.get("hint")) from exc
728
+
729
+ if not resolved_provider:
730
+ resolved_provider = string_or_none(runtime.get("provider"))
731
+ if not resolved_base_url:
732
+ resolved_base_url = string_or_none(runtime.get("base_url"))
733
+
734
+ def clarify_callback(question: Any, choices: Any = None) -> str:
735
+ return (
736
+ "The user is not available for an interactive clarification right now. "
737
+ "Make a reasonable assumption, proceed, and call out the assumption in the response if it matters."
738
+ )
739
+
740
+ session_db = None
741
+ if _SessionDB is not None:
742
+ try:
743
+ session_db = _SessionDB()
744
+ except Exception:
745
+ session_db = None
746
+
747
+ agent_params = _AIAgent_PARAMS
748
+ agent_kwargs: dict[str, Any] = {
749
+ "model": resolved_model,
750
+ "provider": resolved_provider,
751
+ "base_url": resolved_base_url,
752
+ "api_key": runtime.get("api_key"),
753
+ "quiet_mode": True,
754
+ "verbose_logging": False,
755
+ "platform": "minions",
756
+ "session_id": session_id,
757
+ "session_db": session_db,
758
+ "enabled_toolsets": _resolve_toolsets(cfg),
759
+ "fallback_model": _fallback_model(cfg),
760
+ "clarify_callback": clarify_callback,
761
+ }
762
+ if callbacks:
763
+ agent_kwargs.update(callbacks)
764
+
765
+ reasoning_config = _parse_reasoning(resolved_reasoning_effort)
766
+ if "reasoning_config" in agent_params and reasoning_config is not None:
767
+ agent_kwargs["reasoning_config"] = reasoning_config
768
+ if "api_mode" in agent_params:
769
+ agent_kwargs["api_mode"] = runtime.get("api_mode")
770
+ if "acp_command" in agent_params:
771
+ agent_kwargs["acp_command"] = runtime.get("command")
772
+ elif "command" in agent_params:
773
+ agent_kwargs["command"] = runtime.get("command")
774
+ if "acp_args" in agent_params:
775
+ agent_kwargs["acp_args"] = list(runtime.get("args") or [])
776
+ elif "args" in agent_params:
777
+ agent_kwargs["args"] = list(runtime.get("args") or [])
778
+ if "credential_pool" in agent_params:
779
+ agent_kwargs["credential_pool"] = runtime.get("credential_pool")
780
+ if "gateway_session_key" in agent_params:
781
+ agent_kwargs["gateway_session_key"] = session_id
782
+
783
+ filtered_kwargs = {
784
+ key: value
785
+ for key, value in agent_kwargs.items()
786
+ if key in agent_params and value is not None
787
+ }
788
+
789
+ return _AIAgent(**filtered_kwargs)
790
+
791
+
792
+ def _sync_session_identity(agent: Any, session_id: str) -> None:
793
+ """Refresh persisted Hermes session metadata when Minions switches models."""
794
+ session_db = getattr(agent, "_session_db", None)
795
+ model = string_or_none(getattr(agent, "model", None))
796
+ if not session_db or not session_id or not model:
797
+ return
798
+
799
+ try:
800
+ session_row = session_db.get_session(session_id)
801
+ except Exception:
802
+ return
803
+ if not session_row:
804
+ return
805
+
806
+ model_config = getattr(agent, "_session_init_model_config", None)
807
+ stored_model = string_or_none(session_row.get("model"))
808
+ if stored_model == model:
809
+ return
810
+
811
+ model_config_json = None
812
+ if model_config:
813
+ try:
814
+ model_config_json = json.dumps(model_config)
815
+ except Exception:
816
+ model_config_json = None
817
+
818
+ execute_write = getattr(session_db, "_execute_write", None)
819
+ if callable(execute_write):
820
+ def _do(conn: Any) -> None:
821
+ if model_config_json is None:
822
+ conn.execute(
823
+ "UPDATE sessions SET model = ?, system_prompt = NULL WHERE id = ?",
824
+ (model, session_id),
825
+ )
826
+ else:
827
+ conn.execute(
828
+ "UPDATE sessions SET model = ?, model_config = ?, system_prompt = NULL WHERE id = ?",
829
+ (model, model_config_json, session_id),
830
+ )
831
+
832
+ try:
833
+ execute_write(_do)
834
+ except Exception:
835
+ return
836
+ else:
837
+ try:
838
+ session_db.update_system_prompt(session_id, None)
839
+ except Exception:
840
+ return
841
+
842
+ try:
843
+ setattr(agent, "_cached_system_prompt", None)
844
+ except Exception:
845
+ pass
846
+
847
+
848
+ def _warm_agent() -> None:
849
+ _create_agent(
850
+ session_id="minions-healthcheck",
851
+ requested_model=None,
852
+ reasoning_effort=None,
853
+ )
854
+
855
+
856
+ def _run_chat(request_id: str, request: dict[str, Any]) -> None:
857
+ settings = request.get("settings") if isinstance(request.get("settings"), dict) else {}
858
+ requested_model = string_or_none(settings.get("model"))
859
+ requested_effort = _normalize_reasoning(settings.get("reasoningEffort"))
860
+
861
+ session_id = string_or_none(request.get("sessionId")) or request_id
862
+ message = request.get("message")
863
+ if not isinstance(message, str) or not message.strip():
864
+ raise WorkerError("Chat request message is required.", code="bad_request")
865
+
866
+ session_db, session_id = open_session(session_id)
867
+ history = load_agent_history(session_db, session_id)
868
+ system_message = request.get("systemMessage")
869
+ if not isinstance(system_message, str):
870
+ system_message = None
871
+
872
+ state = {"text": "", "thinking": ""}
873
+
874
+ def on_text_delta(text: Any) -> None:
875
+ if text is None:
876
+ return
877
+ chunk = str(text)
878
+ state["text"] += chunk
879
+ _send({"id": request_id, "type": "text_delta", "content": chunk})
880
+
881
+ def on_reasoning_delta(text: Any) -> None:
882
+ if text is None:
883
+ return
884
+ chunk = str(text)
885
+ if not chunk:
886
+ return
887
+ state["thinking"] += chunk
888
+ _send({"id": request_id, "type": "thinking_delta", "content": chunk})
889
+
890
+ def on_tool_progress(*args: Any, **kwargs: Any) -> None:
891
+ event_type = None
892
+ name = None
893
+ preview = None
894
+ tool_args = None
895
+
896
+ if len(args) >= 4:
897
+ event_type, name, preview, tool_args = args[:4]
898
+ elif len(args) == 3:
899
+ name, preview, tool_args = args
900
+ event_type = "tool.started"
901
+ elif len(args) == 2:
902
+ event_type, name = args
903
+ elif len(args) == 1:
904
+ name = args[0]
905
+ event_type = "tool.started"
906
+
907
+ tool_name = str(name or "tool")
908
+ if event_type in {None, "tool.started"}:
909
+ _send({
910
+ "id": request_id,
911
+ "type": "tool_progress",
912
+ "tool": tool_name,
913
+ "status": "running",
914
+ "label": str(preview) if preview else None,
915
+ })
916
+ return
917
+
918
+ if event_type == "tool.completed":
919
+ _send({
920
+ "id": request_id,
921
+ "type": "tool_progress",
922
+ "tool": tool_name,
923
+ "status": "error" if kwargs.get("is_error") else "completed",
924
+ "duration": kwargs.get("duration"),
925
+ "label": str(preview) if preview else None,
926
+ })
927
+
928
+ agent = _create_agent(
929
+ session_id=session_id,
930
+ requested_model=requested_model,
931
+ reasoning_effort=requested_effort,
932
+ callbacks={
933
+ "stream_delta_callback": on_text_delta,
934
+ "reasoning_callback": on_reasoning_delta,
935
+ "tool_progress_callback": on_tool_progress,
936
+ },
937
+ )
938
+ _sync_session_identity(agent, session_id)
939
+ task_id = string_or_none(request.get("taskId")) or session_id
940
+ task_title = string_or_none(request.get("taskTitle")) or task_id
941
+ session_tokens = None
942
+ clear_session_vars = None
943
+ try:
944
+ from gateway.session_context import set_session_vars, clear_session_vars as _clear_session_vars
945
+
946
+ clear_session_vars = _clear_session_vars
947
+ session_tokens = set_session_vars(
948
+ platform="minions",
949
+ chat_id=task_id,
950
+ chat_name=task_title,
951
+ session_key=session_id,
952
+ )
953
+ except Exception:
954
+ session_tokens = None
955
+
956
+ try:
957
+ result = agent.run_conversation(
958
+ user_message=message,
959
+ system_message=system_message,
960
+ conversation_history=history,
961
+ task_id=session_id,
962
+ )
963
+ finally:
964
+ if session_tokens is not None and clear_session_vars is not None:
965
+ try:
966
+ clear_session_vars(session_tokens)
967
+ except Exception:
968
+ pass
969
+
970
+ final_text = str(result.get("final_response") or "")
971
+ if final_text and not state["text"]:
972
+ _send({"id": request_id, "type": "text_delta", "content": final_text})
973
+ if result.get("last_reasoning") and not state["thinking"]:
974
+ _send({"id": request_id, "type": "thinking_delta", "content": str(result["last_reasoning"])})
975
+
976
+ context_engine = getattr(agent, "context_compressor", None)
977
+ context_used = int(result.get("last_prompt_tokens") or 0)
978
+ context_window = int(getattr(context_engine, "context_length", 0) or 0)
979
+ context = None
980
+ if context_used > 0 and context_window > 0:
981
+ context = {
982
+ "used_tokens": context_used,
983
+ "window_tokens": context_window,
984
+ }
985
+ _send({"id": request_id, "type": "done", "sessionId": getattr(agent, "session_id", None) or session_id, "context": context})
986
+
987
+
988
+ def _run_chat_thread(request_id: str, request: dict[str, Any], task_key: str) -> None:
989
+ done_sent = False
990
+ acquired = False
991
+ try:
992
+ AGENT_SEMAPHORE.acquire()
993
+ acquired = True
994
+ _run_chat(request_id, request)
995
+ done_sent = True
996
+ except Exception as exc:
997
+ _send_error(request_id, exc)
998
+ finally:
999
+ if not done_sent:
1000
+ _send({
1001
+ "id": request_id,
1002
+ "type": "done",
1003
+ "sessionId": string_or_none(request.get("sessionId")) or request_id,
1004
+ })
1005
+ if acquired:
1006
+ AGENT_SEMAPHORE.release()
1007
+ _clear_task_active(task_key, request_id)
1008
+
1009
+
1010
+ def _run_one_shot_agent(label: str, system_message: str, user_message: str) -> str:
1011
+ """Run a throwaway zero-reasoning agent turn and return its raw text response."""
1012
+ agent = _create_agent(
1013
+ session_id=f"minions-{label}-{uuid.uuid4().hex[:8]}",
1014
+ requested_model=None,
1015
+ reasoning_effort="none",
1016
+ )
1017
+ result = agent.run_conversation(
1018
+ user_message=user_message,
1019
+ system_message=system_message,
1020
+ conversation_history=[],
1021
+ )
1022
+ return str(result.get("final_response") or "")
1023
+
1024
+
1025
+ def _submit_background_agent_request(
1026
+ request_id: str,
1027
+ request: dict[str, Any],
1028
+ *,
1029
+ name_prefix: str,
1030
+ handler: Callable[[dict[str, Any]], dict[str, Any]],
1031
+ ) -> None:
1032
+ def runner() -> None:
1033
+ acquired = False
1034
+ try:
1035
+ AGENT_SEMAPHORE.acquire()
1036
+ acquired = True
1037
+ _result(request_id, handler(request))
1038
+ except Exception as exc:
1039
+ _send_error(request_id, exc)
1040
+ finally:
1041
+ if acquired:
1042
+ AGENT_SEMAPHORE.release()
1043
+
1044
+ threading.Thread(
1045
+ target=runner,
1046
+ daemon=True,
1047
+ name=f"{name_prefix}-{request_id[:8]}",
1048
+ ).start()
1049
+
1050
+
1051
+ def _judge_completion(request: dict[str, Any]) -> dict[str, Any]:
1052
+ """Evaluate whether the agent's response indicates task completion."""
1053
+ task_title = string_or_none(request.get("taskTitle")) or ""
1054
+ task_description = string_or_none(request.get("taskDescription")) or ""
1055
+ response_text = string_or_none(request.get("responseText")) or ""
1056
+
1057
+ if not response_text:
1058
+ return {"done": False, "reason": "empty response"}
1059
+
1060
+ response_text = truncate_with_ellipsis(response_text, 4000)
1061
+
1062
+ task_context = task_title
1063
+ if task_description:
1064
+ task_context += f"\n{task_description}"
1065
+
1066
+ judge_system = (
1067
+ "You are a task completion judge. "
1068
+ "Respond ONLY with a JSON object, nothing else. "
1069
+ "Do not use any tools."
1070
+ )
1071
+
1072
+ judge_prompt = (
1073
+ f"Task:\n{task_context}\n\n"
1074
+ f"Agent's most recent response:\n{response_text}\n\n"
1075
+ 'Has the agent completed the task? Respond with: {"done": true/false, "reason": "one sentence"}\n'
1076
+ "- done=true ONLY when the response clearly delivers the final result or confirms completion\n"
1077
+ "- done=false for: questions, partial progress, clarification requests, errors, or ongoing work"
1078
+ )
1079
+
1080
+ text = _run_one_shot_agent("judge", judge_system, judge_prompt)
1081
+ json_match = re.search(r"\{[^{}]*\}", text)
1082
+ if json_match:
1083
+ try:
1084
+ parsed = json.loads(json_match.group())
1085
+ return {
1086
+ "done": bool(parsed.get("done", False)),
1087
+ "reason": str(parsed.get("reason", "")),
1088
+ }
1089
+ except (json.JSONDecodeError, ValueError):
1090
+ pass
1091
+
1092
+ return {"done": False, "reason": "unparseable response"}
1093
+
1094
+
1095
+ def _clean_generated_title(raw: str) -> str:
1096
+ """Sanitize the LLM's title output: strip quotes, trailing punctuation, cap length."""
1097
+ stripped = raw.strip()
1098
+ if not stripped:
1099
+ return ""
1100
+ title = stripped.splitlines()[0].strip()
1101
+ # Strip wrapping quotes/backticks the model often adds (handles nested quotes)
1102
+ while len(title) >= 2 and title[0] in {'"', "'", "`"} and title[-1] == title[0]:
1103
+ title = title[1:-1].strip()
1104
+ title = title.rstrip(".!?,;:")
1105
+ return truncate_with_ellipsis(title, 60)
1106
+
1107
+
1108
+ def _generate_title(request: dict[str, Any]) -> dict[str, Any]:
1109
+ """Generate a short descriptive title for a task from its initial message."""
1110
+ description = string_or_none(request.get("description")) or ""
1111
+ if not description:
1112
+ return {"title": ""}
1113
+
1114
+ description = truncate_with_ellipsis(description, 2000)
1115
+
1116
+ title_system = (
1117
+ "You generate short, descriptive titles for tasks. "
1118
+ "Reply with ONLY the title text — no quotes, no preamble, no trailing punctuation. "
1119
+ "Do not use any tools."
1120
+ )
1121
+
1122
+ title_prompt = (
1123
+ f"Write a concise 3-7 word title for this task:\n\n{description}\n\n"
1124
+ "Reply with only the title."
1125
+ )
1126
+
1127
+ text = _run_one_shot_agent("title", title_system, title_prompt)
1128
+ return {"title": _clean_generated_title(text)}
1129
+
1130
+
1131
+ def _submit_chat_request(request_id: str, request: dict[str, Any]) -> None:
1132
+ task_key = _task_key_for(request)
1133
+ if not _try_mark_task_active(task_key, request_id):
1134
+ _send_error(
1135
+ request_id,
1136
+ WorkerError(
1137
+ "This task is already running. Wait for the current turn to finish, then retry.",
1138
+ code="task_busy",
1139
+ ),
1140
+ )
1141
+ _send({
1142
+ "id": request_id,
1143
+ "type": "done",
1144
+ "sessionId": string_or_none(request.get("sessionId")) or request_id,
1145
+ })
1146
+ return
1147
+
1148
+ thread = threading.Thread(
1149
+ target=_run_chat_thread,
1150
+ args=(request_id, request, task_key),
1151
+ daemon=True,
1152
+ name=f"agent-{request_id[:8]}",
1153
+ )
1154
+ thread.start()
1155
+
1156
+
1157
+ def _handle_request(request: dict[str, Any]) -> None:
1158
+ request_id = str(request.get("id") or "")
1159
+ if not request_id:
1160
+ return
1161
+
1162
+ request_type = request.get("type")
1163
+ try:
1164
+ if request_type == "health":
1165
+ _warm_agent()
1166
+ _result(request_id, {
1167
+ "ok": True,
1168
+ "agentDir": str(_AGENT_DIR) if _AGENT_DIR else None,
1169
+ "python": sys.executable,
1170
+ })
1171
+ elif request_type == "settings.get":
1172
+ _result(request_id, _defaults_from_config())
1173
+ elif request_type == "settings.set":
1174
+ _result(request_id, _set_defaults(request))
1175
+ elif request_type == "models.list":
1176
+ _result(request_id, _list_models())
1177
+ elif request_type == "cron.jobs.list":
1178
+ _result(request_id, list_cron_jobs(bool(request.get("includeDisabled"))))
1179
+ elif request_type == "cron.jobs.get":
1180
+ _result(request_id, get_cron_job(request.get("jobId")))
1181
+ elif request_type == "cron.jobs.runs":
1182
+ _result(request_id, list_cron_runs(request.get("jobId"), request.get("limit", 20)))
1183
+ elif request_type == "cron.jobs.run.content":
1184
+ _result(request_id, get_cron_run_content(request.get("jobId"), request.get("runId")))
1185
+ elif request_type == "cron.jobs.pause":
1186
+ _result(request_id, pause_cron_job(request.get("jobId"), request.get("reason")))
1187
+ elif request_type == "cron.jobs.resume":
1188
+ _result(request_id, resume_cron_job(request.get("jobId")))
1189
+ elif request_type == "cron.jobs.run":
1190
+ _result(request_id, trigger_cron_job(request.get("jobId")))
1191
+ elif request_type == "cron.jobs.remove":
1192
+ _result(request_id, remove_cron_job(request.get("jobId")))
1193
+ elif request_type == "cron.tick":
1194
+ _result(request_id, {"executed": tick_cron()})
1195
+ elif request_type == "session.messages.get":
1196
+ _result(request_id, project_session_messages(request.get("sessionId"), request.get("taskId")))
1197
+ elif request_type == "session.get":
1198
+ _result(request_id, project_session_metadata(request.get("sessionId")))
1199
+ elif request_type == "chat":
1200
+ _submit_chat_request(request_id, request)
1201
+ elif request_type == "judge.completion":
1202
+ _submit_background_agent_request(request_id, request, name_prefix="judge", handler=_judge_completion)
1203
+ elif request_type == "title.generate":
1204
+ _submit_background_agent_request(request_id, request, name_prefix="title", handler=_generate_title)
1205
+ else:
1206
+ raise WorkerError(f"Unknown request type: {request_type}", code="bad_request")
1207
+ except Exception as exc:
1208
+ _send_error(request_id, exc)
1209
+ if request_type == "chat":
1210
+ _send({
1211
+ "id": request_id,
1212
+ "type": "done",
1213
+ "sessionId": string_or_none(request.get("sessionId")) or request_id,
1214
+ })
1215
+
1216
+
1217
+ def _run_loop() -> None:
1218
+ for raw_line in sys.stdin:
1219
+ line = raw_line.strip()
1220
+ if not line:
1221
+ continue
1222
+ try:
1223
+ request = json.loads(line)
1224
+ if not isinstance(request, dict):
1225
+ continue
1226
+ _handle_request(request)
1227
+ except Exception as exc:
1228
+ print(f"[hermes-worker] failed to handle request: {exc}", file=sys.stderr, flush=True)
1229
+ traceback.print_exc(file=sys.stderr)
1230
+
1231
+
1232
+ def _self_test() -> int:
1233
+ try:
1234
+ _ensure_imports()
1235
+ cfg = _load_config()
1236
+ payload = {
1237
+ "ok": True,
1238
+ "agentDir": str(_AGENT_DIR) if _AGENT_DIR else None,
1239
+ "python": sys.executable,
1240
+ "defaults": _defaults_from_config(cfg),
1241
+ }
1242
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
1243
+ return 0
1244
+ except Exception as exc:
1245
+ print(json.dumps({"ok": False, "error": _error_payload(exc)}, ensure_ascii=False, indent=2))
1246
+ return 1
1247
+
1248
+
1249
+ def main() -> int:
1250
+ parser = argparse.ArgumentParser()
1251
+ parser.add_argument("--self-test", action="store_true")
1252
+ args = parser.parse_args()
1253
+
1254
+ os.environ.setdefault("HERMES_QUIET", "1")
1255
+ os.environ.setdefault("HERMES_YOLO_MODE", "1")
1256
+
1257
+ if args.self_test:
1258
+ return _self_test()
1259
+
1260
+ sys.stdout = sys.stderr
1261
+ start_cron_ticker()
1262
+ try:
1263
+ _run_loop()
1264
+ except KeyboardInterrupt:
1265
+ pass
1266
+ return 0
1267
+
1268
+
1269
+ if __name__ == "__main__":
1270
+ raise SystemExit(main())