openmausbot 0.1.55
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 +202 -0
- package/README.md +14 -0
- package/cli.js +2 -0
- package/dist/app-icon.svg +29 -0
- package/dist/assets/abap-BdImnpbu.js +1 -0
- package/dist/assets/actionscript-3-B3316cI-.js +1 -0
- package/dist/assets/ada-bCR0ucgS.js +1 -0
- package/dist/assets/ahk-CsyLZFj1.js +1 -0
- package/dist/assets/ahk2-8Zs4aa1G.js +1 -0
- package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/assets/angular-html-DA-rfuFy.js +1 -0
- package/dist/assets/angular-ts-BrjP3tb8.js +1 -0
- package/dist/assets/apache-Pmp26Uib.js +1 -0
- package/dist/assets/apex-DhZFqWV2.js +1 -0
- package/dist/assets/apl-CORt7UWP.js +1 -0
- package/dist/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/assets/ara-BRHolxvo.js +1 -0
- package/dist/assets/asciidoc-CSVQ5wI8.js +1 -0
- package/dist/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/assets/astro-HNnZUWAn.js +1 -0
- package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/assets/awk-DMzUqQB5.js +1 -0
- package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/assets/bat-CickPsom.js +1 -0
- package/dist/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/assets/berry-uYugtg8r.js +1 -0
- package/dist/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/assets/bird2-Bx8U0n9b.js +1 -0
- package/dist/assets/blade-2xfisSek.js +1 -0
- package/dist/assets/bsl-DlhNcFeZ.js +1 -0
- package/dist/assets/c-BIGW1oBm.js +1 -0
- package/dist/assets/c3-Dp5svz6Z.js +1 -0
- package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
- package/dist/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
- package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
- package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
- package/dist/assets/chapel-DTp_pixX.js +1 -0
- package/dist/assets/clarity-Dn5IMItf.js +1 -0
- package/dist/assets/clojure-P80f7IUj.js +1 -0
- package/dist/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/assets/cobol-nBiQ_Alo.js +1 -0
- package/dist/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/assets/coq-C7JzOVbR.js +1 -0
- package/dist/assets/cpp-BMRokrvK.js +1 -0
- package/dist/assets/crystal-DGywbUpC.js +1 -0
- package/dist/assets/csharp-DSvCPggb.js +1 -0
- package/dist/assets/css-CLj8gQPS.js +1 -0
- package/dist/assets/csv-fuZLfV_i.js +1 -0
- package/dist/assets/cue-D82EKSYY.js +1 -0
- package/dist/assets/cypher-COkxafJQ.js +1 -0
- package/dist/assets/d-85-TOEBH.js +1 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/dart-bE4Kk8sk.js +1 -0
- package/dist/assets/dax-CEL-wOlO.js +1 -0
- package/dist/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/assets/diff-D97Zzqfu.js +1 -0
- package/dist/assets/docker-BcOcwvcX.js +1 -0
- package/dist/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/assets/edge-FbVlp4U3.js +1 -0
- package/dist/assets/elixir-CkH2-t6x.js +1 -0
- package/dist/assets/elm-DbKCFpqz.js +1 -0
- package/dist/assets/emacs-lisp-C_m_b--Z.js +1 -0
- package/dist/assets/erb-DXfck5VN.js +1 -0
- package/dist/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/assets/fennel-BYunw83y.js +1 -0
- package/dist/assets/fish-BvzEVeQv.js +1 -0
- package/dist/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/assets/gdresource-TyuKm33G.js +1 -0
- package/dist/assets/gdscript-DqcFQ5yU.js +1 -0
- package/dist/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/assets/genie-D0YGMca9.js +1 -0
- package/dist/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/assets/gleam-BspZqrRM.js +1 -0
- package/dist/assets/glimmer-js-ByusRIyA.js +1 -0
- package/dist/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/dist/assets/glsl-DplSGwfg.js +1 -0
- package/dist/assets/gn-n2N0HUVH.js +1 -0
- package/dist/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/assets/go-C27-OAKa.js +1 -0
- package/dist/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/assets/hack-BWmVpMyf.js +1 -0
- package/dist/assets/haml-D5jkg6IW.js +1 -0
- package/dist/assets/handlebars-BpdQsYii.js +1 -0
- package/dist/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/assets/haxe-CfZj7gIn.js +1 -0
- package/dist/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/assets/hjson-D5-asLiD.js +1 -0
- package/dist/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/dist/assets/houston-DnULxvSX.js +1 -0
- package/dist/assets/html-derivative-DlHx6ybY.js +1 -0
- package/dist/assets/html-pp8916En.js +1 -0
- package/dist/assets/http-jrhK8wxY.js +1 -0
- package/dist/assets/hurl-irOxFIW8.js +1 -0
- package/dist/assets/hxml-2-FPmUDs.js +1 -0
- package/dist/assets/hy-DFXneXwc.js +1 -0
- package/dist/assets/imba-DGztddWO.js +1 -0
- package/dist/assets/index-B9rLIY81.js +263 -0
- package/dist/assets/index-CJBrtDbC.css +1 -0
- package/dist/assets/index-D-u0LYsV.js +153 -0
- package/dist/assets/ini-BEwlwnbL.js +1 -0
- package/dist/assets/java-CylS5w8V.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/jinja-f2NsQr07.js +1 -0
- package/dist/assets/jison-wvAkD_A8.js +1 -0
- package/dist/assets/json-Cp-IABpG.js +1 -0
- package/dist/assets/json5-C9tS-k6U.js +1 -0
- package/dist/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/assets/julia-5Bft2YPA.js +1 -0
- package/dist/assets/just-Cwhn7H3k.js +1 -0
- package/dist/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
- package/dist/assets/kanagawa-lotus-C3DzagqV.js +1 -0
- package/dist/assets/kanagawa-wave-DFGoQZhC.js +1 -0
- package/dist/assets/kdl-DV7GczEv.js +1 -0
- package/dist/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/assets/kusto-wEQ09or8.js +1 -0
- package/dist/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/assets/latex-D5pSuvFb.js +1 -0
- package/dist/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/assets/less-B1dDrJ26.js +1 -0
- package/dist/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/assets/liquid-C0sCDyMI.js +1 -0
- package/dist/assets/llvm-BZoOZj88.js +1 -0
- package/dist/assets/log-2UxHyX5q.js +1 -0
- package/dist/assets/logo-BtOb2qkB.js +1 -0
- package/dist/assets/lua-BaeVxFsk.js +1 -0
- package/dist/assets/luau-BnpPk5vE.js +1 -0
- package/dist/assets/make-CHLpvVh8.js +1 -0
- package/dist/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/assets/marko-DjSrsDqO.js +1 -0
- package/dist/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/assets/matlab-D7o27uSR.js +1 -0
- package/dist/assets/mdc-D1_yUvq7.js +1 -0
- package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/assets/mermaid-CQcHuHx7.js +1 -0
- package/dist/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/assets/min-light-CTRr51gU.js +1 -0
- package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/assets/mojo-DJz3ZmWd.js +1 -0
- package/dist/assets/monokai-BRVnQi9A.js +1 -0
- package/dist/assets/moonbit-CHtswR0a.js +1 -0
- package/dist/assets/move-el3G9tDJ.js +1 -0
- package/dist/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/assets/nextflow-C-mBbutL.js +1 -0
- package/dist/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/dist/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/assets/nim-BIad80T-.js +1 -0
- package/dist/assets/nix-CwoSXNpI.js +1 -0
- package/dist/assets/nord-Ddv68eIx.js +1 -0
- package/dist/assets/nsis-BlV79W_Q.js +1 -0
- package/dist/assets/nushell-D3jzshHO.js +1 -0
- package/dist/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/assets/odin-BBf5iR-q.js +1 -0
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/assets/org-DM6o9KBp.js +1 -0
- package/dist/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/assets/perl-B9cMNwum.js +1 -0
- package/dist/assets/php-Csjmro_R.js +1 -0
- package/dist/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/assets/po-BTJTHyun.js +1 -0
- package/dist/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/assets/polar-C0HS_06l.js +1 -0
- package/dist/assets/postcss-CXtECtnM.js +1 -0
- package/dist/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/assets/powershell-BmBUJMz7.js +1 -0
- package/dist/assets/prisma-Vru482bI.js +1 -0
- package/dist/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/assets/pug-DKIMFp6K.js +1 -0
- package/dist/assets/puppet-BMWR74SV.js +1 -0
- package/dist/assets/purescript-CklMAg4u.js +1 -0
- package/dist/assets/python-B6aJPvgy.js +1 -0
- package/dist/assets/qml-3beO22l8.js +1 -0
- package/dist/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/assets/qss-IeuSbFQv.js +1 -0
- package/dist/assets/r-Cf5RLm7j.js +1 -0
- package/dist/assets/racket-BqYA7rlc.js +1 -0
- package/dist/assets/raku-DXvB9xmW.js +1 -0
- package/dist/assets/razor-BjBPvh-w.js +1 -0
- package/dist/assets/rbs-CpoqiR4B.js +1 -0
- package/dist/assets/red-hvxz__6c.js +1 -0
- package/dist/assets/reg-C-SQnVFl.js +1 -0
- package/dist/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/assets/rel-C3B-1QV4.js +1 -0
- package/dist/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/assets/ron-D8l8udqQ.js +1 -0
- package/dist/assets/rose-pine-5qJOZa0Y.js +1 -0
- package/dist/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
- package/dist/assets/rose-pine-moon-C9pEdX9L.js +1 -0
- package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/assets/rst-bs7f0vWN.js +1 -0
- package/dist/assets/ruby-C0TQ7zu5.js +1 -0
- package/dist/assets/rust-B1yitclQ.js +1 -0
- package/dist/assets/sas-DEy46yEz.js +1 -0
- package/dist/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/assets/scala-CqE71os6.js +1 -0
- package/dist/assets/scheme-C98Dy4si.js +1 -0
- package/dist/assets/scss-D5BDwBP9.js +1 -0
- package/dist/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/assets/smalltalk-BOQMe2GC.js +1 -0
- package/dist/assets/smithy-cds9vsN8.js +1 -0
- package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/assets/solarized-dark-CpvCGNkr.js +1 -0
- package/dist/assets/solarized-light-Dlz6yCKv.js +1 -0
- package/dist/assets/solidity-DijEV5ha.js +1 -0
- package/dist/assets/soy-8wufbnw4.js +1 -0
- package/dist/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/assets/sql-CRqJ_cUM.js +1 -0
- package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/assets/stata-DI20mbqo.js +1 -0
- package/dist/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/assets/surrealql-Cjom0U5J.js +1 -0
- package/dist/assets/svelte-Cy7k_4gC.js +1 -0
- package/dist/assets/swift-C2oV4EkX.js +1 -0
- package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/assets/system-verilog-0hqHdDBg.js +1 -0
- package/dist/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/assets/templ-DhtptRzy.js +1 -0
- package/dist/assets/terraform-BETggiCN.js +1 -0
- package/dist/assets/tex-D96PA37w.js +1 -0
- package/dist/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/assets/toml-vGWfd6FD.js +1 -0
- package/dist/assets/ts-tags-D351s5mN.js +1 -0
- package/dist/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/assets/tsx-COt5Ahok.js +1 -0
- package/dist/assets/turtle-BsS91CYL.js +1 -0
- package/dist/assets/twig-27uCiNez.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/typespec-CAFt9gP4.js +1 -0
- package/dist/assets/typst-BUadGCkm.js +1 -0
- package/dist/assets/v-BGw2Nkan.js +1 -0
- package/dist/assets/vala-CsfeWuGM.js +1 -0
- package/dist/assets/vb-Cu-pLBUe.js +1 -0
- package/dist/assets/verilog-nZwndyjY.js +1 -0
- package/dist/assets/vesper-DRje8inN.js +1 -0
- package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/assets/viml-CJc9bBzg.js +1 -0
- package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/assets/vue-BqiEGhQt.js +1 -0
- package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/assets/vue-vine-BoDAl6tE.js +1 -0
- package/dist/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/assets/wit-5i3qLPDT.js +1 -0
- package/dist/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/assets/yaml-Buea-lGh.js +1 -0
- package/dist/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/assets/zig-VOosw3JB.js +1 -0
- package/dist/index.html +14 -0
- package/dist-server/companion/src/listener.js +187 -0
- package/dist-server/computer-proxy.js +1185 -0
- package/dist-server/connector-proxy.js +186 -0
- package/dist-server/container-mcp.js +15186 -0
- package/dist-server/drivers/agents-proxy.js +887 -0
- package/dist-server/drivers/browser-proxy.js +15037 -0
- package/dist-server/drivers/dweb-proxy.js +175 -0
- package/dist-server/drivers/phone-proxy.js +339 -0
- package/dist-server/drivers/pi-mcp-extension.ts +638 -0
- package/dist-server/index.js +64869 -0
- package/dist-server/local-computer.js +198 -0
- package/dist-server/mcp-server.js +1233 -0
- package/dist-server/openmausbot.js +3671 -0
- package/dist-server/pair-cli.js +3671 -0
- package/dist-server/permission-proxy.js +126 -0
- package/dist-server/prepare-cloudflared.js +253 -0
- package/dist-server/proxy-paths.js +29 -0
- package/dist-server/server/atomic.js +40 -0
- package/dist-server/server/attachments.js +471 -0
- package/dist-server/server/auto-approve.js +162 -0
- package/dist-server/server/auto-review.js +79 -0
- package/dist-server/server/avatar-image.js +144 -0
- package/dist-server/server/bot-cwd.js +29 -0
- package/dist-server/server/bot-directory.js +127 -0
- package/dist-server/server/bot-package.js +268 -0
- package/dist-server/server/bot-profile.js +76 -0
- package/dist-server/server/box-create-idempotency.js +463 -0
- package/dist-server/server/box.js +944 -0
- package/dist-server/server/brand.js +65 -0
- package/dist-server/server/browser-connection.js +187 -0
- package/dist-server/server/browser-lifecycle-cleanup.js +359 -0
- package/dist-server/server/calendar-calls.js +314 -0
- package/dist-server/server/channel-queue.js +70 -0
- package/dist-server/server/checkpoints.js +405 -0
- package/dist-server/server/chief-of-staff.js +49 -0
- package/dist-server/server/claude-update.js +70 -0
- package/dist-server/server/cli.js +524 -0
- package/dist-server/server/cloud-backend.js +30 -0
- package/dist-server/server/comms-visibility.js +109 -0
- package/dist-server/server/composio.js +945 -0
- package/dist-server/server/computer-control.js +149 -0
- package/dist-server/server/computer-observation.js +133 -0
- package/dist-server/server/computer-proxy.js +1061 -0
- package/dist-server/server/config.js +760 -0
- package/dist-server/server/connector-proxy.js +228 -0
- package/dist-server/server/container-computer.js +840 -0
- package/dist-server/server/container-mcp.js +27 -0
- package/dist-server/server/contracts.js +24 -0
- package/dist-server/server/control-client.js +100 -0
- package/dist-server/server/data-dir-lease.js +3 -0
- package/dist-server/server/decision-log.js +106 -0
- package/dist-server/server/delegations.js +628 -0
- package/dist-server/server/drivers/acp/core.js +904 -0
- package/dist-server/server/drivers/acp/cursor.js +365 -0
- package/dist-server/server/drivers/acp/custom.js +51 -0
- package/dist-server/server/drivers/acp/droid.js +225 -0
- package/dist-server/server/drivers/acp/gemini.js +95 -0
- package/dist-server/server/drivers/acp/grok.js +249 -0
- package/dist-server/server/drivers/acp/hermes.js +429 -0
- package/dist-server/server/drivers/acp/kimi.js +559 -0
- package/dist-server/server/drivers/acp/opencode-go.js +370 -0
- package/dist-server/server/drivers/acp/qwen.js +98 -0
- package/dist-server/server/drivers/agents-proxy.js +941 -0
- package/dist-server/server/drivers/antigravity-acp.js +507 -0
- package/dist-server/server/drivers/antigravity-release.js +65 -0
- package/dist-server/server/drivers/antigravity-runtime.js +395 -0
- package/dist-server/server/drivers/antigravity.js +144 -0
- package/dist-server/server/drivers/boxagent.js +245 -0
- package/dist-server/server/drivers/browser-proxy.js +520 -0
- package/dist-server/server/drivers/builtIn.js +36 -0
- package/dist-server/server/drivers/claude.js +1337 -0
- package/dist-server/server/drivers/codex-catalog.js +409 -0
- package/dist-server/server/drivers/codex.js +1116 -0
- package/dist-server/server/drivers/dweb-proxy.js +193 -0
- package/dist-server/server/drivers/grok.js +48 -0
- package/dist-server/server/drivers/grokagent.js +4 -0
- package/dist-server/server/drivers/local-inject.js +388 -0
- package/dist-server/server/drivers/minimax.js +108 -0
- package/dist-server/server/drivers/native.js +21 -0
- package/dist-server/server/drivers/openai-chat.js +240 -0
- package/dist-server/server/drivers/openai-compat.js +143 -0
- package/dist-server/server/drivers/phone-proxy.js +372 -0
- package/dist-server/server/drivers/pi.js +855 -0
- package/dist-server/server/drivers/retry.js +96 -0
- package/dist-server/server/enterprise.js +93 -0
- package/dist-server/server/env-path.js +329 -0
- package/dist-server/server/environment.js +105 -0
- package/dist-server/server/generated-image.js +39 -0
- package/dist-server/server/graceful-shutdown.js +24 -0
- package/dist-server/server/group-goal-run.js +124 -0
- package/dist-server/server/harness/bus.js +86 -0
- package/dist-server/server/harness/registry.js +217 -0
- package/dist-server/server/index.js +11634 -0
- package/dist-server/server/installed-playbooks.js +34 -0
- package/dist-server/server/local-computer.js +278 -0
- package/dist-server/server/local-routing.js +9 -0
- package/dist-server/server/local-vm-idle.js +44 -0
- package/dist-server/server/local-vm-inventory.js +34 -0
- package/dist-server/server/local-vm-lease.js +58 -0
- package/dist-server/server/mcp-bridge.js +251 -0
- package/dist-server/server/mcp-probe.js +153 -0
- package/dist-server/server/mcp-registry.js +130 -0
- package/dist-server/server/member-turn.js +6 -0
- package/dist-server/server/message-db.js +358 -0
- package/dist-server/server/message-file.js +439 -0
- package/dist-server/server/names.js +26 -0
- package/dist-server/server/notify.js +69 -0
- package/dist-server/server/openmaus-status-capsule.js +329 -0
- package/dist-server/server/openmausbot.js +6 -0
- package/dist-server/server/package-export.js +157 -0
- package/dist-server/server/pair-cli.js +7 -0
- package/dist-server/server/peer-approval-key.js +5 -0
- package/dist-server/server/peer-approval.js +182 -0
- package/dist-server/server/peer-provenance.js +44 -0
- package/dist-server/server/peer-roster.js +139 -0
- package/dist-server/server/permission-proxy.js +157 -0
- package/dist-server/server/phone-secret.js +358 -0
- package/dist-server/server/private-screen-capture.js +17 -0
- package/dist-server/server/procs.js +123 -0
- package/dist-server/server/project-scout.js +304 -0
- package/dist-server/server/proxy-paths.js +47 -0
- package/dist-server/server/redact.js +102 -0
- package/dist-server/server/remote-computer.js +170 -0
- package/dist-server/server/repeat-detector.js +48 -0
- package/dist-server/server/replies.js +38 -0
- package/dist-server/server/request-auth.js +327 -0
- package/dist-server/server/room-cwd.js +15 -0
- package/dist-server/server/room-post-budget.js +112 -0
- package/dist-server/server/room-turn-timeout.js +115 -0
- package/dist-server/server/routine-requests.js +871 -0
- package/dist-server/server/routines.js +1111 -0
- package/dist-server/server/schema.js +11 -0
- package/dist-server/server/screen-frame-gate.js +80 -0
- package/dist-server/server/section-context.js +85 -0
- package/dist-server/server/send-idempotency.js +53 -0
- package/dist-server/server/sessions.js +319 -0
- package/dist-server/server/skill-fetch.js +148 -0
- package/dist-server/server/skill-learn.js +61 -0
- package/dist-server/server/skill-library.js +116 -0
- package/dist-server/server/skills.js +1223 -0
- package/dist-server/server/steer-queue.js +114 -0
- package/dist-server/server/store.js +1217 -0
- package/dist-server/server/surface.js +107 -0
- package/dist-server/server/tailscale.js +103 -0
- package/dist-server/server/team-backup.js +183 -0
- package/dist-server/server/team-library.js +205 -0
- package/dist-server/server/team-manifest.js +224 -0
- package/dist-server/server/thread-events.js +226 -0
- package/dist-server/server/tts/elevenlabs.js +92 -0
- package/dist-server/server/tts/index.js +79 -0
- package/dist-server/server/tts/speech-text.js +195 -0
- package/dist-server/server/tts/system-voices.js +74 -0
- package/dist-server/server/tunnel.js +202 -0
- package/dist-server/server/turn-context.js +40 -0
- package/dist-server/server/turn-dispatch-guard.js +152 -0
- package/dist-server/server/turn-images.js +138 -0
- package/dist-server/server/turn-watchdog.js +65 -0
- package/dist-server/server/vps-computer.js +1099 -0
- package/dist-server/server/vps-container-mcp.js +31 -0
- package/dist-server/server/webhook-ingress.js +173 -0
- package/dist-server/server/webhooks.js +491 -0
- package/dist-server/server/workspace.js +178 -0
- package/dist-server/shared/approval-mode.js +39 -0
- package/dist-server/shared/bot-avatar.js +33 -0
- package/dist-server/shared/bot-profile.js +7 -0
- package/dist-server/shared/connector-availability.js +12 -0
- package/dist-server/shared/credential-request.js +82 -0
- package/dist-server/shared/group-goal-run.js +1 -0
- package/dist-server/shared/import-name.js +11 -0
- package/dist-server/shared/mascot-bodies.js +107 -0
- package/dist-server/shared/routine-request.js +10 -0
- package/dist-server/shared/routine-run.js +1 -0
- package/dist-server/shared/skill-request.js +16 -0
- package/dist-server/shared/team-backup.js +148 -0
- package/dist-server/src/lib/composer-attachments.js +676 -0
- package/dist-server/tunnel-guardian.js +390 -0
- package/dist-server/vps-container-mcp.js +15209 -0
- package/package.json +32 -0
- package/skills/create-verification-skill/SKILL.md +65 -0
- package/skills/create-verification-skill/manifest.json +13 -0
- package/skills/phone-harness/SKILL.md +26 -0
- package/skills/phone-harness/agents/openai.yaml +13 -0
- package/skills/phone-harness/manifest.json +17 -0
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
// Config + data dirs. One file, ~/.openmausbot/config.json, env fallbacks:
|
|
2
|
+
// { "xai": {"key":"xai-…"}, "composio": {"apiKey":"ak_…"}, "box": {"token":"…"},
|
|
3
|
+
// "instances": { "<instanceId>": {"driver":"grok", …} } }
|
|
4
|
+
import { readFileSync, mkdirSync, existsSync, renameSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
9
|
+
import { parseStoredMcpServer } from "./mcp-registry.js";
|
|
10
|
+
import { parseJson, schemaIssue } from "./schema.js";
|
|
11
|
+
const optionalText = z.string().optional();
|
|
12
|
+
const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
13
|
+
const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
14
|
+
const BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
|
|
15
|
+
export const DEFAULT_ROOM_TURN_TIMEOUT_MINUTES = 5;
|
|
16
|
+
export const MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
|
|
17
|
+
export const MAX_ROOM_TURN_TIMEOUT_MINUTES = 1_440;
|
|
18
|
+
export const DEFAULT_LOCAL_VM_MODE = "shared";
|
|
19
|
+
export const DEFAULT_LOCAL_VM_MAX_INSTANCES = 2;
|
|
20
|
+
export const MIN_LOCAL_VM_MAX_INSTANCES = 1;
|
|
21
|
+
export const MAX_LOCAL_VM_MAX_INSTANCES = 4;
|
|
22
|
+
export function isValidSshAlias(value) {
|
|
23
|
+
return typeof value === "string" && SSH_ALIAS.test(value);
|
|
24
|
+
}
|
|
25
|
+
/** Keep the persisted VPS shape deliberately smaller than an SSH connection. */
|
|
26
|
+
export function normalizeVpsConfig(raw) {
|
|
27
|
+
if (raw === undefined || raw === null)
|
|
28
|
+
return {};
|
|
29
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
30
|
+
throw new Error("vps must be an object containing an SSH config alias");
|
|
31
|
+
}
|
|
32
|
+
const alias = raw.sshAlias;
|
|
33
|
+
if (alias === undefined || alias === "")
|
|
34
|
+
return {};
|
|
35
|
+
if (!isValidSshAlias(alias)) {
|
|
36
|
+
throw new Error("vps.sshAlias must be a simple SSH config alias (letters, numbers, dot, dash, or underscore)");
|
|
37
|
+
}
|
|
38
|
+
return { sshAlias: alias };
|
|
39
|
+
}
|
|
40
|
+
const vpsConfigSchema = z.object({
|
|
41
|
+
sshAlias: z.string().refine((value) => value === "" || isValidSshAlias(value), {
|
|
42
|
+
message: "must be a simple SSH config alias",
|
|
43
|
+
}).optional(),
|
|
44
|
+
});
|
|
45
|
+
const roomConfigSchema = z.object({
|
|
46
|
+
turnTimeoutMinutes: z
|
|
47
|
+
.number()
|
|
48
|
+
.int()
|
|
49
|
+
.min(MIN_ROOM_TURN_TIMEOUT_MINUTES)
|
|
50
|
+
.max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
|
|
51
|
+
});
|
|
52
|
+
const localVmConfigSchema = z.object({
|
|
53
|
+
mode: z.enum(["shared", "per-bot"]).optional(),
|
|
54
|
+
maxInstances: z
|
|
55
|
+
.number()
|
|
56
|
+
.int()
|
|
57
|
+
.min(MIN_LOCAL_VM_MAX_INSTANCES)
|
|
58
|
+
.max(MAX_LOCAL_VM_MAX_INSTANCES)
|
|
59
|
+
.optional(),
|
|
60
|
+
});
|
|
61
|
+
/** A named, shareable browser session ("Work", "Client A"). The id names a
|
|
62
|
+
* durable Electron partition; user-controlled characters never reach it. */
|
|
63
|
+
const browserProfileSchema = z.object({
|
|
64
|
+
// "guest" is the throwaway session's reserved id, never a saved profile
|
|
65
|
+
// Lowercase is part of the storage contract: durable Chromium partition
|
|
66
|
+
// directories would otherwise collide on case-insensitive filesystems.
|
|
67
|
+
id: z.string().regex(BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved"),
|
|
68
|
+
name: z.string().trim().min(1).max(40),
|
|
69
|
+
}).strict();
|
|
70
|
+
// #567 accepted mixed-case and duplicate ids. This schema exists only at the
|
|
71
|
+
// persisted-data boundary so an existing config can be read and migrated;
|
|
72
|
+
// API patches and save inputs continue to use browserProfileSchema above.
|
|
73
|
+
const legacyBrowserProfileSchema = z.object({
|
|
74
|
+
id: z.string().regex(LEGACY_BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved"),
|
|
75
|
+
name: z.string().trim().min(1).max(40),
|
|
76
|
+
/** Exact #567 Electron partition identity. This is persisted only by the
|
|
77
|
+
* migration boundary; config PATCH callers cannot choose or redirect it. */
|
|
78
|
+
partitionId: z.string().regex(LEGACY_BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved").optional(),
|
|
79
|
+
}).strict();
|
|
80
|
+
function suffixedBrowserProfileId(base, unavailable) {
|
|
81
|
+
for (let suffix = 2;; suffix += 1) {
|
|
82
|
+
const ending = `-${suffix}`;
|
|
83
|
+
const candidate = `${base.slice(0, 40 - ending.length)}${ending}`;
|
|
84
|
+
if (candidate !== "guest" && !unavailable.has(candidate))
|
|
85
|
+
return candidate;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function migrateStoredBrowserProfiles(profiles) {
|
|
89
|
+
const requestedPartitions = profiles.map((profile) => profile.partitionId ?? profile.id);
|
|
90
|
+
const rawBases = profiles.map((profile) => profile.id.toLowerCase());
|
|
91
|
+
// Canonical logical ids must be stable even if bots.json is migrated before
|
|
92
|
+
// config.json is rewritten. Give an exact lowercase spelling first claim on
|
|
93
|
+
// its id, then the first case variant. Generated ids avoid every legacy base
|
|
94
|
+
// and partition spelling, so applying the same legacy alias map again cannot
|
|
95
|
+
// reinterpret a previously migrated bot reference.
|
|
96
|
+
const canonicalIds = Array(profiles.length).fill(undefined);
|
|
97
|
+
const used = new Set();
|
|
98
|
+
const baseOwner = new Map();
|
|
99
|
+
rawBases.forEach((base, index) => {
|
|
100
|
+
if (base === "guest")
|
|
101
|
+
return;
|
|
102
|
+
const current = baseOwner.get(base);
|
|
103
|
+
if (current === undefined || (profiles[index].id === base && profiles[current].id !== base)) {
|
|
104
|
+
baseOwner.set(base, index);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
for (const [base, index] of baseOwner) {
|
|
108
|
+
canonicalIds[index] = base;
|
|
109
|
+
used.add(base);
|
|
110
|
+
}
|
|
111
|
+
const reserved = new Set([
|
|
112
|
+
"guest",
|
|
113
|
+
...rawBases,
|
|
114
|
+
...requestedPartitions.map((partitionId) => partitionId.toLowerCase()),
|
|
115
|
+
]);
|
|
116
|
+
rawBases.forEach((base, index) => {
|
|
117
|
+
if (canonicalIds[index] !== undefined)
|
|
118
|
+
return;
|
|
119
|
+
const id = suffixedBrowserProfileId(base, new Set([...reserved, ...used]));
|
|
120
|
+
canonicalIds[index] = id;
|
|
121
|
+
used.add(id);
|
|
122
|
+
});
|
|
123
|
+
// Chromium partition directories collide by case on Windows and default
|
|
124
|
+
// macOS volumes. Pick one safe owner for every case-folded identity. Prefer
|
|
125
|
+
// the profile whose canonical id matches that partition; every loser gets a
|
|
126
|
+
// new partition named after its collision-safe logical id.
|
|
127
|
+
const partitionWinner = new Map();
|
|
128
|
+
requestedPartitions.forEach((partitionId, index) => {
|
|
129
|
+
const folded = partitionId.toLowerCase();
|
|
130
|
+
const current = partitionWinner.get(folded);
|
|
131
|
+
if (current === undefined) {
|
|
132
|
+
partitionWinner.set(folded, index);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const score = (candidate) => canonicalIds[candidate] === folded ? 1 : 0;
|
|
136
|
+
if (score(index) > score(current))
|
|
137
|
+
partitionWinner.set(folded, index);
|
|
138
|
+
});
|
|
139
|
+
let effectivePartitions = requestedPartitions.map((partitionId, index) => partitionWinner.get(partitionId.toLowerCase()) === index ? partitionId : canonicalIds[index]);
|
|
140
|
+
// An earlier implementation could produce a cycle such as
|
|
141
|
+
// `foo-2 -> partition foo-2-2` and `foo-2-2 -> partition FOO-2`. The
|
|
142
|
+
// partitions are distinct today, but deleting and re-adding either id would
|
|
143
|
+
// join the other account. Move the *logical id owner* to a fresh id while
|
|
144
|
+
// retaining both exact durable partitions. Fresh ids avoid every raw id, so
|
|
145
|
+
// the old->new bot aliases below remain fixed points across repeated starts.
|
|
146
|
+
const conflictingIdOwners = new Set();
|
|
147
|
+
canonicalIds.forEach((id, owner) => {
|
|
148
|
+
effectivePartitions.forEach((partitionId, partitionOwner) => {
|
|
149
|
+
if (partitionOwner !== owner && partitionId.toLowerCase() === id)
|
|
150
|
+
conflictingIdOwners.add(owner);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
const unavailable = new Set([...reserved, ...used]);
|
|
154
|
+
for (const owner of conflictingIdOwners) {
|
|
155
|
+
const id = suffixedBrowserProfileId(rawBases[owner], unavailable);
|
|
156
|
+
canonicalIds[owner] = id;
|
|
157
|
+
unavailable.add(id);
|
|
158
|
+
}
|
|
159
|
+
if (conflictingIdOwners.size > 0) {
|
|
160
|
+
effectivePartitions = requestedPartitions.map((partitionId, index) => partitionWinner.get(partitionId.toLowerCase()) === index ? partitionId : canonicalIds[index]);
|
|
161
|
+
}
|
|
162
|
+
const aliases = new Map();
|
|
163
|
+
const canonical = profiles.map((profile, index) => {
|
|
164
|
+
const id = canonicalIds[index];
|
|
165
|
+
const partitionId = effectivePartitions[index];
|
|
166
|
+
const migrated = { id, name: profile.name };
|
|
167
|
+
if (partitionId !== id)
|
|
168
|
+
migrated.partitionId = partitionId;
|
|
169
|
+
// Exact duplicates are inherently ambiguous. Preserve the first mapping;
|
|
170
|
+
// later duplicate records get isolated ids but existing bot references
|
|
171
|
+
// cannot be distinguished from the first record.
|
|
172
|
+
if (!aliases.has(profile.id))
|
|
173
|
+
aliases.set(profile.id, id);
|
|
174
|
+
return migrated;
|
|
175
|
+
});
|
|
176
|
+
return { profiles: canonical, aliases };
|
|
177
|
+
}
|
|
178
|
+
const legacyBrowserProfilesSchema = z.array(legacyBrowserProfileSchema).max(20);
|
|
179
|
+
const storedBrowserProfilesSchema = legacyBrowserProfilesSchema.transform((profiles) => migrateStoredBrowserProfiles(profiles).profiles);
|
|
180
|
+
const browserProfilesSchema = z.array(browserProfileSchema).max(20).superRefine((profiles, ctx) => {
|
|
181
|
+
const seen = new Set();
|
|
182
|
+
profiles.forEach((profile, index) => {
|
|
183
|
+
if (!seen.has(profile.id)) {
|
|
184
|
+
seen.add(profile.id);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
ctx.addIssue({
|
|
188
|
+
code: "custom",
|
|
189
|
+
path: [index, "id"],
|
|
190
|
+
message: `browser profile id ${profile.id} is duplicated`,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
const featureConfigSchema = z.object({
|
|
195
|
+
/** Experimental desktop workflow recorder. Hidden unless explicitly enabled. */
|
|
196
|
+
skillRecorder: z.boolean().optional(),
|
|
197
|
+
/** Show each tool run in the transcript. Off unless explicitly enabled. */
|
|
198
|
+
showToolCalls: z.boolean().optional(),
|
|
199
|
+
/** Experimental built-in browser. Off until explicitly enabled; each bot
|
|
200
|
+
* also has its own switch. */
|
|
201
|
+
browser: z.boolean().optional(),
|
|
202
|
+
});
|
|
203
|
+
const instanceConfigSchema = z.object({
|
|
204
|
+
driver: z.string().min(1),
|
|
205
|
+
displayName: optionalText,
|
|
206
|
+
accentColor: optionalText,
|
|
207
|
+
environment: z.record(z.string(), z.string()).optional(),
|
|
208
|
+
enabled: z.boolean().optional(),
|
|
209
|
+
config: z.json().optional(),
|
|
210
|
+
});
|
|
211
|
+
const instanceConfigMapSchema = z.record(z.string(), instanceConfigSchema);
|
|
212
|
+
const appConfigSchema = z.object({
|
|
213
|
+
xai: z.object({ key: optionalText, url: optionalText }).optional(),
|
|
214
|
+
/** `model` seeds the default selection; `provider` pins an OpenRouter
|
|
215
|
+
* upstream (e.g. "fireworks"). Both are non-secret and optional. */
|
|
216
|
+
openaiCompat: z
|
|
217
|
+
.object({ key: optionalText, url: optionalText, model: optionalText, provider: optionalText })
|
|
218
|
+
.optional(),
|
|
219
|
+
/** Project key used for Sessions, catalog and agent tools. userId/sessionId
|
|
220
|
+
* are non-secret local identifiers used to reuse one Composio Session. */
|
|
221
|
+
composio: z.object({ apiKey: optionalText, userId: optionalText, sessionId: optionalText }).optional(),
|
|
222
|
+
box: z.object({ token: optionalText }).optional(),
|
|
223
|
+
vps: vpsConfigSchema.optional(),
|
|
224
|
+
/** Optional OpenCode key; persisted write-only and passed only to its child. */
|
|
225
|
+
opencodeGo: z.object({ apiKey: optionalText }).optional(),
|
|
226
|
+
/** Voice credentials and the selected voice id. `provider` picks the
|
|
227
|
+
* engine: "elevenlabs" (default; needs a key) or "system" (the Mac's
|
|
228
|
+
* built-in voices, no key). */
|
|
229
|
+
tts: z.object({ key: optionalText, voice: optionalText, provider: z.enum(["elevenlabs", "system"]).optional() }).optional(),
|
|
230
|
+
/** OpenAI key used only by the in-process avatar image generator. */
|
|
231
|
+
imageGen: z.object({ key: optionalText }).optional(),
|
|
232
|
+
/** Non-secret profile details shown in the sidebar. */
|
|
233
|
+
profile: z.object({ name: optionalText, email: optionalText }).optional(),
|
|
234
|
+
/** UI language override (BCP-47, lowercase). Empty/absent = follow the
|
|
235
|
+
* system language. Unknown tags degrade to English in the renderer. */
|
|
236
|
+
language: optionalText,
|
|
237
|
+
rooms: roomConfigSchema.optional(),
|
|
238
|
+
localVm: localVmConfigSchema.optional(),
|
|
239
|
+
features: featureConfigSchema.optional(),
|
|
240
|
+
browserProfiles: browserProfilesSchema.optional(),
|
|
241
|
+
instances: instanceConfigMapSchema.optional(),
|
|
242
|
+
/** User-configured MCP servers, mounted into every capable engine. Kept
|
|
243
|
+
* loosely typed HERE on purpose: parseStoredConfig throws away the whole
|
|
244
|
+
* file on a schema error, and one bad server entry must degrade to a
|
|
245
|
+
* skipped entry (customMcpServers), never to a vanished config. */
|
|
246
|
+
mcpServers: z.record(z.string(), z.unknown()).optional(),
|
|
247
|
+
});
|
|
248
|
+
const storedAppConfigSchema = appConfigSchema.extend({
|
|
249
|
+
browserProfiles: storedBrowserProfilesSchema.optional(),
|
|
250
|
+
});
|
|
251
|
+
const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true });
|
|
252
|
+
const jsonObjectSchema = z.record(z.string(), z.json());
|
|
253
|
+
/** Resolve a canonical profile record to its exact durable Electron
|
|
254
|
+
* partition identity. Callers must never substitute the display/API id. */
|
|
255
|
+
export function browserProfilePartitionId(profile) {
|
|
256
|
+
return profile.partitionId ?? profile.id;
|
|
257
|
+
}
|
|
258
|
+
/** Every durable partition must have one owner, and no other profile may use
|
|
259
|
+
* that partition's folded name as its logical id. Otherwise deleting and
|
|
260
|
+
* re-adding the logical id can silently attach a bot to the retained account. */
|
|
261
|
+
export function browserProfileRoutingConflict(profiles) {
|
|
262
|
+
const logicalOwner = new Map(profiles.map((profile, index) => [profile.id.toLowerCase(), index]));
|
|
263
|
+
const partitionOwner = new Map();
|
|
264
|
+
for (const [index, profile] of profiles.entries()) {
|
|
265
|
+
const partitionId = browserProfilePartitionId(profile);
|
|
266
|
+
const foldedPartition = partitionId.toLowerCase();
|
|
267
|
+
const existingPartitionOwner = partitionOwner.get(foldedPartition);
|
|
268
|
+
if (existingPartitionOwner !== undefined && existingPartitionOwner !== index) {
|
|
269
|
+
return `browser profiles cannot share the durable session “${partitionId}”`;
|
|
270
|
+
}
|
|
271
|
+
partitionOwner.set(foldedPartition, index);
|
|
272
|
+
const otherLogicalOwner = logicalOwner.get(foldedPartition);
|
|
273
|
+
if (otherLogicalOwner !== undefined && otherLogicalOwner !== index) {
|
|
274
|
+
return `browser profile id “${profiles[otherLogicalOwner].id}” is already used by another durable session`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
/** A list replacement cannot recycle a removed partition in the same write.
|
|
280
|
+
* Electron erases that partition only after commit, so allowing a new profile
|
|
281
|
+
* to claim its case-folded name would race new activity against the wipe. */
|
|
282
|
+
export function browserProfileReplacementConflict(currentProfiles, nextProfiles) {
|
|
283
|
+
const routingConflict = browserProfileRoutingConflict(nextProfiles);
|
|
284
|
+
if (routingConflict)
|
|
285
|
+
return routingConflict;
|
|
286
|
+
const currentIds = new Set(currentProfiles.map((profile) => profile.id));
|
|
287
|
+
const nextIds = new Set(nextProfiles.map((profile) => profile.id));
|
|
288
|
+
const removedPartitions = new Set(currentProfiles
|
|
289
|
+
.filter((profile) => !nextIds.has(profile.id))
|
|
290
|
+
.map((profile) => browserProfilePartitionId(profile).toLowerCase()));
|
|
291
|
+
const reused = nextProfiles.find((profile) => !currentIds.has(profile.id)
|
|
292
|
+
&& removedPartitions.has(browserProfilePartitionId(profile).toLowerCase()));
|
|
293
|
+
return reused
|
|
294
|
+
? `browser profile “${reused.name}” cannot reuse a session that is being erased; delete it first, then add the new profile`
|
|
295
|
+
: null;
|
|
296
|
+
}
|
|
297
|
+
export function browserProfilePartitionTarget(config, profileId) {
|
|
298
|
+
const profile = config.browserProfiles?.find((candidate) => candidate.id === profileId);
|
|
299
|
+
return profile ? { profileId: profile.id, partitionId: browserProfilePartitionId(profile) } : null;
|
|
300
|
+
}
|
|
301
|
+
export function parseStoredConfig(value) {
|
|
302
|
+
const parsed = storedAppConfigSchema.safeParse(value);
|
|
303
|
+
if (!parsed.success)
|
|
304
|
+
throw new Error(schemaIssue(parsed.error, "Invalid stored configuration"));
|
|
305
|
+
return parsed.data;
|
|
306
|
+
}
|
|
307
|
+
/** Exact old→canonical profile ids from #567's persisted config. Store
|
|
308
|
+
* hydration uses this to migrate bot references in the same write that
|
|
309
|
+
* resets other transient bot state. Invalid/non-legacy config is inert. */
|
|
310
|
+
export function loadBrowserProfileIdAliases() {
|
|
311
|
+
try {
|
|
312
|
+
const document = z.object({ browserProfiles: legacyBrowserProfilesSchema.optional() }).safeParse(parseJson(readFileSync(join(DATA_DIR, "config.json"), "utf8")));
|
|
313
|
+
if (!document.success || !document.data.browserProfiles)
|
|
314
|
+
return new Map();
|
|
315
|
+
return migrateStoredBrowserProfiles(document.data.browserProfiles).aliases;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return new Map();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export function parseConfigPatch(value) {
|
|
322
|
+
const parsed = appConfigPatchSchema.safeParse(value);
|
|
323
|
+
if (!parsed.success) {
|
|
324
|
+
throw Object.assign(new Error(schemaIssue(parsed.error, "Invalid configuration")), { status: 400 });
|
|
325
|
+
}
|
|
326
|
+
return parsed.data;
|
|
327
|
+
}
|
|
328
|
+
export function vpsSshAlias(cfg) {
|
|
329
|
+
return isValidSshAlias(cfg.vps?.sshAlias) ? cfg.vps.sshAlias : null;
|
|
330
|
+
}
|
|
331
|
+
export function roomTurnTimeoutMinutes(cfg) {
|
|
332
|
+
return cfg.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
|
|
333
|
+
}
|
|
334
|
+
export function localVmMode(cfg) {
|
|
335
|
+
return cfg.localVm?.mode ?? DEFAULT_LOCAL_VM_MODE;
|
|
336
|
+
}
|
|
337
|
+
export function localVmMaxInstances(cfg) {
|
|
338
|
+
return cfg.localVm?.maxInstances ?? DEFAULT_LOCAL_VM_MAX_INSTANCES;
|
|
339
|
+
}
|
|
340
|
+
export function skillRecorderEnabled(cfg) {
|
|
341
|
+
return cfg.features?.skillRecorder === true;
|
|
342
|
+
}
|
|
343
|
+
export function showToolCallsEnabled(cfg) {
|
|
344
|
+
return cfg.features?.showToolCalls === true;
|
|
345
|
+
}
|
|
346
|
+
/** Workspace-level gate for the experimental built-in browser. A bot's own
|
|
347
|
+
* switch sits under it, so either can withhold the browser. */
|
|
348
|
+
export function builtInBrowserEnabled(cfg) {
|
|
349
|
+
return cfg.features?.browser === true;
|
|
350
|
+
}
|
|
351
|
+
// OMB_DATA_DIR isolates test/soak rigs from the user's real fleet.
|
|
352
|
+
export const DATA_DIR = process.env.OMB_DATA_DIR ?? join(homedir(), ".openmausbot");
|
|
353
|
+
const LEGACY_DATA_DIR = join(homedir(), ".opengrokbot");
|
|
354
|
+
export const EVENTS_DIR = join(DATA_DIR, "events");
|
|
355
|
+
export const NATIVE_DIR = join(DATA_DIR, "native");
|
|
356
|
+
export function ensureDirs() {
|
|
357
|
+
// one-time migration from the pre-rename data dir — bots, transcripts,
|
|
358
|
+
// config and keys all carry over
|
|
359
|
+
if (!existsSync(DATA_DIR) && existsSync(LEGACY_DATA_DIR)) {
|
|
360
|
+
try {
|
|
361
|
+
renameSync(LEGACY_DATA_DIR, DATA_DIR);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
/* cross-device or busy — fall through to a fresh dir */
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
for (const dir of [DATA_DIR, EVENTS_DIR, NATIVE_DIR])
|
|
368
|
+
mkdirSync(dir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
export function loadConfig() {
|
|
371
|
+
let cfg = {};
|
|
372
|
+
try {
|
|
373
|
+
cfg = parseStoredConfig(parseJson(readFileSync(join(DATA_DIR, "config.json"), "utf8")));
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
/* first run — env fallbacks below */
|
|
377
|
+
}
|
|
378
|
+
// Env wins over the file for every credential. The desktop shell keeps
|
|
379
|
+
// these secrets OS-encrypted and hands them to this process as env at
|
|
380
|
+
// spawn, leaving config.json without the plaintext field — so the file
|
|
381
|
+
// value is the dev-mode (no desktop shell) fallback, not the primary.
|
|
382
|
+
// Anything that saves a credential mid-session must keep process.env in
|
|
383
|
+
// step (syncCredentialEnv below), or the value injected at boot would
|
|
384
|
+
// shadow the save until the next launch.
|
|
385
|
+
cfg.xai = { ...cfg.xai };
|
|
386
|
+
if (process.env.XAI_API_KEY !== undefined)
|
|
387
|
+
cfg.xai.key = process.env.XAI_API_KEY;
|
|
388
|
+
cfg.openaiCompat = { ...cfg.openaiCompat };
|
|
389
|
+
if (process.env.OPENAI_COMPAT_API_KEY !== undefined)
|
|
390
|
+
cfg.openaiCompat.key = process.env.OPENAI_COMPAT_API_KEY;
|
|
391
|
+
if (process.env.OPENAI_COMPAT_URL !== undefined)
|
|
392
|
+
cfg.openaiCompat.url = process.env.OPENAI_COMPAT_URL;
|
|
393
|
+
if (process.env.OPENAI_COMPAT_MODEL !== undefined)
|
|
394
|
+
cfg.openaiCompat.model = process.env.OPENAI_COMPAT_MODEL;
|
|
395
|
+
if (process.env.OPENAI_COMPAT_PROVIDER !== undefined)
|
|
396
|
+
cfg.openaiCompat.provider = process.env.OPENAI_COMPAT_PROVIDER;
|
|
397
|
+
cfg.composio = { ...cfg.composio };
|
|
398
|
+
if (process.env.COMPOSIO_API_KEY !== undefined)
|
|
399
|
+
cfg.composio.apiKey = process.env.COMPOSIO_API_KEY;
|
|
400
|
+
cfg.box = { ...cfg.box };
|
|
401
|
+
if (process.env.BOX_TOKEN !== undefined)
|
|
402
|
+
cfg.box.token = process.env.BOX_TOKEN;
|
|
403
|
+
cfg.opencodeGo = { ...cfg.opencodeGo };
|
|
404
|
+
if (process.env.OPENCODE_API_KEY !== undefined)
|
|
405
|
+
cfg.opencodeGo.apiKey = process.env.OPENCODE_API_KEY;
|
|
406
|
+
cfg.tts = { ...cfg.tts };
|
|
407
|
+
if (process.env.OMB_TTS_KEY !== undefined)
|
|
408
|
+
cfg.tts.key = process.env.OMB_TTS_KEY;
|
|
409
|
+
cfg.imageGen = { ...cfg.imageGen };
|
|
410
|
+
if (process.env.OMB_OPENAI_IMAGE_KEY !== undefined)
|
|
411
|
+
cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
|
|
412
|
+
return cfg;
|
|
413
|
+
}
|
|
414
|
+
/** After saveConfig() writes a credential, the running process's env must
|
|
415
|
+
* follow the newest value — loadConfig() prefers env, so the secret injected
|
|
416
|
+
* at boot would otherwise shadow the save until relaunch: the UI would show
|
|
417
|
+
* "saved" while every turn still used the old key. An empty string means the
|
|
418
|
+
* user cleared the credential, so the var is dropped and the (now empty)
|
|
419
|
+
* file value is authoritative again. Fields absent from the patch are
|
|
420
|
+
* untouched. */
|
|
421
|
+
export function syncCredentialEnv(patch) {
|
|
422
|
+
const secrets = [
|
|
423
|
+
[patch.xai?.key, "XAI_API_KEY"],
|
|
424
|
+
[patch.openaiCompat?.key, "OPENAI_COMPAT_API_KEY"],
|
|
425
|
+
[patch.composio?.apiKey, "COMPOSIO_API_KEY"],
|
|
426
|
+
[patch.box?.token, "BOX_TOKEN"],
|
|
427
|
+
[patch.opencodeGo?.apiKey, "OPENCODE_API_KEY"],
|
|
428
|
+
[patch.tts?.key, "OMB_TTS_KEY"],
|
|
429
|
+
[patch.imageGen?.key, "OMB_OPENAI_IMAGE_KEY"],
|
|
430
|
+
];
|
|
431
|
+
for (const [value, name] of secrets) {
|
|
432
|
+
if (value === undefined)
|
|
433
|
+
continue;
|
|
434
|
+
if (value)
|
|
435
|
+
process.env[name] = value;
|
|
436
|
+
else
|
|
437
|
+
delete process.env[name];
|
|
438
|
+
}
|
|
439
|
+
// loadConfig() also prefers env for url/model/provider, so a saved value
|
|
440
|
+
// must follow the same set-when-truthy / delete-when-cleared rule as keys.
|
|
441
|
+
const settings = [
|
|
442
|
+
[patch.openaiCompat?.url, "OPENAI_COMPAT_URL"],
|
|
443
|
+
[patch.openaiCompat?.model, "OPENAI_COMPAT_MODEL"],
|
|
444
|
+
[patch.openaiCompat?.provider, "OPENAI_COMPAT_PROVIDER"],
|
|
445
|
+
];
|
|
446
|
+
for (const [value, name] of settings) {
|
|
447
|
+
if (value === undefined)
|
|
448
|
+
continue;
|
|
449
|
+
if (value)
|
|
450
|
+
process.env[name] = value;
|
|
451
|
+
else
|
|
452
|
+
delete process.env[name];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/** Env names of every workspace credential this process may be holding —
|
|
456
|
+
* injected at boot by the desktop shell or exported by a developer. Spawned
|
|
457
|
+
* engine CLIs must never inherit them: the one driver that consumes a given
|
|
458
|
+
* secret receives it through instanceConfigs() narrowing, and to every other
|
|
459
|
+
* child these are someone else's keys riding along in `...process.env`. */
|
|
460
|
+
export const WORKSPACE_CREDENTIAL_ENV = [
|
|
461
|
+
"XAI_API_KEY",
|
|
462
|
+
"OPENAI_COMPAT_API_KEY",
|
|
463
|
+
"OPENAI_COMPAT_URL",
|
|
464
|
+
"BOX_TOKEN",
|
|
465
|
+
"OPENCODE_API_KEY",
|
|
466
|
+
"OMB_TTS_KEY",
|
|
467
|
+
"OMB_OPENAI_IMAGE_KEY",
|
|
468
|
+
"COMPOSIO_API_KEY",
|
|
469
|
+
"OMB_COMPOSIO_BROKER_TOKEN",
|
|
470
|
+
// Harness-private filesystem hints are not credentials themselves, but
|
|
471
|
+
// exposing them to a shell-capable agent points straight at app-owned
|
|
472
|
+
// state. The built-in browser master is delivered privately in memory.
|
|
473
|
+
"OMB_BROWSER_CONNECTION",
|
|
474
|
+
"OMB_USER_DATA",
|
|
475
|
+
];
|
|
476
|
+
/** Drop every workspace credential from a child-process env (in place). */
|
|
477
|
+
export function stripWorkspaceCredentialEnv(env) {
|
|
478
|
+
for (const key of WORKSPACE_CREDENTIAL_ENV)
|
|
479
|
+
delete env[key];
|
|
480
|
+
}
|
|
481
|
+
/** Env names a provider CLI might read as its own billing identity. A spawned
|
|
482
|
+
* engine keeps only what its driver explicitly allows: a foreign key riding
|
|
483
|
+
* along in `...process.env` must not flip a subscription CLI onto
|
|
484
|
+
* pay-as-you-go billing the user never granted. */
|
|
485
|
+
export const PROVIDER_CREDENTIAL_ENV = [
|
|
486
|
+
"ANTHROPIC_API_KEY",
|
|
487
|
+
"FACTORY_API_KEY",
|
|
488
|
+
"GEMINI_API_KEY",
|
|
489
|
+
"GOOGLE_API_KEY",
|
|
490
|
+
"KIMI_API_KEY",
|
|
491
|
+
"MOONSHOT_API_KEY",
|
|
492
|
+
"MINIMAX_API_KEY",
|
|
493
|
+
"OPENAI_API_KEY",
|
|
494
|
+
"OPENCODE_API_KEY",
|
|
495
|
+
"XAI_API_KEY",
|
|
496
|
+
"CURSOR_API_KEY",
|
|
497
|
+
"CURSOR_AUTH_TOKEN",
|
|
498
|
+
];
|
|
499
|
+
/** Merge a partial config into ~/.openmausbot/config.json (secrets never
|
|
500
|
+
* echoed back — callers report configured-or-not booleans only). */
|
|
501
|
+
export function saveConfig(patch) {
|
|
502
|
+
const p = join(DATA_DIR, "config.json");
|
|
503
|
+
let disk = {};
|
|
504
|
+
try {
|
|
505
|
+
const parsed = jsonObjectSchema.safeParse(parseJson(readFileSync(p, "utf8")));
|
|
506
|
+
if (parsed.success)
|
|
507
|
+
disk = parsed.data;
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
/* first write */
|
|
511
|
+
}
|
|
512
|
+
const checkedPatch = appConfigSchema.partial().parse(patch);
|
|
513
|
+
// A write is the durable migration point. Preserve every other raw key in
|
|
514
|
+
// config.json, but never write #567's mixed-case or duplicate profile ids
|
|
515
|
+
// back after we have successfully recognized the legacy list.
|
|
516
|
+
const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
|
|
517
|
+
if (storedProfiles.success)
|
|
518
|
+
disk.browserProfiles = storedProfiles.data;
|
|
519
|
+
for (const key of ["xai", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "localVm", "features"]) {
|
|
520
|
+
const section = checkedPatch[key];
|
|
521
|
+
if (!section)
|
|
522
|
+
continue;
|
|
523
|
+
const current = jsonObjectSchema.safeParse(disk[key]);
|
|
524
|
+
const merged = current.success ? { ...current.data } : {};
|
|
525
|
+
Object.assign(merged, section);
|
|
526
|
+
disk[key] = merged;
|
|
527
|
+
}
|
|
528
|
+
if (checkedPatch.vps !== undefined)
|
|
529
|
+
disk.vps = normalizeVpsConfig(checkedPatch.vps);
|
|
530
|
+
// scalar, not a section: the merge loop above only walks objects
|
|
531
|
+
if (checkedPatch.language !== undefined)
|
|
532
|
+
disk.language = checkedPatch.language;
|
|
533
|
+
// Custom MCP mutations go through their own dedicated local API, but
|
|
534
|
+
// saveConfig remains the single atomic persistence boundary.
|
|
535
|
+
if (checkedPatch.mcpServers !== undefined) {
|
|
536
|
+
disk.mcpServers = jsonObjectSchema.parse(checkedPatch.mcpServers);
|
|
537
|
+
}
|
|
538
|
+
// the whole list is the unit of change: an add or a delete arrives as the
|
|
539
|
+
// new list, never as a per-item merge
|
|
540
|
+
if (checkedPatch.browserProfiles !== undefined) {
|
|
541
|
+
// `partitionId` is read-only migration metadata. A rename/list replace
|
|
542
|
+
// from the renderer omits it, so carry it forward only for an unchanged
|
|
543
|
+
// canonical id. A genuinely new id always gets its own fresh partition.
|
|
544
|
+
const existingProfiles = new Map((storedProfiles.success ? storedProfiles.data : []).map((profile) => [profile.id, profile]));
|
|
545
|
+
const nextProfiles = checkedPatch.browserProfiles.map((profile) => {
|
|
546
|
+
const partitionId = existingProfiles.get(profile.id)?.partitionId;
|
|
547
|
+
return partitionId ? { ...profile, partitionId } : profile;
|
|
548
|
+
});
|
|
549
|
+
const routingConflict = browserProfileReplacementConflict(storedProfiles.success ? storedProfiles.data : [], nextProfiles);
|
|
550
|
+
if (routingConflict)
|
|
551
|
+
throw Object.assign(new Error(routingConflict), { status: 409 });
|
|
552
|
+
disk.browserProfiles = nextProfiles;
|
|
553
|
+
}
|
|
554
|
+
if (checkedPatch.instances) {
|
|
555
|
+
const currentInstances = jsonObjectSchema.safeParse(disk.instances);
|
|
556
|
+
const diskInstances = currentInstances.success ? currentInstances.data : {};
|
|
557
|
+
for (const [instanceId, entry] of Object.entries(checkedPatch.instances)) {
|
|
558
|
+
const current = jsonObjectSchema.safeParse(diskInstances[instanceId]);
|
|
559
|
+
const merged = current.success ? { ...current.data } : {};
|
|
560
|
+
Object.assign(merged, entry);
|
|
561
|
+
diskInstances[instanceId] = merged;
|
|
562
|
+
}
|
|
563
|
+
disk.instances = diskInstances;
|
|
564
|
+
}
|
|
565
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
566
|
+
writeFileAtomic(p, JSON.stringify(disk, null, 2), { mode: 0o600 });
|
|
567
|
+
}
|
|
568
|
+
/** Set one instance's `config.cli` ("" clears the override back to the
|
|
569
|
+
* driver default). Creating the instance entry is fine — a config-less
|
|
570
|
+
* entry rides driver.defaultConfig(). Returns false for unknown instances
|
|
571
|
+
* when the fleet is explicitly configured. The returned map must stay
|
|
572
|
+
* PERSISTABLE: instanceConfigs() injects credential env into consuming
|
|
573
|
+
* drivers' entries for the live fleet, so those injected keys are stripped
|
|
574
|
+
* back out before the map is returned — otherwise saving an override would
|
|
575
|
+
* copy xai/box/opencodeGo secrets into the instances section of
|
|
576
|
+
* config.json. */
|
|
577
|
+
export function withInstanceCli(cfg, instanceId, cli) {
|
|
578
|
+
const next = structuredClone(cfg);
|
|
579
|
+
const map = instanceConfigs(next);
|
|
580
|
+
// hasOwn, not truthiness: map is a plain object literal, so
|
|
581
|
+
// map["__proto__"] resolves to Object.prototype — truthy — and the
|
|
582
|
+
// assignment below would poison EVERY object in the process (instanceId
|
|
583
|
+
// comes off the URL, where `__proto__` passes the route's [\w.-]+ regex)
|
|
584
|
+
if (!Object.hasOwn(map, instanceId))
|
|
585
|
+
return { ok: false, config: cfg };
|
|
586
|
+
const entry = map[instanceId];
|
|
587
|
+
const cliKey = cli.trim();
|
|
588
|
+
const currentConfig = jsonObjectSchema.safeParse(entry.config);
|
|
589
|
+
if (cliKey) {
|
|
590
|
+
const nextConfig = currentConfig.success ? { ...currentConfig.data } : {};
|
|
591
|
+
nextConfig.cli = cliKey;
|
|
592
|
+
entry.config = nextConfig;
|
|
593
|
+
}
|
|
594
|
+
else if (currentConfig.success && Object.hasOwn(currentConfig.data, "cli")) {
|
|
595
|
+
const rest = { ...currentConfig.data };
|
|
596
|
+
delete rest.cli;
|
|
597
|
+
entry.config = Object.keys(rest).length ? rest : undefined;
|
|
598
|
+
}
|
|
599
|
+
for (const e of Object.values(map)) {
|
|
600
|
+
if (!e.environment)
|
|
601
|
+
continue;
|
|
602
|
+
const injected = injectedEnvironment(next, e.driver);
|
|
603
|
+
for (const [k, v] of Object.entries(e.environment)) {
|
|
604
|
+
if (injected.get(k) === v)
|
|
605
|
+
delete e.environment[k];
|
|
606
|
+
}
|
|
607
|
+
if (!Object.keys(e.environment).length)
|
|
608
|
+
delete e.environment;
|
|
609
|
+
}
|
|
610
|
+
next.instances = map;
|
|
611
|
+
return { ok: true, config: next };
|
|
612
|
+
}
|
|
613
|
+
/** The credential env instanceConfigs() injects for one driver — shared with
|
|
614
|
+
* withInstanceCli() so the inject rule and the strip rule cannot drift apart.
|
|
615
|
+
* Each secret goes only to the driver that actually reads it: the API-key
|
|
616
|
+
* Grok driver reads XAI_API_KEY, the Computer driver reads BOX_TOKEN, and
|
|
617
|
+
* OpenCode reads OPENCODE_API_KEY. Every other engine brings its own
|
|
618
|
+
* login, so handing it a key it never uses would only put that key in the
|
|
619
|
+
* environment of an unrelated child process. */
|
|
620
|
+
function injectedEnvironment(cfg, driver) {
|
|
621
|
+
const environment = new Map();
|
|
622
|
+
if (driver === "grok" && cfg.xai?.key)
|
|
623
|
+
environment.set("XAI_API_KEY", cfg.xai.key);
|
|
624
|
+
if (driver === "openai-compat" && cfg.openaiCompat?.key)
|
|
625
|
+
environment.set("OPENAI_COMPAT_API_KEY", cfg.openaiCompat.key);
|
|
626
|
+
if (driver === "openai-compat" && cfg.openaiCompat?.url)
|
|
627
|
+
environment.set("OPENAI_COMPAT_URL", cfg.openaiCompat.url);
|
|
628
|
+
if (driver === "boxAgent" && cfg.box?.token)
|
|
629
|
+
environment.set("BOX_TOKEN", cfg.box.token);
|
|
630
|
+
if (driver === "opencodeGo" && cfg.opencodeGo?.apiKey)
|
|
631
|
+
environment.set("OPENCODE_API_KEY", cfg.opencodeGo.apiKey);
|
|
632
|
+
return environment;
|
|
633
|
+
}
|
|
634
|
+
// Default fleet: one instance per built-in driver (upstream
|
|
635
|
+
// defaultInstanceIdForDriver — instanceId defaults to the driver kind).
|
|
636
|
+
// Config-file keys are injected as per-instance environment so drivers
|
|
637
|
+
// see them without needing real process env vars — but only into the
|
|
638
|
+
// driver that consumes each key (injectedEnvironment above).
|
|
639
|
+
export function instanceConfigs(cfg) {
|
|
640
|
+
// The default `grok` instance rides the `grokAgent` driver, not the API-key
|
|
641
|
+
// one: like claude and codex it needs no credential from us, just the CLI
|
|
642
|
+
// installed and logged in (it shows up unavailable otherwise). The API-key
|
|
643
|
+
// `grok` driver stays registered but out of the default fleet — that key is
|
|
644
|
+
// a credential Milind doesn't want to manage; an `instances` entry brings
|
|
645
|
+
// it back anytime.
|
|
646
|
+
//
|
|
647
|
+
// Google rides `antigravityAgent` (the official Google ACP server), not
|
|
648
|
+
// `geminiAgent`:
|
|
649
|
+
// Google retired Gemini CLI for the free/Pro/Ultra tiers on 2026-06-18
|
|
650
|
+
// (developers.googleblog.com, "transitioning Gemini CLI to Antigravity
|
|
651
|
+
// CLI"), so a default `gemini` instance could only ever show unavailable.
|
|
652
|
+
// The driver stays registered for enterprise licences, which keep Gemini
|
|
653
|
+
// CLI — `{"instances": {"gemini": {"driver": "geminiAgent"}}}` restores it.
|
|
654
|
+
const DEFAULT_FLEET = {
|
|
655
|
+
grok: { driver: "grokAgent" },
|
|
656
|
+
kimi: { driver: "kimiAgent" },
|
|
657
|
+
droid: { driver: "droidAgent" },
|
|
658
|
+
cursor: { driver: "cursorAgent" },
|
|
659
|
+
claude: { driver: "claudeAgent" },
|
|
660
|
+
codex: { driver: "codex" },
|
|
661
|
+
antigravity: { driver: "antigravityAgent" },
|
|
662
|
+
opencodeGo: { driver: "opencodeGo" },
|
|
663
|
+
computer: { driver: "boxAgent" },
|
|
664
|
+
openaiCompat: { driver: "openai-compat" },
|
|
665
|
+
qwen: { driver: "qwenAgent" },
|
|
666
|
+
hermes: { driver: "hermesAgent" },
|
|
667
|
+
pi: { driver: "piAgent" },
|
|
668
|
+
};
|
|
669
|
+
const CUSTOM_ONLY = {
|
|
670
|
+
qwen: { driver: "qwenAgent" },
|
|
671
|
+
hermes: { driver: "hermesAgent" },
|
|
672
|
+
pi: { driver: "piAgent" },
|
|
673
|
+
};
|
|
674
|
+
// New default-fleet engines that existing product configs would otherwise
|
|
675
|
+
// never see. Custom-only engines stay in CUSTOM_ONLY so a one-off test map
|
|
676
|
+
// is not expanded, matching the claude/grok/codex product-fleet probe.
|
|
677
|
+
const PRODUCT_FLEET_ADDITIONS = {
|
|
678
|
+
cursor: { driver: "cursorAgent" },
|
|
679
|
+
openaiCompat: { driver: "openai-compat" },
|
|
680
|
+
...CUSTOM_ONLY,
|
|
681
|
+
};
|
|
682
|
+
const configured = cfg.instances && Object.keys(cfg.instances).length ? cfg.instances : null;
|
|
683
|
+
const map = configured ? { ...configured } : { ...DEFAULT_FLEET };
|
|
684
|
+
// Product fleets pick up newly shipped engines. A one-off test/shadow map
|
|
685
|
+
// (no claude/grok/codex) is left exactly as written.
|
|
686
|
+
if (configured &&
|
|
687
|
+
(Object.hasOwn(configured, "claude") || Object.hasOwn(configured, "grok") || Object.hasOwn(configured, "codex"))) {
|
|
688
|
+
for (const [id, entry] of Object.entries(PRODUCT_FLEET_ADDITIONS)) {
|
|
689
|
+
if (!Object.hasOwn(map, id))
|
|
690
|
+
map[id] = { ...entry };
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const [id, sourceEntry] of Object.entries(map)) {
|
|
694
|
+
// instanceConfigs() builds a transient runtime map. Never mutate the
|
|
695
|
+
// caller's persisted entries while injecting workspace defaults: doing so
|
|
696
|
+
// would turn the first workspace URL into a stale per-instance override.
|
|
697
|
+
const entry = { ...sourceEntry };
|
|
698
|
+
map[id] = entry;
|
|
699
|
+
const environment = { ...entry.environment };
|
|
700
|
+
for (const [key, value] of injectedEnvironment(cfg, entry.driver))
|
|
701
|
+
environment[key] = value;
|
|
702
|
+
entry.environment = environment;
|
|
703
|
+
// The driver URL is configuration, not a credential. Environment is
|
|
704
|
+
// intentionally not consulted by ProviderRegistry when it decodes a
|
|
705
|
+
// driver's config, so carry the workspace default into the transient
|
|
706
|
+
// instance map while preserving a per-instance override.
|
|
707
|
+
if (entry.driver === "openai-compat" && cfg.openaiCompat) {
|
|
708
|
+
const defaults = {};
|
|
709
|
+
if (cfg.openaiCompat.url)
|
|
710
|
+
defaults.url = cfg.openaiCompat.url;
|
|
711
|
+
if (cfg.openaiCompat.model)
|
|
712
|
+
defaults.model = cfg.openaiCompat.model;
|
|
713
|
+
if (cfg.openaiCompat.provider)
|
|
714
|
+
defaults.provider = cfg.openaiCompat.provider;
|
|
715
|
+
if (Object.keys(defaults).length) {
|
|
716
|
+
const raw = entry.config;
|
|
717
|
+
const current = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
718
|
+
const merged = { ...current };
|
|
719
|
+
// A per-instance value always wins over the workspace default.
|
|
720
|
+
for (const [k, v] of Object.entries(defaults)) {
|
|
721
|
+
if (typeof merged[k] !== "string" || !merged[k].trim())
|
|
722
|
+
merged[k] = v;
|
|
723
|
+
}
|
|
724
|
+
entry.config = merged;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return map;
|
|
729
|
+
}
|
|
730
|
+
const reportedMcpSkips = new Set();
|
|
731
|
+
function skipMcpEntry(name, why) {
|
|
732
|
+
const key = `${name}: ${why}`;
|
|
733
|
+
if (reportedMcpSkips.has(key))
|
|
734
|
+
return;
|
|
735
|
+
reportedMcpSkips.add(key);
|
|
736
|
+
console.error(`mcpServers.${JSON.stringify(name)} skipped — ${why}`);
|
|
737
|
+
}
|
|
738
|
+
/** The validated, normalized custom servers from config — or {}. */
|
|
739
|
+
export function customMcpServers(cfg) {
|
|
740
|
+
const out = {};
|
|
741
|
+
for (const [name, raw] of Object.entries(cfg.mcpServers ?? {})) {
|
|
742
|
+
if (raw && typeof raw === "object" && "url" in raw) {
|
|
743
|
+
skipMcpEntry(name, 'only stdio servers ("command") are supported so far — HTTP transports are a planned follow-up');
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const parsed = parseStoredMcpServer(name, raw);
|
|
747
|
+
if (!parsed.ok) {
|
|
748
|
+
skipMcpEntry(name, `${parsed.error} Expected { "command": "npx", "args": [...], "env": { ... } }`);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (!parsed.server.enabled)
|
|
752
|
+
continue;
|
|
753
|
+
out[name] = {
|
|
754
|
+
command: parsed.server.command,
|
|
755
|
+
args: parsed.server.args,
|
|
756
|
+
env: parsed.server.env,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
return out;
|
|
760
|
+
}
|