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,471 @@
|
|
|
1
|
+
// Image attachments: pasted/dropped images become files under
|
|
2
|
+
// ~/.openmausbot/attachments so every CLI engine can open them by path —
|
|
3
|
+
// the app never ships image bytes through the prompt itself.
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { createReadStream, linkSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
6
|
+
import { link, open, unlink } from "node:fs/promises";
|
|
7
|
+
import { join, extname } from "node:path";
|
|
8
|
+
import { DATA_DIR } from "./config.js";
|
|
9
|
+
export const ATTACHMENTS_DIR = join(DATA_DIR, "attachments");
|
|
10
|
+
/** The spec's ceiling: a screenshot bigger than this is rejected before it
|
|
11
|
+
* is ever buffered, matching the composer's existing size discipline. */
|
|
12
|
+
export const IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
13
|
+
/** Shared documents are deliberately smaller than Cloudflare's transport
|
|
14
|
+
* ceiling. This is a local inbox, not unbounded remote storage. */
|
|
15
|
+
export const FILE_MAX_BYTES = 25 * 1024 * 1024;
|
|
16
|
+
/** Attachments are durable because prompts refer to their paths. Never
|
|
17
|
+
* silently evict them: once this ceiling is reached, a new upload gets an
|
|
18
|
+
* explicit 507 and the person can decide what to remove. */
|
|
19
|
+
export const ATTACHMENTS_MAX_BYTES = 512 * 1024 * 1024;
|
|
20
|
+
/** Interrupted streamed uploads use private partial files. A crash can leave
|
|
21
|
+
* one behind, so future uploads remove only our stale partials — never a
|
|
22
|
+
* committed attachment that a transcript may still reference. */
|
|
23
|
+
export const ATTACHMENT_PARTIAL_MAX_AGE_MS = 60 * 60 * 1000;
|
|
24
|
+
const PARTIAL_NAME = /^\.openmaus-upload-[0-9a-f-]+-[0-9a-f-]+\.partial$/i;
|
|
25
|
+
const UPLOAD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
26
|
+
const activePartials = new Set();
|
|
27
|
+
const uploadLocks = new Map();
|
|
28
|
+
let reservedAttachmentBytes = 0;
|
|
29
|
+
const STREAM_RESERVATION_INCREMENT_BYTES = 1024 * 1024;
|
|
30
|
+
/** Mimes the endpoint accepts, mapped to the extension stored on disk.
|
|
31
|
+
* Sniffing is not attempted — a lie here only changes the filename. */
|
|
32
|
+
const IMAGE_MIMES = {
|
|
33
|
+
"image/png": ".png",
|
|
34
|
+
"image/jpeg": ".jpg",
|
|
35
|
+
"image/gif": ".gif",
|
|
36
|
+
"image/webp": ".webp",
|
|
37
|
+
};
|
|
38
|
+
/** Useful document formats accepted by the phone share sheet.
|
|
39
|
+
* Generic archives, binaries, HTML, SVG, and executable/script mimes stay
|
|
40
|
+
* out. Office/OpenDocument packages are allowed because they are documents,
|
|
41
|
+
* despite using ZIP internally. The claimed mime determines the extension;
|
|
42
|
+
* an attacker-controlled filename never does. */
|
|
43
|
+
const FILE_MIMES = {
|
|
44
|
+
"text/plain": ".txt",
|
|
45
|
+
"text/markdown": ".md",
|
|
46
|
+
"text/csv": ".csv",
|
|
47
|
+
"text/tab-separated-values": ".tsv",
|
|
48
|
+
"application/json": ".json",
|
|
49
|
+
"application/pdf": ".pdf",
|
|
50
|
+
"application/rtf": ".rtf",
|
|
51
|
+
"text/rtf": ".rtf",
|
|
52
|
+
"application/msword": ".doc",
|
|
53
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
54
|
+
"application/vnd.ms-excel": ".xls",
|
|
55
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
56
|
+
"application/vnd.ms-powerpoint": ".ppt",
|
|
57
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
|
|
58
|
+
"application/vnd.oasis.opendocument.text": ".odt",
|
|
59
|
+
"application/vnd.oasis.opendocument.spreadsheet": ".ods",
|
|
60
|
+
"application/vnd.oasis.opendocument.presentation": ".odp",
|
|
61
|
+
};
|
|
62
|
+
export function extensionForMime(mime) {
|
|
63
|
+
if (!mime)
|
|
64
|
+
return null;
|
|
65
|
+
return IMAGE_MIMES[mime.split(";")[0].trim().toLowerCase()] ?? null;
|
|
66
|
+
}
|
|
67
|
+
export function ensureAttachmentsDir() {
|
|
68
|
+
mkdirSync(ATTACHMENTS_DIR, { recursive: true, mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
export function validateAttachmentUploadId(value) {
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const normalized = value.trim().toLowerCase();
|
|
74
|
+
if (!UPLOAD_ID.test(normalized)) {
|
|
75
|
+
throw statusError(400, "uploadId must be a UUID");
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
function attachmentUsageAndCleanup(now = Date.now()) {
|
|
80
|
+
ensureAttachmentsDir();
|
|
81
|
+
let bytes = 0;
|
|
82
|
+
let removed = 0;
|
|
83
|
+
for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
|
|
84
|
+
if (!entry.isFile())
|
|
85
|
+
continue;
|
|
86
|
+
const path = join(ATTACHMENTS_DIR, entry.name);
|
|
87
|
+
// A live partial is represented by its in-memory reservation. A fresh
|
|
88
|
+
// orphan from a crashed process still occupies disk and therefore counts
|
|
89
|
+
// until this same scan is allowed to remove it.
|
|
90
|
+
if (PARTIAL_NAME.test(entry.name) && activePartials.has(path))
|
|
91
|
+
continue;
|
|
92
|
+
try {
|
|
93
|
+
const stat = statSync(path);
|
|
94
|
+
if (PARTIAL_NAME.test(entry.name) && now - stat.mtimeMs >= ATTACHMENT_PARTIAL_MAX_AGE_MS) {
|
|
95
|
+
unlinkSync(path);
|
|
96
|
+
removed += 1;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
bytes += stat.size;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// A concurrent cleanup or manual removal made this entry consume zero.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { bytes, removed };
|
|
107
|
+
}
|
|
108
|
+
/** Remove only abandoned temporary uploads. Active partials are protected
|
|
109
|
+
* even if a very slow request crosses the age threshold. */
|
|
110
|
+
export function cleanupStaleAttachmentPartials(now = Date.now()) {
|
|
111
|
+
return attachmentUsageAndCleanup(now).removed;
|
|
112
|
+
}
|
|
113
|
+
/** A retry owns the same UUID namespace as the interrupted attempt. Once it
|
|
114
|
+
* holds that UUID's in-process lock, any inactive partial with the prefix is
|
|
115
|
+
* from a crashed/failed process and can be replaced immediately instead of
|
|
116
|
+
* consuming quota for an hour. */
|
|
117
|
+
function cleanupAttachmentPartialsForUpload(uploadId) {
|
|
118
|
+
ensureAttachmentsDir();
|
|
119
|
+
const prefix = `.openmaus-upload-${uploadId}-`;
|
|
120
|
+
for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
|
|
121
|
+
if (!entry.isFile() || !entry.name.startsWith(prefix) || !PARTIAL_NAME.test(entry.name))
|
|
122
|
+
continue;
|
|
123
|
+
const path = join(ATTACHMENTS_DIR, entry.name);
|
|
124
|
+
if (activePartials.has(path))
|
|
125
|
+
continue;
|
|
126
|
+
try {
|
|
127
|
+
unlinkSync(path);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// A concurrent cleanup already removed the abandoned attempt.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
class AttachmentReservation {
|
|
135
|
+
held = 0;
|
|
136
|
+
released = false;
|
|
137
|
+
reserve(bytes) {
|
|
138
|
+
if (this.released || bytes <= 0)
|
|
139
|
+
return;
|
|
140
|
+
this.reserveAtLeast(bytes, bytes);
|
|
141
|
+
}
|
|
142
|
+
reserveAtLeast(required, preferred) {
|
|
143
|
+
const used = attachmentUsageAndCleanup().bytes;
|
|
144
|
+
const available = ATTACHMENTS_MAX_BYTES - used - reservedAttachmentBytes;
|
|
145
|
+
if (available < required) {
|
|
146
|
+
throw statusError(507, `attachments storage is full (limit ${ATTACHMENTS_MAX_BYTES} bytes)`);
|
|
147
|
+
}
|
|
148
|
+
// Unknown-length HTTP bodies reserve in bounded chunks. Taking whatever
|
|
149
|
+
// remains below the preferred increment avoids falsely rejecting a small
|
|
150
|
+
// final upload near the quota while still bounding directory scans.
|
|
151
|
+
const granted = Math.min(preferred, available);
|
|
152
|
+
reservedAttachmentBytes += granted;
|
|
153
|
+
this.held += granted;
|
|
154
|
+
}
|
|
155
|
+
ensure(bytes) {
|
|
156
|
+
if (bytes <= this.held)
|
|
157
|
+
return;
|
|
158
|
+
const required = bytes - this.held;
|
|
159
|
+
const remainingPerFileCapacity = FILE_MAX_BYTES - this.held;
|
|
160
|
+
const preferred = Math.min(Math.max(required, STREAM_RESERVATION_INCREMENT_BYTES), remainingPerFileCapacity);
|
|
161
|
+
this.reserveAtLeast(required, preferred);
|
|
162
|
+
}
|
|
163
|
+
release() {
|
|
164
|
+
if (this.released)
|
|
165
|
+
return;
|
|
166
|
+
this.released = true;
|
|
167
|
+
reservedAttachmentBytes -= this.held;
|
|
168
|
+
this.held = 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function withUploadLock(uploadId, operation) {
|
|
172
|
+
if (!uploadId)
|
|
173
|
+
return operation();
|
|
174
|
+
const previous = uploadLocks.get(uploadId) ?? Promise.resolve();
|
|
175
|
+
let release;
|
|
176
|
+
const current = new Promise((resolve) => {
|
|
177
|
+
release = resolve;
|
|
178
|
+
});
|
|
179
|
+
uploadLocks.set(uploadId, current);
|
|
180
|
+
await previous;
|
|
181
|
+
try {
|
|
182
|
+
return await operation();
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
release();
|
|
186
|
+
if (uploadLocks.get(uploadId) === current)
|
|
187
|
+
uploadLocks.delete(uploadId);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function committedPathForUpload(uploadId, extension) {
|
|
191
|
+
ensureAttachmentsDir();
|
|
192
|
+
const exact = `${uploadId}${extension}`;
|
|
193
|
+
for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
|
|
194
|
+
if (!entry.isFile() || PARTIAL_NAME.test(entry.name))
|
|
195
|
+
continue;
|
|
196
|
+
if (entry.name === exact)
|
|
197
|
+
return join(ATTACHMENTS_DIR, entry.name);
|
|
198
|
+
if (entry.name.startsWith(`${uploadId}.`)) {
|
|
199
|
+
throw statusError(409, "uploadId was already used for another content type");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
async function digestFile(path) {
|
|
205
|
+
const hash = createHash("sha256");
|
|
206
|
+
for await (const chunk of createReadStream(path))
|
|
207
|
+
hash.update(chunk);
|
|
208
|
+
return hash.digest("hex");
|
|
209
|
+
}
|
|
210
|
+
function statusError(status, message) {
|
|
211
|
+
return Object.assign(new Error(message), { status });
|
|
212
|
+
}
|
|
213
|
+
function normalizedMime(mime) {
|
|
214
|
+
const value = mime?.split(";")[0]?.trim().toLowerCase();
|
|
215
|
+
return value || null;
|
|
216
|
+
}
|
|
217
|
+
export function extensionForFileMime(mime) {
|
|
218
|
+
const value = normalizedMime(mime);
|
|
219
|
+
return value ? FILE_MIMES[value] ?? null : null;
|
|
220
|
+
}
|
|
221
|
+
/** Strip platform-reserved characters and replace any sender-provided
|
|
222
|
+
* extension with the canonical one for the accepted mime. Separators and
|
|
223
|
+
* encoded separators are rejected rather than massaged so traversal attempts
|
|
224
|
+
* remain visible as bad requests. */
|
|
225
|
+
export function sanitizeSharedFileName(name, mime) {
|
|
226
|
+
const extension = extensionForFileMime(mime);
|
|
227
|
+
if (!extension)
|
|
228
|
+
throw statusError(400, "content-type must be a supported document type");
|
|
229
|
+
const normalized = name.normalize("NFKC").trim();
|
|
230
|
+
if (!normalized)
|
|
231
|
+
throw statusError(400, "name is required");
|
|
232
|
+
if (normalized.length > 512)
|
|
233
|
+
throw statusError(400, "name is too long");
|
|
234
|
+
if (/[\\/]/.test(normalized) || /%(?:00|2f|5c)/i.test(normalized)) {
|
|
235
|
+
throw statusError(400, "name must be a filename, not a path");
|
|
236
|
+
}
|
|
237
|
+
const dot = normalized.lastIndexOf(".");
|
|
238
|
+
const withoutExtension = dot > 0 ? normalized.slice(0, dot) : normalized;
|
|
239
|
+
const safeCharacters = Array.from(withoutExtension, (character) => {
|
|
240
|
+
const code = character.charCodeAt(0);
|
|
241
|
+
return code <= 31 || code === 127 || "<>:\"|?*".includes(character) ? "_" : character;
|
|
242
|
+
}).join("");
|
|
243
|
+
const stem = safeCharacters
|
|
244
|
+
.replace(/^\.+/, "")
|
|
245
|
+
.replace(/[.\s]+$/, "")
|
|
246
|
+
.replace(/\s+/g, " ")
|
|
247
|
+
.trim();
|
|
248
|
+
const boundedStem = Array.from(stem).slice(0, 180).join("");
|
|
249
|
+
if (!boundedStem)
|
|
250
|
+
throw statusError(400, "name must contain visible characters");
|
|
251
|
+
return `${boundedStem}${extension}`;
|
|
252
|
+
}
|
|
253
|
+
/** Stream one shared document to a generated file without ever collecting
|
|
254
|
+
* the request in memory. A failed, empty, or oversized upload leaves no
|
|
255
|
+
* partial file behind. */
|
|
256
|
+
export async function saveFile(chunks, originalName, mime, options = {}) {
|
|
257
|
+
const normalized = normalizedMime(mime);
|
|
258
|
+
if (!normalized || !extensionForFileMime(normalized)) {
|
|
259
|
+
throw statusError(400, "content-type must be a supported document type");
|
|
260
|
+
}
|
|
261
|
+
const name = sanitizeSharedFileName(originalName, normalized);
|
|
262
|
+
const extension = extensionForFileMime(normalized);
|
|
263
|
+
const uploadId = validateAttachmentUploadId(options.uploadId);
|
|
264
|
+
const expectedBytes = options.expectedBytes;
|
|
265
|
+
if (expectedBytes !== undefined && (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0)) {
|
|
266
|
+
throw statusError(400, "content-length must be a non-negative integer");
|
|
267
|
+
}
|
|
268
|
+
if (expectedBytes !== undefined && expectedBytes > FILE_MAX_BYTES) {
|
|
269
|
+
throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
|
|
270
|
+
}
|
|
271
|
+
return withUploadLock(uploadId, async () => {
|
|
272
|
+
ensureAttachmentsDir();
|
|
273
|
+
if (uploadId)
|
|
274
|
+
cleanupAttachmentPartialsForUpload(uploadId);
|
|
275
|
+
const existing = uploadId ? committedPathForUpload(uploadId, extension) : null;
|
|
276
|
+
if (existing) {
|
|
277
|
+
const incomingHash = createHash("sha256");
|
|
278
|
+
let bytes = 0;
|
|
279
|
+
for await (const value of chunks) {
|
|
280
|
+
const chunk = Buffer.isBuffer(value)
|
|
281
|
+
? value
|
|
282
|
+
: Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
283
|
+
if (chunk.byteLength === 0)
|
|
284
|
+
continue;
|
|
285
|
+
if (bytes + chunk.byteLength > FILE_MAX_BYTES) {
|
|
286
|
+
throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
|
|
287
|
+
}
|
|
288
|
+
incomingHash.update(chunk);
|
|
289
|
+
bytes += chunk.byteLength;
|
|
290
|
+
}
|
|
291
|
+
if (bytes === 0)
|
|
292
|
+
throw statusError(400, "empty file");
|
|
293
|
+
if (statSync(existing).size !== bytes || await digestFile(existing) !== incomingHash.digest("hex")) {
|
|
294
|
+
throw statusError(409, "uploadId was already used for different file bytes");
|
|
295
|
+
}
|
|
296
|
+
return { path: existing, name, mime: normalized, bytes };
|
|
297
|
+
}
|
|
298
|
+
const id = uploadId ?? randomUUID();
|
|
299
|
+
const path = join(ATTACHMENTS_DIR, `${id}${extension}`);
|
|
300
|
+
const partialPath = join(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID()}.partial`);
|
|
301
|
+
const reservation = new AttachmentReservation();
|
|
302
|
+
if (expectedBytes)
|
|
303
|
+
reservation.reserve(expectedBytes);
|
|
304
|
+
activePartials.add(partialPath);
|
|
305
|
+
let file;
|
|
306
|
+
let bytes = 0;
|
|
307
|
+
let closed = false;
|
|
308
|
+
try {
|
|
309
|
+
file = await open(partialPath, "wx", 0o600);
|
|
310
|
+
for await (const value of chunks) {
|
|
311
|
+
const chunk = Buffer.isBuffer(value)
|
|
312
|
+
? value
|
|
313
|
+
: Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
314
|
+
if (chunk.byteLength === 0)
|
|
315
|
+
continue;
|
|
316
|
+
if (bytes + chunk.byteLength > FILE_MAX_BYTES) {
|
|
317
|
+
throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
|
|
318
|
+
}
|
|
319
|
+
reservation.ensure(bytes + chunk.byteLength);
|
|
320
|
+
let offset = 0;
|
|
321
|
+
while (offset < chunk.byteLength) {
|
|
322
|
+
const result = await file.write(chunk, offset, chunk.byteLength - offset, null);
|
|
323
|
+
if (result.bytesWritten === 0)
|
|
324
|
+
throw new Error("file write made no progress");
|
|
325
|
+
offset += result.bytesWritten;
|
|
326
|
+
}
|
|
327
|
+
bytes += chunk.byteLength;
|
|
328
|
+
}
|
|
329
|
+
if (bytes === 0)
|
|
330
|
+
throw statusError(400, "empty file");
|
|
331
|
+
await file.close();
|
|
332
|
+
closed = true;
|
|
333
|
+
try {
|
|
334
|
+
await link(partialPath, path);
|
|
335
|
+
await unlink(partialPath);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
if (error.code !== "EEXIST")
|
|
339
|
+
throw error;
|
|
340
|
+
if (statSync(path).size !== bytes || await digestFile(path) !== await digestFile(partialPath)) {
|
|
341
|
+
throw statusError(409, "uploadId was already used for different file bytes");
|
|
342
|
+
}
|
|
343
|
+
await unlink(partialPath);
|
|
344
|
+
}
|
|
345
|
+
return { path, name, mime: normalized, bytes };
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (file && !closed)
|
|
349
|
+
await file.close().catch(() => undefined);
|
|
350
|
+
await unlink(partialPath).catch(() => undefined);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
activePartials.delete(partialPath);
|
|
355
|
+
reservation.release();
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
/** Persist one image and return its path. The UUID filename means the name
|
|
360
|
+
* is never attacker-controlled and never collides; the extension preserves
|
|
361
|
+
* the format the sender claimed. */
|
|
362
|
+
export function saveImage(bytes, mime, requestedUploadId) {
|
|
363
|
+
const ext = extensionForMime(mime);
|
|
364
|
+
if (!ext)
|
|
365
|
+
throw Object.assign(new Error("unsupported image type"), { status: 400 });
|
|
366
|
+
if (bytes.byteLength === 0)
|
|
367
|
+
throw Object.assign(new Error("empty image"), { status: 400 });
|
|
368
|
+
if (bytes.byteLength > IMAGE_MAX_BYTES) {
|
|
369
|
+
throw Object.assign(new Error(`image exceeds ${IMAGE_MAX_BYTES} bytes`), { status: 413 });
|
|
370
|
+
}
|
|
371
|
+
const uploadId = validateAttachmentUploadId(requestedUploadId);
|
|
372
|
+
ensureAttachmentsDir();
|
|
373
|
+
const existing = uploadId ? committedPathForUpload(uploadId, ext) : null;
|
|
374
|
+
if (existing) {
|
|
375
|
+
const saved = readFileSync(existing);
|
|
376
|
+
if (!saved.equals(bytes)) {
|
|
377
|
+
throw statusError(409, "uploadId was already used for different image bytes");
|
|
378
|
+
}
|
|
379
|
+
return { path: existing, mime: mime.split(";")[0].trim().toLowerCase(), bytes: bytes.byteLength };
|
|
380
|
+
}
|
|
381
|
+
const reservation = new AttachmentReservation();
|
|
382
|
+
reservation.reserve(bytes.byteLength);
|
|
383
|
+
const id = uploadId ?? randomUUID();
|
|
384
|
+
const name = `${id}${ext}`;
|
|
385
|
+
const path = join(ATTACHMENTS_DIR, name);
|
|
386
|
+
const partialPath = join(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID()}.partial`);
|
|
387
|
+
activePartials.add(partialPath);
|
|
388
|
+
try {
|
|
389
|
+
writeFileSync(partialPath, bytes, { mode: 0o600, flag: "wx" });
|
|
390
|
+
try {
|
|
391
|
+
linkSync(partialPath, path);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
if (error.code !== "EEXIST")
|
|
395
|
+
throw error;
|
|
396
|
+
const saved = readFileSync(path);
|
|
397
|
+
if (!saved.equals(bytes)) {
|
|
398
|
+
throw statusError(409, "uploadId was already used for different image bytes");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
unlinkSync(partialPath);
|
|
402
|
+
return { path, mime: mime.split(";")[0].trim().toLowerCase(), bytes: bytes.byteLength };
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
try {
|
|
406
|
+
unlinkSync(partialPath);
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// The successful path already removed it, or the write never created it.
|
|
410
|
+
}
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
activePartials.delete(partialPath);
|
|
415
|
+
reservation.release();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/** HTTP uploads take the same per-ID lock as streamed files. saveImage stays
|
|
419
|
+
* synchronous for generated avatars, while this wrapper prevents an image
|
|
420
|
+
* and a document using the same caller-supplied UUID from committing with
|
|
421
|
+
* different extensions at the same time. */
|
|
422
|
+
export async function saveImageUpload(bytes, mime, requestedUploadId) {
|
|
423
|
+
const uploadId = validateAttachmentUploadId(requestedUploadId);
|
|
424
|
+
return withUploadLock(uploadId, async () => {
|
|
425
|
+
if (uploadId)
|
|
426
|
+
cleanupAttachmentPartialsForUpload(uploadId);
|
|
427
|
+
return saveImage(bytes, mime, uploadId);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/** Existence check with the same name discipline as readAttachment, without
|
|
431
|
+
* reading up to 10MB of pixels just to learn the file is there. */
|
|
432
|
+
export function attachmentExists(name) {
|
|
433
|
+
if (!/^[A-Za-z0-9-]+\.(png|jpg|gif|webp)$/.test(name))
|
|
434
|
+
return false;
|
|
435
|
+
try {
|
|
436
|
+
return statSync(join(ATTACHMENTS_DIR, name)).isFile();
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** Read an attachment back for serving. Only names that are exactly a bare
|
|
443
|
+
* filename (no separators, no dotfiles) inside ATTACHMENTS_DIR resolve —
|
|
444
|
+
* the route must never become a general file server for the data dir. */
|
|
445
|
+
export function readAttachment(name) {
|
|
446
|
+
if (!/^[A-Za-z0-9-]+\.(png|jpg|jpeg|gif|webp)$/.test(name))
|
|
447
|
+
return null;
|
|
448
|
+
const path = join(ATTACHMENTS_DIR, name);
|
|
449
|
+
if (extname(path) === ".jpeg")
|
|
450
|
+
return null; // saved as .jpg; .jpeg is not a name we write
|
|
451
|
+
try {
|
|
452
|
+
return { bytes: readFileSync(path), mime: mimeForExt(extname(path)) };
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function mimeForExt(ext) {
|
|
459
|
+
switch (ext) {
|
|
460
|
+
case ".png":
|
|
461
|
+
return "image/png";
|
|
462
|
+
case ".jpg":
|
|
463
|
+
return "image/jpeg";
|
|
464
|
+
case ".gif":
|
|
465
|
+
return "image/gif";
|
|
466
|
+
case ".webp":
|
|
467
|
+
return "image/webp";
|
|
468
|
+
default:
|
|
469
|
+
return "application/octet-stream";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Auto mode: when a bot may answer its own permission requests.
|
|
2
|
+
//
|
|
3
|
+
// Safe Auto and remembered grants stop at the destructive/sensitive and
|
|
4
|
+
// unattended boundaries below. Full access is a separate, explicitly
|
|
5
|
+
// acknowledged mode: it answers every permission request, including those
|
|
6
|
+
// guards, while questions remain outside this module and always reach a human.
|
|
7
|
+
//
|
|
8
|
+
// The guard is deliberately tiny and literal. It is NOT a security
|
|
9
|
+
// boundary (an agent set on damage has a thousand spellings for `rm`);
|
|
10
|
+
// it is a "you probably didn't mean to hand THIS one over unattended"
|
|
11
|
+
// backstop for the obvious catastrophes. Real containment is the
|
|
12
|
+
// sandbox and the bot's own computer, not a regex.
|
|
13
|
+
import { approvalModeFor } from "../shared/approval-mode.js";
|
|
14
|
+
const DESTRUCTIVE = [
|
|
15
|
+
/\brm\s+(-[a-z]*\s+)*-[a-z]*[rf]/i, // rm -rf, rm -fr, rm -r -f
|
|
16
|
+
/\bmkfs\b|\bdiskutil\s+erase|\bdd\s+[^|]*\bof=\/dev\//i,
|
|
17
|
+
/\bshutdown\b|\breboot\b|\bhalt\b/i,
|
|
18
|
+
/:\(\)\s*\{.*\}\s*;?\s*:/, // fork bomb
|
|
19
|
+
/\bgit\s+push\s+[^|]*--force(-with-lease)?\b|\bgit\s+reset\s+--hard\b/i,
|
|
20
|
+
/\bDROP\s+(TABLE|DATABASE)\b|\bTRUNCATE\s+TABLE\b/i,
|
|
21
|
+
/\bsudo\s+rm\b|\bchmod\s+-R\s+777\s+\//i,
|
|
22
|
+
];
|
|
23
|
+
// Not destructive, but exactly what you don't hand over unattended: a
|
|
24
|
+
// bot reading your keys is quiet, permanent, and unrecoverable.
|
|
25
|
+
const SENSITIVE = [
|
|
26
|
+
/(^|[\s/"'])\.env(\.|$|["'\s])/i,
|
|
27
|
+
/\.ssh\/|id_rsa|id_ed25519|authorized_keys/i,
|
|
28
|
+
/\.aws\/credentials|\.netrc|\.npmrc|\.pypirc|\.docker\/config\.json/i,
|
|
29
|
+
/security\s+find-(generic|internet)-password|\bkeychain\b/i,
|
|
30
|
+
/\bcredentials?\.json\b|\bserviceaccount\b/i,
|
|
31
|
+
];
|
|
32
|
+
/** First matching pattern's source, so a verdict can NAME the rule that
|
|
33
|
+
* made it — the decision log's whole value is "which rule", and deriving
|
|
34
|
+
* the match a second time at the call site is how the log and the verdict
|
|
35
|
+
* drift apart. */
|
|
36
|
+
function matchFirst(rules, text) {
|
|
37
|
+
for (const re of rules)
|
|
38
|
+
if (re.test(text))
|
|
39
|
+
return re.source;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
export function looksSensitive(text) {
|
|
43
|
+
return matchFirst(SENSITIVE, text) !== null;
|
|
44
|
+
}
|
|
45
|
+
export function looksDestructive(text) {
|
|
46
|
+
return matchFirst(DESTRUCTIVE, text) !== null;
|
|
47
|
+
}
|
|
48
|
+
/** The key an "Always allow" remembers.
|
|
49
|
+
*
|
|
50
|
+
* A bare tool name is far too coarse for a command runner: remembering
|
|
51
|
+
* "Bash" would hand the bot a permanent unattended shell, which is the
|
|
52
|
+
* opposite of what someone pressing "always allow" on `git status`
|
|
53
|
+
* intends. Command tools are therefore keyed by their program —
|
|
54
|
+
* `Bash:git`, `Bash:npm` — so the grant is as narrow as the thing you
|
|
55
|
+
* actually looked at. Computed once, server-side, and echoed back by the
|
|
56
|
+
* client so the two sides can never disagree about what was granted. */
|
|
57
|
+
const COMMAND_TOOLS = new Set(["bash", "shell", "execute", "run_command", "computer_exec", "terminal"]);
|
|
58
|
+
export function approvalKey(tool, summary, scope) {
|
|
59
|
+
const bare = tool.replace(/^mcp__[^_]+__/, "").toLowerCase();
|
|
60
|
+
if (!COMMAND_TOOLS.has(bare))
|
|
61
|
+
return scope ? `${scope}:${tool}` : tool;
|
|
62
|
+
// first bare word of the command, skipping env assignments and sudo
|
|
63
|
+
const words = summary.trim().split(/\s+/);
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (i < words.length && (/^[A-Z_][A-Z0-9_]*=/.test(words[i]) || words[i] === "sudo"))
|
|
66
|
+
i += 1;
|
|
67
|
+
const program = (words[i] ?? "").split("/").pop()?.replace(/[^\w.-]/g, "") ?? "";
|
|
68
|
+
const key = program ? `${tool}:${program}` : tool;
|
|
69
|
+
return scope ? `${scope}:${key}` : key;
|
|
70
|
+
}
|
|
71
|
+
/** A durable "Always allow" choice is offered only when that exact grant
|
|
72
|
+
* would be honored on the next identical request. Custom delegates approval
|
|
73
|
+
* semantics to config.toml, while guards and provider-sandbox changes are
|
|
74
|
+
* intentionally never bypassed by remembered app grants. */
|
|
75
|
+
export function rememberableApprovalKey(bot, tool, summary, context) {
|
|
76
|
+
if (!bot ||
|
|
77
|
+
approvalModeFor(bot) === "custom" ||
|
|
78
|
+
context.source !== "no-grant" ||
|
|
79
|
+
context.scope ||
|
|
80
|
+
context.requiresExplicitApproval) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return approvalKey(tool, summary, context.scope);
|
|
84
|
+
}
|
|
85
|
+
/** The verdict AND its provenance. The decision itself is unchanged from
|
|
86
|
+
* autoDecision below — this exists so the decision log can record which
|
|
87
|
+
* rule decided without the call site re-deriving (and eventually
|
|
88
|
+
* mis-deriving) the match. */
|
|
89
|
+
export function autoVerdict(bot, tool, summary, context) {
|
|
90
|
+
const mode = approvalModeFor(bot);
|
|
91
|
+
if (context?.nativeApproval)
|
|
92
|
+
return { approve: null, source: "native-approval" };
|
|
93
|
+
// This branch intentionally precedes every guard. Entering Full access is
|
|
94
|
+
// separately consent-gated by the bot PATCH endpoint, and its promise is
|
|
95
|
+
// literal: even destructive, sensitive, unattended, and host-computer
|
|
96
|
+
// permission requests are approved. The request.opened caller invokes this
|
|
97
|
+
// function for permissions only, never for provider questions.
|
|
98
|
+
if (mode === "full") {
|
|
99
|
+
return {
|
|
100
|
+
approve: `approved ${tool} (full access)`,
|
|
101
|
+
source: "full-access",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (context?.requiresExplicitApproval) {
|
|
105
|
+
return { approve: null, source: "explicit-approval-block" };
|
|
106
|
+
}
|
|
107
|
+
// the guards outrank the grants, so an "always allow" can never widen
|
|
108
|
+
// into them
|
|
109
|
+
const destructive = matchFirst(DESTRUCTIVE, summary) ?? matchFirst(DESTRUCTIVE, tool);
|
|
110
|
+
const sensitive = destructive ? null : matchFirst(SENSITIVE, summary);
|
|
111
|
+
// The grant is computed even when a hard block will refuse it: the row
|
|
112
|
+
// worth auditing is "this WOULD have auto-approved, and only the block
|
|
113
|
+
// stood in the way", which cannot be told apart from an ordinary
|
|
114
|
+
// "nobody granted this" card without knowing both halves.
|
|
115
|
+
const key = approvalKey(tool, summary, context?.scope);
|
|
116
|
+
const grant = destructive || sensitive
|
|
117
|
+
? null
|
|
118
|
+
: mode !== "custom" && bot.alwaysAllow?.includes(key)
|
|
119
|
+
? { approve: `auto-approved ${key} (always allowed)`, source: "always-allow", rule: key }
|
|
120
|
+
: mode === "auto"
|
|
121
|
+
? { approve: `auto-approved ${tool}`, source: "auto-mode", rule: undefined }
|
|
122
|
+
: null;
|
|
123
|
+
if (context?.unattended) {
|
|
124
|
+
// Auto mode is something a person switched on for turns they are present
|
|
125
|
+
// for. A webhook turn begins with nobody watching, on a payload someone
|
|
126
|
+
// else wrote, so it does not inherit that decision — the guard above is a
|
|
127
|
+
// pattern list its own comment calls "not a security boundary", and it
|
|
128
|
+
// must not stand in for a human at 3am. A guard that would have carded
|
|
129
|
+
// anyway keeps its own name; the block is only the story when it is the
|
|
130
|
+
// thing that changed the outcome.
|
|
131
|
+
if (grant)
|
|
132
|
+
return { approve: null, source: "unattended-block", rule: grant.rule };
|
|
133
|
+
if (destructive)
|
|
134
|
+
return { approve: null, source: "destructive-guard", rule: destructive };
|
|
135
|
+
if (sensitive)
|
|
136
|
+
return { approve: null, source: "sensitive-guard", rule: sensitive };
|
|
137
|
+
return { approve: null, source: "no-grant" };
|
|
138
|
+
}
|
|
139
|
+
if (context?.scope === "local-computer" && mode !== "auto") {
|
|
140
|
+
// Host control is not covered by a remembered always-allow grant.
|
|
141
|
+
// After the Auto-on-this-computer warning, unclassified GUI actions
|
|
142
|
+
// (click/type) may auto-approve; destructive/sensitive still card.
|
|
143
|
+
if (grant)
|
|
144
|
+
return { approve: null, source: "local-computer-block", rule: grant.rule };
|
|
145
|
+
if (destructive)
|
|
146
|
+
return { approve: null, source: "destructive-guard", rule: destructive };
|
|
147
|
+
if (sensitive)
|
|
148
|
+
return { approve: null, source: "sensitive-guard", rule: sensitive };
|
|
149
|
+
return { approve: null, source: "no-grant" };
|
|
150
|
+
}
|
|
151
|
+
if (destructive)
|
|
152
|
+
return { approve: null, source: "destructive-guard", rule: destructive };
|
|
153
|
+
if (sensitive)
|
|
154
|
+
return { approve: null, source: "sensitive-guard", rule: sensitive };
|
|
155
|
+
if (grant)
|
|
156
|
+
return { approve: grant.approve, source: grant.source, rule: grant.rule };
|
|
157
|
+
return { approve: null, source: "no-grant" };
|
|
158
|
+
}
|
|
159
|
+
/** Why this request may be answered without the human, or null to ask. */
|
|
160
|
+
export function autoDecision(bot, tool, summary, context) {
|
|
161
|
+
return autoVerdict(bot, tool, summary, context).approve;
|
|
162
|
+
}
|