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,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BOT_AVATAR_CROPS, botAvatarCropSchema, botAvatarUrlSchema } from "../shared/bot-avatar.js";
|
|
3
|
+
import { BOT_PROFILE_LIMITS } from "../shared/bot-profile.js";
|
|
4
|
+
import { MASCOT_BODY_IDS, mascotBodySchema } from "../shared/mascot-bodies.js";
|
|
5
|
+
export const BOT_PROFILE_PATCH_FIELDS = [
|
|
6
|
+
"name",
|
|
7
|
+
"title",
|
|
8
|
+
"description",
|
|
9
|
+
"notifications",
|
|
10
|
+
"avatarUrl",
|
|
11
|
+
"avatarCrop",
|
|
12
|
+
"mascotBody",
|
|
13
|
+
"voice",
|
|
14
|
+
"speakReplies",
|
|
15
|
+
];
|
|
16
|
+
const profilePatchSchema = z.object({
|
|
17
|
+
name: z
|
|
18
|
+
.string({ error: "name must be a string" })
|
|
19
|
+
.max(BOT_PROFILE_LIMITS.name, { error: "name must be at most 100 characters" })
|
|
20
|
+
.refine((value) => Boolean(value.trim()), { error: "name must not be empty" })
|
|
21
|
+
.optional(),
|
|
22
|
+
title: z
|
|
23
|
+
.string({ error: "title must be a string" })
|
|
24
|
+
.max(BOT_PROFILE_LIMITS.title, { error: "title must be at most 200 characters" })
|
|
25
|
+
.optional(),
|
|
26
|
+
description: z
|
|
27
|
+
.string({ error: "description must be a string" })
|
|
28
|
+
.max(BOT_PROFILE_LIMITS.description, { error: "description must be at most 4000 characters" })
|
|
29
|
+
.optional(),
|
|
30
|
+
notifications: z.boolean({ error: "notifications must be true or false" }).optional(),
|
|
31
|
+
avatarUrl: z
|
|
32
|
+
.union([botAvatarUrlSchema, z.literal(""), z.null()], {
|
|
33
|
+
error: "avatarUrl must be a stored PNG, JPEG, GIF, or WebP attachment",
|
|
34
|
+
})
|
|
35
|
+
.optional(),
|
|
36
|
+
avatarCrop: botAvatarCropSchema.optional(),
|
|
37
|
+
mascotBody: mascotBodySchema.optional(),
|
|
38
|
+
voice: z
|
|
39
|
+
.string({ error: "voice must be a string" })
|
|
40
|
+
.max(BOT_PROFILE_LIMITS.voice, { error: "voice must be at most 200 characters" })
|
|
41
|
+
.optional(),
|
|
42
|
+
speakReplies: z.boolean({ error: "speakReplies must be true or false" }).optional(),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* The shared validation boundary for profile fields. The desktop's broad bot
|
|
46
|
+
* PATCH passes strict=false; paired clients use strict=true so a future bot
|
|
47
|
+
* field cannot silently become remotely writable.
|
|
48
|
+
*
|
|
49
|
+
* avatarUrl deliberately uses `undefined` as the normalized clear value.
|
|
50
|
+
* Store persistence already omits undefined fields, while wireBot sends null
|
|
51
|
+
* back to clients so Codable and object-spread clients both clear stale data.
|
|
52
|
+
*/
|
|
53
|
+
export function parseBotProfilePatch(input, strict = false) {
|
|
54
|
+
const parsed = (strict ? profilePatchSchema.strict() : profilePatchSchema).safeParse(input);
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
const unsupported = parsed.error.issues.find((issue) => issue.code === "unrecognized_keys");
|
|
57
|
+
if (unsupported?.code === "unrecognized_keys") {
|
|
58
|
+
return { ok: false, error: `unsupported profile field: ${unsupported.keys[0] ?? "unknown"}` };
|
|
59
|
+
}
|
|
60
|
+
const issue = parsed.error.issues[0];
|
|
61
|
+
if (issue?.path[0] === "avatarCrop") {
|
|
62
|
+
const options = `${BOT_AVATAR_CROPS.slice(0, -1).join(", ")}, or ${BOT_AVATAR_CROPS.at(-1)}`;
|
|
63
|
+
return { ok: false, error: `avatarCrop must be ${options}` };
|
|
64
|
+
}
|
|
65
|
+
if (issue?.path[0] === "mascotBody") {
|
|
66
|
+
const options = `${MASCOT_BODY_IDS.slice(0, -1).join(", ")}, or ${MASCOT_BODY_IDS.at(-1)}`;
|
|
67
|
+
return { ok: false, error: `mascotBody must be ${options}` };
|
|
68
|
+
}
|
|
69
|
+
return { ok: false, error: issue?.message ?? "invalid profile patch" };
|
|
70
|
+
}
|
|
71
|
+
const { avatarUrl, ...fields } = parsed.data;
|
|
72
|
+
const patch = fields;
|
|
73
|
+
if (avatarUrl !== undefined)
|
|
74
|
+
patch.avatarUrl = avatarUrl || undefined;
|
|
75
|
+
return { ok: true, patch };
|
|
76
|
+
}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { linkSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
5
|
+
import { DATA_DIR } from "./config.js";
|
|
6
|
+
const FILE = join(DATA_DIR, "box-create-requests.json");
|
|
7
|
+
const LOCK_FILE = join(DATA_DIR, "box-create-requests.lock");
|
|
8
|
+
const KEY_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
9
|
+
const MAX_REQUESTS = 4_096;
|
|
10
|
+
const LOCK_WAIT_MS = 2_000;
|
|
11
|
+
const LOCK_RETRY_MS = 20;
|
|
12
|
+
const MAX_REAPER_GENERATIONS = 128;
|
|
13
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
14
|
+
const BOT_ID = /^[A-Za-z0-9_-]{1,120}$/;
|
|
15
|
+
const BOX_ID = /^bx_[23456789abcdefghjkmnpqrstuvwxyz]{8}$/;
|
|
16
|
+
const lockWait = new Int32Array(new SharedArrayBuffer(4));
|
|
17
|
+
function isRequest(value) {
|
|
18
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
19
|
+
return false;
|
|
20
|
+
const request = value;
|
|
21
|
+
return (typeof request.botId === "string"
|
|
22
|
+
&& BOT_ID.test(request.botId)
|
|
23
|
+
&& typeof request.requestBody === "string"
|
|
24
|
+
&& request.requestBody.length > 0
|
|
25
|
+
&& request.requestBody.length <= 1_024
|
|
26
|
+
&& typeof request.idempotencyKey === "string"
|
|
27
|
+
&& UUID.test(request.idempotencyKey)
|
|
28
|
+
&& typeof request.createdAt === "number"
|
|
29
|
+
&& Number.isFinite(request.createdAt)
|
|
30
|
+
&& request.createdAt > 0
|
|
31
|
+
&& (request.boxId === undefined || (typeof request.boxId === "string" && BOX_ID.test(request.boxId)))
|
|
32
|
+
&& (request.resolved === undefined || (request.resolved === true && typeof request.boxId === "string")));
|
|
33
|
+
}
|
|
34
|
+
function recoveryStateError(detail, cause) {
|
|
35
|
+
return Object.assign(new Error(`Cloud computer creation is paused because its recovery state is ${detail}. `
|
|
36
|
+
+ "Check ascii.dev for an unnamed Box before repairing OpenMausBot's local state."), { status: 503, cause });
|
|
37
|
+
}
|
|
38
|
+
function loadFresh() {
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = JSON.parse(readFileSync(FILE, "utf8"));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error?.code === "ENOENT")
|
|
45
|
+
return [];
|
|
46
|
+
throw recoveryStateError("unreadable", error);
|
|
47
|
+
}
|
|
48
|
+
const file = raw;
|
|
49
|
+
if (!file
|
|
50
|
+
|| typeof file !== "object"
|
|
51
|
+
|| Array.isArray(file)
|
|
52
|
+
|| file.version !== 1
|
|
53
|
+
|| !Array.isArray(file.requests)
|
|
54
|
+
|| file.requests.length > MAX_REQUESTS
|
|
55
|
+
|| !file.requests.every(isRequest)) {
|
|
56
|
+
throw recoveryStateError("invalid");
|
|
57
|
+
}
|
|
58
|
+
const identities = new Set();
|
|
59
|
+
const keys = new Set();
|
|
60
|
+
const botsWithKnownBoxes = new Set();
|
|
61
|
+
for (const request of file.requests) {
|
|
62
|
+
const identity = `${request.botId}\0${request.requestBody}`;
|
|
63
|
+
if (identities.has(identity) || keys.has(request.idempotencyKey))
|
|
64
|
+
throw recoveryStateError("invalid");
|
|
65
|
+
if (request.boxId && botsWithKnownBoxes.has(request.botId))
|
|
66
|
+
throw recoveryStateError("invalid");
|
|
67
|
+
identities.add(identity);
|
|
68
|
+
keys.add(request.idempotencyKey);
|
|
69
|
+
if (request.boxId)
|
|
70
|
+
botsWithKnownBoxes.add(request.botId);
|
|
71
|
+
}
|
|
72
|
+
return file.requests.map((request) => ({ ...request }));
|
|
73
|
+
}
|
|
74
|
+
function save(next) {
|
|
75
|
+
mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
|
|
76
|
+
try {
|
|
77
|
+
writeFileAtomic(FILE, `${JSON.stringify({ version: 1, requests: next }, null, 2)}\n`, { mode: 0o600 });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw recoveryStateError("unavailable", error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function isLockOwner(value) {
|
|
84
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
85
|
+
return false;
|
|
86
|
+
const owner = value;
|
|
87
|
+
return owner.version === 1
|
|
88
|
+
&& Number.isInteger(owner.pid)
|
|
89
|
+
&& Number(owner.pid) > 0
|
|
90
|
+
&& Number(owner.pid) <= 0x7fffffff
|
|
91
|
+
&& typeof owner.token === "string"
|
|
92
|
+
&& UUID.test(owner.token)
|
|
93
|
+
&& typeof owner.createdAt === "number"
|
|
94
|
+
&& Number.isFinite(owner.createdAt)
|
|
95
|
+
&& owner.createdAt > 0;
|
|
96
|
+
}
|
|
97
|
+
function readLockOwner() {
|
|
98
|
+
let raw;
|
|
99
|
+
try {
|
|
100
|
+
raw = readFileSync(LOCK_FILE, "utf8");
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error?.code === "ENOENT")
|
|
104
|
+
return null;
|
|
105
|
+
throw recoveryStateError("lock is unreadable", error);
|
|
106
|
+
}
|
|
107
|
+
let owner;
|
|
108
|
+
try {
|
|
109
|
+
owner = JSON.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw recoveryStateError("lock is invalid", error);
|
|
113
|
+
}
|
|
114
|
+
if (!isLockOwner(owner))
|
|
115
|
+
throw recoveryStateError("lock is invalid");
|
|
116
|
+
return owner;
|
|
117
|
+
}
|
|
118
|
+
function readReaperOwner(path, targetToken) {
|
|
119
|
+
let raw;
|
|
120
|
+
try {
|
|
121
|
+
raw = readFileSync(path, "utf8");
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error?.code === "ENOENT")
|
|
125
|
+
return null;
|
|
126
|
+
throw recoveryStateError("stale-lock recovery record is unreadable", error);
|
|
127
|
+
}
|
|
128
|
+
// Versions before the successor chain wrote only the elected reaper's PID.
|
|
129
|
+
// Treat that immutable record as generation zero so an interrupted upgrade
|
|
130
|
+
// remains recoverable without deleting another contender's authority.
|
|
131
|
+
if (/^[1-9][0-9]*\n?$/.test(raw)) {
|
|
132
|
+
const pid = Number(raw.trim());
|
|
133
|
+
if (Number.isInteger(pid) && pid <= 0x7fffffff)
|
|
134
|
+
return { legacy: true, pid };
|
|
135
|
+
}
|
|
136
|
+
let owner;
|
|
137
|
+
try {
|
|
138
|
+
owner = JSON.parse(raw);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
throw recoveryStateError("stale-lock recovery record is invalid", error);
|
|
142
|
+
}
|
|
143
|
+
if (!isLockOwner(owner) || !("targetToken" in owner) || owner.targetToken !== targetToken) {
|
|
144
|
+
throw recoveryStateError("stale-lock recovery record is invalid");
|
|
145
|
+
}
|
|
146
|
+
return owner;
|
|
147
|
+
}
|
|
148
|
+
function processIsAlive(pid) {
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pid, 0);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const code = error?.code;
|
|
155
|
+
if (code === "ESRCH")
|
|
156
|
+
return false;
|
|
157
|
+
// EPERM proves a process owns the pid even though this account cannot
|
|
158
|
+
// signal it. Every other answer is ambiguous and must keep the lock.
|
|
159
|
+
if (code === "EPERM")
|
|
160
|
+
return true;
|
|
161
|
+
throw recoveryStateError("lock owner could not be verified", error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function unlinkCandidate(path) {
|
|
165
|
+
try {
|
|
166
|
+
unlinkSync(path);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error?.code !== "ENOENT") {
|
|
170
|
+
throw recoveryStateError("lock cleanup failed", error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function publishReaper(path, owner) {
|
|
175
|
+
const candidate = `${path}.candidate-${owner.pid}-${owner.token}`;
|
|
176
|
+
try {
|
|
177
|
+
writeFileSync(candidate, `${JSON.stringify(owner)}\n`, { flag: "wx", mode: 0o600, flush: true });
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
throw recoveryStateError("stale-lock recovery candidate is unavailable", error);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
try {
|
|
184
|
+
linkSync(candidate, path);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (error?.code === "EEXIST")
|
|
189
|
+
return false;
|
|
190
|
+
throw recoveryStateError("stale-lock recovery is unavailable", error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
unlinkCandidate(candidate);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function successorReaperPath(targetToken, reaperIdentity) {
|
|
198
|
+
const digest = createHash("sha256").update(reaperIdentity).digest("hex").slice(0, 32);
|
|
199
|
+
return `${LOCK_FILE}.reap-${targetToken}-${digest}`;
|
|
200
|
+
}
|
|
201
|
+
/** Elect exactly one live process to retire this immutable lock owner. A
|
|
202
|
+
* reaper can itself crash, so each dead reaper deterministically names the
|
|
203
|
+
* next election. Election records are never removed: no contender can delete
|
|
204
|
+
* a successor's authority between checking it and publishing its own. */
|
|
205
|
+
function claimReaperAuthority(expected) {
|
|
206
|
+
let reaperPath = `${LOCK_FILE}.reap-${expected.token}`;
|
|
207
|
+
for (let generation = 0; generation < MAX_REAPER_GENERATIONS; generation += 1) {
|
|
208
|
+
const candidate = {
|
|
209
|
+
version: 1,
|
|
210
|
+
pid: process.pid,
|
|
211
|
+
token: randomUUID(),
|
|
212
|
+
createdAt: Date.now(),
|
|
213
|
+
targetToken: expected.token,
|
|
214
|
+
};
|
|
215
|
+
if (publishReaper(reaperPath, candidate))
|
|
216
|
+
return true;
|
|
217
|
+
const current = readReaperOwner(reaperPath, expected.token);
|
|
218
|
+
if (!current)
|
|
219
|
+
continue;
|
|
220
|
+
if (processIsAlive(current.pid))
|
|
221
|
+
return false;
|
|
222
|
+
const identity = "legacy" in current ? `legacy:${current.pid}` : current.token;
|
|
223
|
+
reaperPath = successorReaperPath(expected.token, identity);
|
|
224
|
+
}
|
|
225
|
+
throw recoveryStateError("stale lock could not be recovered after repeated interrupted attempts");
|
|
226
|
+
}
|
|
227
|
+
function reapDeadLock(expected) {
|
|
228
|
+
if (!claimReaperAuthority(expected))
|
|
229
|
+
return false;
|
|
230
|
+
const current = readLockOwner();
|
|
231
|
+
if (!current || current.token !== expected.token)
|
|
232
|
+
return true;
|
|
233
|
+
if (processIsAlive(current.pid))
|
|
234
|
+
return false;
|
|
235
|
+
try {
|
|
236
|
+
unlinkSync(LOCK_FILE);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error?.code !== "ENOENT") {
|
|
240
|
+
throw recoveryStateError("stale lock could not be retired", error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
function acquireJournalLock() {
|
|
246
|
+
try {
|
|
247
|
+
mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw recoveryStateError("lock directory is unavailable", error);
|
|
251
|
+
}
|
|
252
|
+
const owner = {
|
|
253
|
+
version: 1,
|
|
254
|
+
pid: process.pid,
|
|
255
|
+
token: randomUUID(),
|
|
256
|
+
createdAt: Date.now(),
|
|
257
|
+
};
|
|
258
|
+
// The fully-written candidate exists before link(2) publishes it at the
|
|
259
|
+
// fixed lock path. Unlike open-then-write, contenders can never observe an
|
|
260
|
+
// ownerless lock after a crash between those two operations.
|
|
261
|
+
const candidate = `${LOCK_FILE}.candidate-${owner.pid}-${owner.token}`;
|
|
262
|
+
try {
|
|
263
|
+
writeFileSync(candidate, `${JSON.stringify(owner)}\n`, { flag: "wx", mode: 0o600, flush: true });
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw recoveryStateError("lock candidate is unavailable", error);
|
|
267
|
+
}
|
|
268
|
+
const deadline = performance.now() + LOCK_WAIT_MS;
|
|
269
|
+
try {
|
|
270
|
+
for (;;) {
|
|
271
|
+
try {
|
|
272
|
+
linkSync(candidate, LOCK_FILE);
|
|
273
|
+
return owner;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error?.code !== "EEXIST") {
|
|
277
|
+
throw recoveryStateError("lock could not be acquired", error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const current = readLockOwner();
|
|
281
|
+
const reaped = current !== null && !processIsAlive(current.pid) && reapDeadLock(current);
|
|
282
|
+
// Every retry path is bounded. In particular, an adversarially changing
|
|
283
|
+
// lock can disappear between link(EEXIST) and read, or replace each
|
|
284
|
+
// successfully reaped owner before the next link attempt.
|
|
285
|
+
if (performance.now() >= deadline) {
|
|
286
|
+
throw recoveryStateError("locked by another OpenMausBot process");
|
|
287
|
+
}
|
|
288
|
+
if (reaped)
|
|
289
|
+
continue;
|
|
290
|
+
Atomics.wait(lockWait, 0, 0, LOCK_RETRY_MS);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
unlinkCandidate(candidate);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function releaseJournalLock(owner) {
|
|
298
|
+
const current = readLockOwner();
|
|
299
|
+
if (!current || current.token !== owner.token || current.pid !== owner.pid) {
|
|
300
|
+
throw recoveryStateError("lock ownership changed unexpectedly");
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
unlinkSync(LOCK_FILE);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
throw recoveryStateError("lock could not be released", error);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function withJournalLock(operation) {
|
|
310
|
+
const owner = acquireJournalLock();
|
|
311
|
+
try {
|
|
312
|
+
return operation(loadFresh());
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
releaseJournalLock(owner);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Record the exact provider request before it leaves the process. A known
|
|
319
|
+
* Box wins over a changed body: once a provider resource exists, recovering
|
|
320
|
+
* and naming it is safer than issuing any second create request. */
|
|
321
|
+
export function beginBoxCreate(botId, requestBody) {
|
|
322
|
+
if (!BOT_ID.test(botId))
|
|
323
|
+
throw new Error("invalid bot id for cloud computer creation");
|
|
324
|
+
if (!requestBody || requestBody.length > 1_024)
|
|
325
|
+
throw new Error("invalid cloud computer create request");
|
|
326
|
+
return withJournalLock((requests) => {
|
|
327
|
+
const known = requests.find((request) => request.botId === botId && request.boxId);
|
|
328
|
+
if (known)
|
|
329
|
+
return { request: { ...known }, startedNow: false };
|
|
330
|
+
const now = Date.now();
|
|
331
|
+
const pending = requests.find((request) => request.botId === botId);
|
|
332
|
+
if (pending) {
|
|
333
|
+
if (pending.requestBody !== requestBody) {
|
|
334
|
+
throw recoveryStateError("waiting for an earlier cloud computer request to be reconciled");
|
|
335
|
+
}
|
|
336
|
+
if (now - pending.createdAt >= KEY_RETENTION_MS) {
|
|
337
|
+
// Once the provider forgets the idempotency key, retrying it (or using
|
|
338
|
+
// a new key) may create a second billable Box. Absence cannot be
|
|
339
|
+
// inferred from a lost response, so stop for manual reconciliation.
|
|
340
|
+
throw recoveryStateError("older than ascii.dev's 24-hour retry window");
|
|
341
|
+
}
|
|
342
|
+
return { request: { ...pending }, startedNow: false };
|
|
343
|
+
}
|
|
344
|
+
const request = {
|
|
345
|
+
botId,
|
|
346
|
+
requestBody,
|
|
347
|
+
idempotencyKey: randomUUID(),
|
|
348
|
+
createdAt: now,
|
|
349
|
+
};
|
|
350
|
+
if (requests.length >= MAX_REQUESTS)
|
|
351
|
+
throw recoveryStateError("full");
|
|
352
|
+
save([...requests, request]);
|
|
353
|
+
return { request: { ...request }, startedNow: true };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** Persist the returned identity before the caller attempts to rename it. */
|
|
357
|
+
export function rememberCreatedBox(request, boxId) {
|
|
358
|
+
if (!BOX_ID.test(boxId))
|
|
359
|
+
throw new Error("ascii.dev returned an invalid cloud computer id");
|
|
360
|
+
return withJournalLock((requests) => {
|
|
361
|
+
const current = requests.find((candidate) => (candidate.botId === request.botId
|
|
362
|
+
&& candidate.requestBody === request.requestBody
|
|
363
|
+
&& candidate.idempotencyKey === request.idempotencyKey));
|
|
364
|
+
if (!current || (current.boxId !== undefined && current.boxId !== boxId)) {
|
|
365
|
+
throw recoveryStateError("out of date");
|
|
366
|
+
}
|
|
367
|
+
const completed = { ...current, boxId };
|
|
368
|
+
// There can be an older rejected-TTL request for this bot. Once a Box is
|
|
369
|
+
// known, it is the only recovery authority we need to retain.
|
|
370
|
+
save([...requests.filter((candidate) => candidate.botId !== request.botId), completed]);
|
|
371
|
+
return { ...completed };
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
/** Mark the recovery record safe only after the deterministic provider rename
|
|
375
|
+
* succeeds. Keeping the resolved Box ID still lets a later retry recover from
|
|
376
|
+
* an eventually-consistent account listing without blocking bot deletion. */
|
|
377
|
+
export function resolveBoxCreate(request) {
|
|
378
|
+
return withJournalLock((requests) => {
|
|
379
|
+
const current = requests.find((candidate) => (candidate.botId === request.botId
|
|
380
|
+
&& candidate.requestBody === request.requestBody
|
|
381
|
+
&& candidate.idempotencyKey === request.idempotencyKey
|
|
382
|
+
&& candidate.boxId === request.boxId));
|
|
383
|
+
if (!current?.boxId)
|
|
384
|
+
throw recoveryStateError("out of date");
|
|
385
|
+
const resolved = { ...current, resolved: true };
|
|
386
|
+
save(requests.map((candidate) => candidate.idempotencyKey === current.idempotencyKey ? resolved : candidate));
|
|
387
|
+
return { ...resolved };
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/** Read-only deletion guard. Both a key-only request with an ambiguous
|
|
391
|
+
* provider outcome and a known-but-not-yet-named Box must keep its bot owner. */
|
|
392
|
+
export function hasUnresolvedBoxCreate(botId) {
|
|
393
|
+
if (!BOT_ID.test(botId))
|
|
394
|
+
throw new Error("invalid bot id for cloud computer creation");
|
|
395
|
+
return withJournalLock((requests) => (requests.some((request) => request.botId === botId && request.resolved !== true)));
|
|
396
|
+
}
|
|
397
|
+
/** Sanitized local recovery authority for configuration guards. The fresh,
|
|
398
|
+
* lock-protected read can prove a remembered provider id even while the
|
|
399
|
+
* account-wide LIST endpoint is eventually consistent. Provider request
|
|
400
|
+
* bodies and idempotency keys never cross this boundary. */
|
|
401
|
+
export function boxCreateRecoverySnapshot() {
|
|
402
|
+
return withJournalLock((requests) => requests.map((request) => ({
|
|
403
|
+
botId: request.botId,
|
|
404
|
+
...(request.boxId ? { boxId: request.boxId } : {}),
|
|
405
|
+
resolved: request.resolved === true,
|
|
406
|
+
})));
|
|
407
|
+
}
|
|
408
|
+
/** Persist ownership discovered from a pre-journal deterministic Box name.
|
|
409
|
+
* The provider listing proves which live bot owns the exact immutable id, but
|
|
410
|
+
* it must never steal an identity remembered for another bot. It supersedes
|
|
411
|
+
* stale attempts for the same bot, is idempotent, and fails closed on a true
|
|
412
|
+
* cross-bot conflict. */
|
|
413
|
+
export function adoptResolvedBox(botId, boxId) {
|
|
414
|
+
if (!BOT_ID.test(botId))
|
|
415
|
+
throw new Error("invalid bot id for cloud computer ownership");
|
|
416
|
+
if (!BOX_ID.test(boxId))
|
|
417
|
+
throw new Error("invalid cloud computer id for ownership");
|
|
418
|
+
withJournalLock((requests) => {
|
|
419
|
+
const sameBox = requests.find((request) => request.boxId === boxId);
|
|
420
|
+
if (sameBox && sameBox.botId !== botId) {
|
|
421
|
+
throw recoveryStateError("conflicted with another bot's remembered cloud computer");
|
|
422
|
+
}
|
|
423
|
+
const adopted = sameBox
|
|
424
|
+
? { ...sameBox, resolved: true }
|
|
425
|
+
: {
|
|
426
|
+
botId,
|
|
427
|
+
requestBody: JSON.stringify({ adopted: "legacy-name", boxId }),
|
|
428
|
+
idempotencyKey: randomUUID(),
|
|
429
|
+
createdAt: Date.now(),
|
|
430
|
+
boxId,
|
|
431
|
+
resolved: true,
|
|
432
|
+
};
|
|
433
|
+
const retained = requests.filter((request) => request.botId !== botId);
|
|
434
|
+
if (retained.length >= MAX_REQUESTS)
|
|
435
|
+
throw recoveryStateError("full");
|
|
436
|
+
if (requests.filter((request) => request.botId === botId).length === 1
|
|
437
|
+
&& sameBox?.resolved === true
|
|
438
|
+
&& sameBox.botId === botId)
|
|
439
|
+
return;
|
|
440
|
+
// A successfully listed deterministic legacy name resolves any older
|
|
441
|
+
// key-only/remembered attempt for this bot. Keeping those stale rows would
|
|
442
|
+
// leave deletion permanently blocked after the adopted Box is removed.
|
|
443
|
+
save([...retained, adopted]);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/** Retire only the durable identity for a Box the provider has confirmed was
|
|
447
|
+
* deleted. Callers must never use this for a failed or ambiguous deletion. */
|
|
448
|
+
export function retireDeletedBoxCreate(boxId) {
|
|
449
|
+
if (!BOX_ID.test(boxId))
|
|
450
|
+
throw new Error("invalid deleted cloud computer id");
|
|
451
|
+
withJournalLock((requests) => {
|
|
452
|
+
const next = requests.filter((request) => request.boxId !== boxId);
|
|
453
|
+
if (next.length !== requests.length)
|
|
454
|
+
save(next);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
export function discardBoxCreate(request) {
|
|
458
|
+
withJournalLock((requests) => {
|
|
459
|
+
const next = requests.filter((candidate) => candidate.idempotencyKey !== request.idempotencyKey);
|
|
460
|
+
if (next.length !== requests.length)
|
|
461
|
+
save(next);
|
|
462
|
+
});
|
|
463
|
+
}
|