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.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/bin/minions.mjs +19 -0
- package/dist/server/client/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/server/client/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/server/client/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/server/client/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/server/client/dist/assets/angular-html-CU67Zn6k.js +1 -0
- package/dist/server/client/dist/assets/angular-ts-BwZT4LLn.js +1 -0
- package/dist/server/client/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/server/client/dist/assets/apex-D8_7TLub.js +1 -0
- package/dist/server/client/dist/assets/apl-dKokRX4l.js +1 -0
- package/dist/server/client/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/server/client/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/server/client/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/server/client/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/server/client/dist/assets/astro-CbQHKStN.js +1 -0
- package/dist/server/client/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/server/client/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/server/client/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/server/client/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/server/client/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/server/client/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/server/client/dist/assets/bat-BkioyH1T.js +1 -0
- package/dist/server/client/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/server/client/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/server/client/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/server/client/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/server/client/dist/assets/bird2-DPOp833l.js +1 -0
- package/dist/server/client/dist/assets/blade-D4QpJJKB.js +1 -0
- package/dist/server/client/dist/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/server/client/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/server/client/dist/assets/c3-eo99z4R2.js +1 -0
- package/dist/server/client/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/server/client/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/server/client/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/server/client/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/server/client/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/server/client/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/server/client/dist/assets/clarity-D53aC0YG.js +1 -0
- package/dist/server/client/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/server/client/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/server/client/dist/assets/cobol-nwyudZeR.js +1 -0
- package/dist/server/client/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/server/client/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/server/client/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/server/client/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/server/client/dist/assets/coq-DkFqJrB1.js +1 -0
- package/dist/server/client/dist/assets/cpp-CofmeUqb.js +1 -0
- package/dist/server/client/dist/assets/crystal-tKQVLTB8.js +1 -0
- package/dist/server/client/dist/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/server/client/dist/assets/css-DPfMkruS.js +1 -0
- package/dist/server/client/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/server/client/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/server/client/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/server/client/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/server/client/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/server/client/dist/assets/dart-CF10PKvl.js +1 -0
- package/dist/server/client/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/server/client/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/server/client/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/server/client/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/server/client/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/server/client/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/server/client/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/server/client/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/server/client/dist/assets/edge-BkV0erSs.js +1 -0
- package/dist/server/client/dist/assets/elixir-CDX3lj18.js +1 -0
- package/dist/server/client/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/server/client/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
- package/dist/server/client/dist/assets/erb-B12qg9BL.js +1 -0
- package/dist/server/client/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/server/client/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/server/client/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/server/client/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/server/client/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/server/client/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/server/client/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/server/client/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/server/client/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/server/client/dist/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/server/client/dist/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/server/client/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/server/client/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/server/client/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/server/client/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/server/client/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/server/client/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/server/client/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/server/client/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/server/client/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/server/client/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/server/client/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/server/client/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/server/client/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/server/client/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
- package/dist/server/client/dist/assets/glimmer-ts-U6CK756n.js +1 -0
- package/dist/server/client/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/server/client/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/server/client/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/server/client/dist/assets/go-CxLEBnE3.js +1 -0
- package/dist/server/client/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/server/client/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/server/client/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/server/client/dist/assets/hack-CaT9iCJl.js +1 -0
- package/dist/server/client/dist/assets/haml-B8DHNrY2.js +1 -0
- package/dist/server/client/dist/assets/handlebars-BL8al0AC.js +1 -0
- package/dist/server/client/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/server/client/dist/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/server/client/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/server/client/dist/assets/highlighted-body-TPN3WLV5-Dmyr2DoJ.js +1 -0
- package/dist/server/client/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/server/client/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/server/client/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/server/client/dist/assets/horizon-bright-Cn-bp-IR.js +1 -0
- package/dist/server/client/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/server/client/dist/assets/html-GMplVEZG.js +1 -0
- package/dist/server/client/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
- package/dist/server/client/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/server/client/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/server/client/dist/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/server/client/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/server/client/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/server/client/dist/assets/index-BB7507W7.css +1 -0
- package/dist/server/client/dist/assets/index-hLQDnL9J.js +694 -0
- package/dist/server/client/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/server/client/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/server/client/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/server/client/dist/assets/jinja-4LBKfQ-Z.js +1 -0
- package/dist/server/client/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/server/client/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/server/client/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/server/client/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/server/client/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/server/client/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/server/client/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/server/client/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/server/client/dist/assets/julia-CxzCAyBv.js +1 -0
- package/dist/server/client/dist/assets/just-Cw27pwNe.js +1 -0
- package/dist/server/client/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/server/client/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/server/client/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/server/client/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/server/client/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/server/client/dist/assets/kusto-DZf3V79B.js +1 -0
- package/dist/server/client/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/server/client/dist/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/server/client/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/server/client/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/server/client/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/server/client/dist/assets/liquid-DYVedYrR.js +1 -0
- package/dist/server/client/dist/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/server/client/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/server/client/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/server/client/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/server/client/dist/assets/luau-C-HG3fhB.js +1 -0
- package/dist/server/client/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/server/client/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/server/client/dist/assets/marko-CnJfTvn9.js +1 -0
- package/dist/server/client/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/server/client/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/server/client/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/server/client/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/server/client/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/server/client/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/server/client/dist/assets/mdc-BMNejdWA.js +1 -0
- package/dist/server/client/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/server/client/dist/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/server/client/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/server/client/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/server/client/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/server/client/dist/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/server/client/dist/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/server/client/dist/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/server/client/dist/assets/move-IF9eRakj.js +1 -0
- package/dist/server/client/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/server/client/dist/assets/nextflow-Zz6hmt5N.js +1 -0
- package/dist/server/client/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
- package/dist/server/client/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/server/client/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/server/client/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/server/client/dist/assets/nim-CVrawwO9.js +1 -0
- package/dist/server/client/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/server/client/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/server/client/dist/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/server/client/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/server/client/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/server/client/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/server/client/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/server/client/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/server/client/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/server/client/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/server/client/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/server/client/dist/assets/perl-C0TMdlhV.js +1 -0
- package/dist/server/client/dist/assets/php-Dhbhpdrm.js +1 -0
- package/dist/server/client/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/server/client/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/server/client/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/server/client/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/server/client/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/server/client/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/server/client/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/server/client/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/server/client/dist/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/server/client/dist/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/server/client/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/server/client/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/server/client/dist/assets/pug-CGlum2m_.js +1 -0
- package/dist/server/client/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/server/client/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/server/client/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/server/client/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/server/client/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/server/client/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/server/client/dist/assets/r-Dspwwk_N.js +1 -0
- package/dist/server/client/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/server/client/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/server/client/dist/assets/razor-Uh8Bk_45.js +1 -0
- package/dist/server/client/dist/assets/red-bN70gL4F.js +1 -0
- package/dist/server/client/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/server/client/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/server/client/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/server/client/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/server/client/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/server/client/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/server/client/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/server/client/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/server/client/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/server/client/dist/assets/rst-BrH8l1NY.js +1 -0
- package/dist/server/client/dist/assets/ruby-Dw2BHqvy.js +1 -0
- package/dist/server/client/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/server/client/dist/assets/sas-cz2c8ADy.js +1 -0
- package/dist/server/client/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/server/client/dist/assets/scala-C151Ov-r.js +1 -0
- package/dist/server/client/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/server/client/dist/assets/scss-OYdSNvt2.js +1 -0
- package/dist/server/client/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/server/client/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/server/client/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/server/client/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/server/client/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/server/client/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/server/client/dist/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/server/client/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/server/client/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/server/client/dist/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/server/client/dist/assets/solidity-rGO070M0.js +1 -0
- package/dist/server/client/dist/assets/soy-Brmx7dQM.js +1 -0
- package/dist/server/client/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/server/client/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/server/client/dist/assets/sql-BLtJtn59.js +1 -0
- package/dist/server/client/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/server/client/dist/assets/stata-BH5u7GGu.js +1 -0
- package/dist/server/client/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/server/client/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/server/client/dist/assets/svelte-C_ipcX3V.js +1 -0
- package/dist/server/client/dist/assets/swift-D82vCrfD.js +1 -0
- package/dist/server/client/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/server/client/dist/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/server/client/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/server/client/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/server/client/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/server/client/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/server/client/dist/assets/templ-P3uqSqPl.js +1 -0
- package/dist/server/client/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/server/client/dist/assets/tex-idrVyKtj.js +1 -0
- package/dist/server/client/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/server/client/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/server/client/dist/assets/ts-tags-zn1MmPIZ.js +1 -0
- package/dist/server/client/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/server/client/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/server/client/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/server/client/dist/assets/twig-DNn4PbVi.js +1 -0
- package/dist/server/client/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/server/client/dist/assets/typespec-BGHnOYBU.js +1 -0
- package/dist/server/client/dist/assets/typst-DHCkPAjA.js +1 -0
- package/dist/server/client/dist/assets/v-BcVCzyr7.js +1 -0
- package/dist/server/client/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/server/client/dist/assets/vb-D17OF-Vu.js +1 -0
- package/dist/server/client/dist/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/server/client/dist/assets/vesper-DU1UobuO.js +1 -0
- package/dist/server/client/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/server/client/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/server/client/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/server/client/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/server/client/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/server/client/dist/assets/vue-DN_0RTcg.js +1 -0
- package/dist/server/client/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/server/client/dist/assets/vue-vine-CQOfvN7w.js +1 -0
- package/dist/server/client/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/server/client/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/server/client/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/server/client/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/server/client/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/server/client/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/server/client/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/server/client/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/server/client/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/server/client/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/server/client/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/server/client/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/server/client/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/server/client/dist/favicon.ico +0 -0
- package/dist/server/client/dist/index.html +17 -0
- package/dist/server/client/dist/logo.png +0 -0
- package/dist/server/server/adapters/hermes-worker.d.ts +37 -0
- package/dist/server/server/adapters/hermes-worker.js +525 -0
- package/dist/server/server/adapters/types.d.ts +39 -0
- package/dist/server/server/adapters/types.js +1 -0
- package/dist/server/server/adapters/worker-protocol.d.ts +141 -0
- package/dist/server/server/adapters/worker-protocol.js +1 -0
- package/dist/server/server/agent-settings.d.ts +12 -0
- package/dist/server/server/agent-settings.js +56 -0
- package/dist/server/server/app.d.ts +5 -0
- package/dist/server/server/app.js +41 -0
- package/dist/server/server/db/index.d.ts +2 -0
- package/dist/server/server/db/index.js +14 -0
- package/dist/server/server/db/queries.d.ts +22 -0
- package/dist/server/server/db/queries.js +116 -0
- package/dist/server/server/db/schema.sql +16 -0
- package/dist/server/server/errors.d.ts +3 -0
- package/dist/server/server/errors.js +12 -0
- package/dist/server/server/events.d.ts +7 -0
- package/dist/server/server/events.js +50 -0
- package/dist/server/server/frontend.d.ts +4 -0
- package/dist/server/server/frontend.js +32 -0
- package/dist/server/server/index.d.ts +2 -0
- package/dist/server/server/index.js +63 -0
- package/dist/server/server/live-chat.d.ts +17 -0
- package/dist/server/server/live-chat.js +217 -0
- package/dist/server/server/paths.d.ts +7 -0
- package/dist/server/server/paths.js +39 -0
- package/dist/server/server/prompts/task-agent.d.ts +1 -0
- package/dist/server/server/prompts/task-agent.js +26 -0
- package/dist/server/server/routes/agent.d.ts +4 -0
- package/dist/server/server/routes/agent.js +94 -0
- package/dist/server/server/routes/chat.d.ts +1 -0
- package/dist/server/server/routes/chat.js +177 -0
- package/dist/server/server/routes/cron.d.ts +4 -0
- package/dist/server/server/routes/cron.js +109 -0
- package/dist/server/server/routes/files.d.ts +1 -0
- package/dist/server/server/routes/files.js +543 -0
- package/dist/server/server/routes/skills.d.ts +1 -0
- package/dist/server/server/routes/skills.js +13 -0
- package/dist/server/server/routes/tasks.d.ts +1 -0
- package/dist/server/server/routes/tasks.js +107 -0
- package/dist/server/server/skills/catalog.d.ts +21 -0
- package/dist/server/server/skills/catalog.js +160 -0
- package/dist/server/server/workers/hermes_cron.py +241 -0
- package/dist/server/server/workers/hermes_sessions.py +264 -0
- package/dist/server/server/workers/hermes_worker.py +1270 -0
- package/dist/server/server/workers/hermes_worker_utils.py +39 -0
- package/dist/server/shared/types.d.ts +211 -0
- package/dist/server/shared/types.js +2 -0
- package/package.json +74 -0
- 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())
|