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,871 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { newId } from "./contracts.js";
|
|
4
|
+
import { redactSecretsInText } from "./redact.js";
|
|
5
|
+
import { parseJson, schemaIssue } from "./schema.js";
|
|
6
|
+
import { nextOccurrence, } from "./routines.js";
|
|
7
|
+
const WEEKDAY_NUMBER = {
|
|
8
|
+
sunday: 0,
|
|
9
|
+
monday: 1,
|
|
10
|
+
tuesday: 2,
|
|
11
|
+
wednesday: 3,
|
|
12
|
+
thursday: 4,
|
|
13
|
+
friday: 5,
|
|
14
|
+
saturday: 6,
|
|
15
|
+
};
|
|
16
|
+
const WEEKDAY_LABEL = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
17
|
+
const RFC3339_WITH_OFFSET = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(?:Z|[+-](\d{2}):(\d{2}))$/i;
|
|
18
|
+
const TIME = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
19
|
+
const ROUTINE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
20
|
+
const MAX_DATE_MS = 8_640_000_000_000_000;
|
|
21
|
+
const ACTION_COPY = {
|
|
22
|
+
create: { title: "Schedule", detail: "Create routine" },
|
|
23
|
+
update: { title: "Update", detail: "Update routine" },
|
|
24
|
+
pause: { title: "Pause", detail: "Pause routine" },
|
|
25
|
+
resume: { title: "Resume", detail: "Resume routine" },
|
|
26
|
+
run_now: { title: "Run now", detail: "Run routine now" },
|
|
27
|
+
delete: { title: "Delete", detail: "Delete routine" },
|
|
28
|
+
};
|
|
29
|
+
const ROUTINE_REQUEST_FINGERPRINT_VERSION = 1;
|
|
30
|
+
const jsonObjectSchema = z.record(z.string(), z.custom());
|
|
31
|
+
const routineToolScheduleSchema = z.discriminatedUnion("type", [
|
|
32
|
+
z.object({ type: z.literal("once"), at: z.string().max(64) }).strict(),
|
|
33
|
+
z.object({
|
|
34
|
+
type: z.literal("weekly"),
|
|
35
|
+
time: z.string().max(5),
|
|
36
|
+
weekdays: z.array(z.string().max(9)).min(1).max(7),
|
|
37
|
+
}).strict(),
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal("interval"),
|
|
40
|
+
everyMinutes: z.number(),
|
|
41
|
+
anchorAt: z.string().max(64).optional(),
|
|
42
|
+
}).strict(),
|
|
43
|
+
]);
|
|
44
|
+
const routineToolDefinitionSchema = z.object({
|
|
45
|
+
name: z.string().max(80),
|
|
46
|
+
instructions: z.string().max(20_000),
|
|
47
|
+
schedule: routineToolScheduleSchema,
|
|
48
|
+
runOn: z.enum(["maus", "cloud"]).optional(),
|
|
49
|
+
durationMinutes: z.number().optional(),
|
|
50
|
+
timeoutMinutes: z.number().nullable().optional(),
|
|
51
|
+
}).strict();
|
|
52
|
+
const routineToolChangesSchema = routineToolDefinitionSchema
|
|
53
|
+
.omit({ timeoutMinutes: true })
|
|
54
|
+
.partial()
|
|
55
|
+
.extend({ timeoutMinutes: z.number().nullable().optional() })
|
|
56
|
+
.strict()
|
|
57
|
+
.refine((changes) => Object.values(changes).some((value) => value !== undefined), "Choose at least one routine field to update");
|
|
58
|
+
/** Resolved and authorized by the harness route (existence + same section)
|
|
59
|
+
* before it reaches this service; re-authorized again at confirm time. */
|
|
60
|
+
const targetBotSchema = z.object({
|
|
61
|
+
botId: z.string().min(1).max(128),
|
|
62
|
+
name: z.string().min(1).max(80),
|
|
63
|
+
}).strict();
|
|
64
|
+
const routineProposalSchema = z.discriminatedUnion("action", [
|
|
65
|
+
z.object({ action: z.literal("create"), routine: routineToolDefinitionSchema, forBot: targetBotSchema.optional() }).strict(),
|
|
66
|
+
z.object({ action: z.literal("update"), routineId: z.string().max(128), changes: routineToolChangesSchema }).strict(),
|
|
67
|
+
z.object({ action: z.literal("pause"), routineId: z.string().max(128) }).strict(),
|
|
68
|
+
z.object({ action: z.literal("resume"), routineId: z.string().max(128) }).strict(),
|
|
69
|
+
z.object({ action: z.literal("run_now"), routineId: z.string().max(128) }).strict(),
|
|
70
|
+
z.object({ action: z.literal("delete"), routineId: z.string().max(128) }).strict(),
|
|
71
|
+
]);
|
|
72
|
+
const storedWeekdaysSchema = z.array(z.number().int().min(0).max(6)).min(1).max(7).refine((weekdays) => new Set(weekdays).size === weekdays.length, "Stored routine weekdays must be unique");
|
|
73
|
+
const storedScheduleSchema = z.discriminatedUnion("type", [
|
|
74
|
+
z.object({ type: z.literal("once"), at: z.number().int().nonnegative() }).strict(),
|
|
75
|
+
z.object({
|
|
76
|
+
type: z.literal("daily"),
|
|
77
|
+
time: z.string().regex(TIME),
|
|
78
|
+
weekdays: storedWeekdaysSchema,
|
|
79
|
+
}).strict(),
|
|
80
|
+
z.object({
|
|
81
|
+
type: z.literal("interval"),
|
|
82
|
+
everyMinutes: z.number().int().min(5).max(1_440),
|
|
83
|
+
anchorAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
|
|
84
|
+
}).strict(),
|
|
85
|
+
]);
|
|
86
|
+
const storedDefinitionSchema = z.object({
|
|
87
|
+
name: z.string().trim().min(1).max(80),
|
|
88
|
+
instructions: z.string().trim().min(1).max(20_000),
|
|
89
|
+
schedule: storedScheduleSchema,
|
|
90
|
+
runOn: z.enum(["maus", "cloud"]),
|
|
91
|
+
durationMinutes: z.number().int().min(5).max(240),
|
|
92
|
+
timeoutMinutes: z.number().int().min(5).max(240).optional(),
|
|
93
|
+
}).strict();
|
|
94
|
+
const storedChangesSchema = storedDefinitionSchema
|
|
95
|
+
.omit({ timeoutMinutes: true })
|
|
96
|
+
.partial()
|
|
97
|
+
.extend({ timeoutMinutes: z.number().int().min(5).max(240).nullable().optional() })
|
|
98
|
+
.strict()
|
|
99
|
+
.refine((changes) => Object.values(changes).some((value) => value !== undefined), "Stored routine update must change at least one field");
|
|
100
|
+
const storedManageBase = {
|
|
101
|
+
routineId: z.string().regex(ROUTINE_ID),
|
|
102
|
+
expectedUpdatedAt: z.number().int().nonnegative(),
|
|
103
|
+
};
|
|
104
|
+
const storedOperationSchema = z.discriminatedUnion("action", [
|
|
105
|
+
z.object({ action: z.literal("create"), routine: storedDefinitionSchema, forBot: targetBotSchema.optional() }).strict(),
|
|
106
|
+
z.object({ action: z.literal("update"), ...storedManageBase, changes: storedChangesSchema }).strict(),
|
|
107
|
+
z.object({ action: z.literal("pause"), ...storedManageBase }).strict(),
|
|
108
|
+
z.object({ action: z.literal("resume"), ...storedManageBase }).strict(),
|
|
109
|
+
z.object({ action: z.literal("run_now"), ...storedManageBase }).strict(),
|
|
110
|
+
z.object({ action: z.literal("delete"), ...storedManageBase }).strict(),
|
|
111
|
+
]);
|
|
112
|
+
const routineRequestCardDataSchema = z.object({
|
|
113
|
+
version: z.literal(1),
|
|
114
|
+
requestId: z.string().min(1).max(128),
|
|
115
|
+
botId: z.string().min(1).max(128),
|
|
116
|
+
threadId: z.string().min(1).max(128),
|
|
117
|
+
createdAt: z.number().int().nonnegative(),
|
|
118
|
+
operation: storedOperationSchema,
|
|
119
|
+
appliedAt: z.number().int().nonnegative().optional(),
|
|
120
|
+
resultId: z.string().min(1).max(128).optional(),
|
|
121
|
+
}).strict();
|
|
122
|
+
export class RoutineRequestError extends Error {
|
|
123
|
+
status;
|
|
124
|
+
constructor(message, status = 400) {
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = "RoutineRequestError";
|
|
127
|
+
this.status = status;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function text(value, field, max) {
|
|
131
|
+
const trimmed = value.trim();
|
|
132
|
+
if (!trimmed)
|
|
133
|
+
throw new RoutineRequestError(`${field} is required`);
|
|
134
|
+
if (trimmed.length > max)
|
|
135
|
+
throw new RoutineRequestError(`${field} must be ${max.toLocaleString("en-US")} characters or fewer`);
|
|
136
|
+
// This payload is hidden under the visible card fields, so the store's
|
|
137
|
+
// shallow card redaction cannot reach it. Scrub before it is persisted.
|
|
138
|
+
const redacted = redactSecretsInText(trimmed);
|
|
139
|
+
if (redacted.length > max) {
|
|
140
|
+
throw new RoutineRequestError(`${field} must remain ${max.toLocaleString("en-US")} characters or fewer after credentials are removed`);
|
|
141
|
+
}
|
|
142
|
+
return redacted;
|
|
143
|
+
}
|
|
144
|
+
function runOn(value) {
|
|
145
|
+
return value ?? "maus";
|
|
146
|
+
}
|
|
147
|
+
function duration(value) {
|
|
148
|
+
const normalized = value ?? 30;
|
|
149
|
+
if (!Number.isInteger(normalized) || normalized < 5 || normalized > 240) {
|
|
150
|
+
throw new RoutineRequestError("durationMinutes must be a whole number from 5 to 240");
|
|
151
|
+
}
|
|
152
|
+
return normalized;
|
|
153
|
+
}
|
|
154
|
+
function timeout(value) {
|
|
155
|
+
if (value == null)
|
|
156
|
+
return value;
|
|
157
|
+
if (!Number.isInteger(value) || value < 5 || value > 240) {
|
|
158
|
+
throw new RoutineRequestError("timeoutMinutes must be a whole number from 5 to 240");
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
function rfc3339Instant(value, offsetMessage) {
|
|
163
|
+
const parts = RFC3339_WITH_OFFSET.exec(value);
|
|
164
|
+
if (!parts)
|
|
165
|
+
throw new RoutineRequestError(offsetMessage);
|
|
166
|
+
const year = Number(parts[1]);
|
|
167
|
+
const month = Number(parts[2]);
|
|
168
|
+
const day = Number(parts[3]);
|
|
169
|
+
const hour = Number(parts[4]);
|
|
170
|
+
const minute = Number(parts[5]);
|
|
171
|
+
const second = Number(parts[6]);
|
|
172
|
+
const offsetHour = Number(parts[7] ?? 0);
|
|
173
|
+
const offsetMinute = Number(parts[8] ?? 0);
|
|
174
|
+
const daysInMonth = month >= 1 && month <= 12
|
|
175
|
+
? new Date(Date.UTC(year, month, 0)).getUTCDate()
|
|
176
|
+
: 0;
|
|
177
|
+
if (day < 1 ||
|
|
178
|
+
day > daysInMonth ||
|
|
179
|
+
hour > 23 ||
|
|
180
|
+
minute > 59 ||
|
|
181
|
+
second > 59 ||
|
|
182
|
+
offsetHour > 23 ||
|
|
183
|
+
offsetMinute > 59) {
|
|
184
|
+
throw new RoutineRequestError("Choose a valid RFC3339 date and time");
|
|
185
|
+
}
|
|
186
|
+
const at = Date.parse(value);
|
|
187
|
+
if (!Number.isFinite(at))
|
|
188
|
+
throw new RoutineRequestError("Choose a valid RFC3339 date and time");
|
|
189
|
+
return at;
|
|
190
|
+
}
|
|
191
|
+
function normalizeSchedule(schedule, now) {
|
|
192
|
+
if (schedule.type === "once") {
|
|
193
|
+
const at = rfc3339Instant(schedule.at, "One-time schedules need an RFC3339 date-time with an explicit timezone offset");
|
|
194
|
+
if (at <= now)
|
|
195
|
+
throw new RoutineRequestError("The scheduled date and time must be in the future");
|
|
196
|
+
return { type: "once", at };
|
|
197
|
+
}
|
|
198
|
+
if (schedule.type === "interval") {
|
|
199
|
+
if (!Number.isInteger(schedule.everyMinutes) || schedule.everyMinutes < 5 || schedule.everyMinutes > 1_440) {
|
|
200
|
+
throw new RoutineRequestError("everyMinutes must be a whole number from 5 to 1440");
|
|
201
|
+
}
|
|
202
|
+
if (schedule.anchorAt === undefined) {
|
|
203
|
+
return { type: "interval", everyMinutes: schedule.everyMinutes };
|
|
204
|
+
}
|
|
205
|
+
const anchorAt = rfc3339Instant(schedule.anchorAt, "Interval starts need an RFC3339 date-time with an explicit timezone offset");
|
|
206
|
+
if (!Number.isSafeInteger(anchorAt) || anchorAt < 0 || anchorAt > MAX_DATE_MS) {
|
|
207
|
+
throw new RoutineRequestError("Choose a valid interval start time");
|
|
208
|
+
}
|
|
209
|
+
return { type: "interval", everyMinutes: schedule.everyMinutes, anchorAt };
|
|
210
|
+
}
|
|
211
|
+
if (!TIME.test(schedule.time)) {
|
|
212
|
+
throw new RoutineRequestError("Weekly schedule time must use 24-hour HH:MM");
|
|
213
|
+
}
|
|
214
|
+
if (schedule.weekdays.length === 0)
|
|
215
|
+
throw new RoutineRequestError("Choose at least one weekday");
|
|
216
|
+
const weekdays = schedule.weekdays.map((day) => {
|
|
217
|
+
// SAFETY: every key in WEEKDAY_NUMBER is lower-case; membership is
|
|
218
|
+
// checked immediately below before the numeric value is retained.
|
|
219
|
+
const number = WEEKDAY_NUMBER[day.toLowerCase()];
|
|
220
|
+
if (number === undefined)
|
|
221
|
+
throw new RoutineRequestError(`Unsupported weekday: ${day}`);
|
|
222
|
+
return number;
|
|
223
|
+
});
|
|
224
|
+
return { type: "daily", time: schedule.time, weekdays: [...new Set(weekdays)].sort() };
|
|
225
|
+
}
|
|
226
|
+
function normalizeDefinition(input, now) {
|
|
227
|
+
const timeoutMinutes = timeout(input.timeoutMinutes);
|
|
228
|
+
return {
|
|
229
|
+
name: text(input.name, "name", 80),
|
|
230
|
+
instructions: text(input.instructions, "instructions", 20_000),
|
|
231
|
+
schedule: normalizeSchedule(input.schedule, now),
|
|
232
|
+
runOn: runOn(input.runOn),
|
|
233
|
+
durationMinutes: duration(input.durationMinutes),
|
|
234
|
+
...(timeoutMinutes == null ? {} : { timeoutMinutes }),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function normalizeChanges(input, now) {
|
|
238
|
+
const changes = {};
|
|
239
|
+
if (input.name !== undefined)
|
|
240
|
+
changes.name = text(input.name, "name", 80);
|
|
241
|
+
if (input.instructions !== undefined)
|
|
242
|
+
changes.instructions = text(input.instructions, "instructions", 20_000);
|
|
243
|
+
if (input.schedule !== undefined)
|
|
244
|
+
changes.schedule = normalizeSchedule(input.schedule, now);
|
|
245
|
+
if (input.runOn !== undefined)
|
|
246
|
+
changes.runOn = runOn(input.runOn);
|
|
247
|
+
if (input.durationMinutes !== undefined)
|
|
248
|
+
changes.durationMinutes = duration(input.durationMinutes);
|
|
249
|
+
if (input.timeoutMinutes !== undefined)
|
|
250
|
+
changes.timeoutMinutes = timeout(input.timeoutMinutes);
|
|
251
|
+
return changes;
|
|
252
|
+
}
|
|
253
|
+
function routineId(value) {
|
|
254
|
+
if (!ROUTINE_ID.test(value))
|
|
255
|
+
throw new RoutineRequestError("Choose a valid routine id");
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function ownedRoutine(manager, id, botId) {
|
|
259
|
+
return manager.listRoutines().find((routine) => routine.id === id && routine.botId === botId) ?? null;
|
|
260
|
+
}
|
|
261
|
+
function normalizedOperation(manager, botId, validated, now) {
|
|
262
|
+
if (validated.action === "create") {
|
|
263
|
+
const operation = {
|
|
264
|
+
action: "create",
|
|
265
|
+
routine: normalizeDefinition(validated.routine, now),
|
|
266
|
+
};
|
|
267
|
+
if (validated.forBot)
|
|
268
|
+
operation.forBot = validated.forBot;
|
|
269
|
+
return operation;
|
|
270
|
+
}
|
|
271
|
+
const id = routineId(validated.routineId);
|
|
272
|
+
const current = ownedRoutine(manager, id, botId);
|
|
273
|
+
if (!current)
|
|
274
|
+
throw new RoutineRequestError("That routine does not exist", 404);
|
|
275
|
+
if (validated.action === "update") {
|
|
276
|
+
return {
|
|
277
|
+
action: "update",
|
|
278
|
+
routineId: id,
|
|
279
|
+
expectedUpdatedAt: current.updatedAt,
|
|
280
|
+
changes: normalizeChanges(validated.changes, now),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (validated.action === "resume" && nextOccurrence(current.schedule, now) === null) {
|
|
284
|
+
throw new RoutineRequestError("That one-time routine's scheduled time has passed. Update it to a new future time before resuming.", 409);
|
|
285
|
+
}
|
|
286
|
+
return { action: validated.action, routineId: id, expectedUpdatedAt: current.updatedAt };
|
|
287
|
+
}
|
|
288
|
+
function asSchedule(schedule, now) {
|
|
289
|
+
if (schedule.type === "once")
|
|
290
|
+
return { type: "once", at: schedule.at };
|
|
291
|
+
if (schedule.type === "interval") {
|
|
292
|
+
return { type: "interval", everyMinutes: schedule.everyMinutes, anchorAt: schedule.anchorAt ?? now };
|
|
293
|
+
}
|
|
294
|
+
return { type: "daily", time: schedule.time, weekdays: [...schedule.weekdays] };
|
|
295
|
+
}
|
|
296
|
+
function nextForOperation(operation, manager, now) {
|
|
297
|
+
if (operation.action === "create") {
|
|
298
|
+
if (operation.routine.schedule.type === "interval" && operation.routine.schedule.anchorAt === undefined)
|
|
299
|
+
return null;
|
|
300
|
+
return nextOccurrence(asSchedule(operation.routine.schedule, now), now);
|
|
301
|
+
}
|
|
302
|
+
const current = manager.listRoutines().find((routine) => routine.id === operation.routineId);
|
|
303
|
+
if (!current)
|
|
304
|
+
return null;
|
|
305
|
+
if (operation.action === "pause" || operation.action === "delete")
|
|
306
|
+
return null;
|
|
307
|
+
if (operation.action === "run_now")
|
|
308
|
+
return now;
|
|
309
|
+
if (operation.action === "resume")
|
|
310
|
+
return nextOccurrence(current.schedule, now);
|
|
311
|
+
if (!("changes" in operation))
|
|
312
|
+
return null;
|
|
313
|
+
if (!current.enabled)
|
|
314
|
+
return null;
|
|
315
|
+
if (operation.changes.schedule?.type === "interval" && operation.changes.schedule.anchorAt === undefined)
|
|
316
|
+
return null;
|
|
317
|
+
const schedule = operation.changes.schedule ? asSchedule(operation.changes.schedule, now) : current.schedule;
|
|
318
|
+
return nextOccurrence(schedule, now);
|
|
319
|
+
}
|
|
320
|
+
function formatInstant(at, timeZone) {
|
|
321
|
+
try {
|
|
322
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
323
|
+
timeZone,
|
|
324
|
+
dateStyle: "medium",
|
|
325
|
+
timeStyle: "short",
|
|
326
|
+
}).format(new Date(at));
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return new Date(at).toISOString();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function scheduleText(schedule, timeZone) {
|
|
333
|
+
if (schedule.type === "once")
|
|
334
|
+
return `${formatInstant(schedule.at, timeZone)} (${timeZone})`;
|
|
335
|
+
if (schedule.type === "interval") {
|
|
336
|
+
return schedule.anchorAt === undefined
|
|
337
|
+
? `Every ${schedule.everyMinutes} minutes, starting one interval after confirmation`
|
|
338
|
+
: `Every ${schedule.everyMinutes} minutes, anchored at ${formatInstant(schedule.anchorAt, timeZone)} (${timeZone})`;
|
|
339
|
+
}
|
|
340
|
+
const days = schedule.weekdays.map((day) => WEEKDAY_LABEL[day]).join(", ");
|
|
341
|
+
return `${days} at ${schedule.time} (${timeZone})`;
|
|
342
|
+
}
|
|
343
|
+
function effectiveDefinition(operation, manager) {
|
|
344
|
+
if (operation.action === "create")
|
|
345
|
+
return operation.routine;
|
|
346
|
+
const existing = manager.listRoutines().find((routine) => routine.id === operation.routineId);
|
|
347
|
+
if (!existing)
|
|
348
|
+
return null;
|
|
349
|
+
const base = {
|
|
350
|
+
name: existing.name,
|
|
351
|
+
instructions: existing.prompt,
|
|
352
|
+
schedule: { ...existing.schedule },
|
|
353
|
+
runOn: existing.runOn,
|
|
354
|
+
durationMinutes: existing.durationMinutes,
|
|
355
|
+
...(existing.timeoutMinutes === undefined ? {} : { timeoutMinutes: existing.timeoutMinutes }),
|
|
356
|
+
};
|
|
357
|
+
if (operation.action !== "update")
|
|
358
|
+
return base;
|
|
359
|
+
const { timeoutMinutes, ...changes } = operation.changes;
|
|
360
|
+
const merged = { ...base, ...changes };
|
|
361
|
+
if (timeoutMinutes === null)
|
|
362
|
+
delete merged.timeoutMinutes;
|
|
363
|
+
else if (timeoutMinutes !== undefined)
|
|
364
|
+
merged.timeoutMinutes = timeoutMinutes;
|
|
365
|
+
return merged;
|
|
366
|
+
}
|
|
367
|
+
function cardCopy(operation, manager, timeZone, now) {
|
|
368
|
+
const definition = effectiveDefinition(operation, manager);
|
|
369
|
+
const actionCopy = ACTION_COPY[operation.action];
|
|
370
|
+
const actionLabel = actionCopy.title;
|
|
371
|
+
const name = redactSecretsInText(definition?.name ?? "routine");
|
|
372
|
+
const forBot = operation.action === "create" ? operation.forBot : undefined;
|
|
373
|
+
const forSuffix = forBot ? ` for @${redactSecretsInText(forBot.name)}` : "";
|
|
374
|
+
const title = `${actionLabel} “${name}”${forSuffix}?`;
|
|
375
|
+
if (!definition) {
|
|
376
|
+
return {
|
|
377
|
+
title,
|
|
378
|
+
summary: title,
|
|
379
|
+
detail: `Action: ${actionCopy.detail}\nName: ${name}`,
|
|
380
|
+
nextRunAt: null,
|
|
381
|
+
tool: "manage_routine",
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const nextRunAt = nextForOperation(operation, manager, now);
|
|
385
|
+
const when = operation.action === "run_now" ? "Now" : scheduleText(definition.schedule, timeZone);
|
|
386
|
+
const destination = definition.runOn === "cloud" ? "Cloud VM" : "This OpenMausBot setup";
|
|
387
|
+
const current = operation.action === "create"
|
|
388
|
+
? null
|
|
389
|
+
: manager.listRoutines().find((routine) => routine.id === operation.routineId) ?? null;
|
|
390
|
+
const remainsPaused = operation.action === "update" && current?.enabled === false;
|
|
391
|
+
const deferredInterval = definition.schedule.type === "interval" && definition.schedule.anchorAt === undefined;
|
|
392
|
+
const nextDescription = remainsPaused
|
|
393
|
+
? "None — this routine remains paused"
|
|
394
|
+
: deferredInterval
|
|
395
|
+
? "One interval after confirmation"
|
|
396
|
+
: nextRunAt !== null
|
|
397
|
+
? formatInstant(nextRunAt, timeZone)
|
|
398
|
+
: operation.action === "pause"
|
|
399
|
+
? "None — this routine will be paused"
|
|
400
|
+
: operation.action === "delete"
|
|
401
|
+
? "None — this routine will be deleted"
|
|
402
|
+
: "None";
|
|
403
|
+
const status = remainsPaused ? " · Remains paused" : "";
|
|
404
|
+
// Existing routines may predate nested-card redaction. The approval still
|
|
405
|
+
// shows every instruction, but credential-shaped values never travel back
|
|
406
|
+
// through the bot's MCP response or into the transcript.
|
|
407
|
+
const visibleInstructions = redactSecretsInText(definition.instructions);
|
|
408
|
+
const runLimit = definition.timeoutMinutes === undefined
|
|
409
|
+
? "no run limit"
|
|
410
|
+
: `${definition.timeoutMinutes} min limit`;
|
|
411
|
+
return {
|
|
412
|
+
title,
|
|
413
|
+
summary: `${actionLabel} “${name}”${forSuffix} · ${when} · ${destination} · ${runLimit}${status}`,
|
|
414
|
+
detail: [
|
|
415
|
+
`Action: ${actionCopy.detail}`,
|
|
416
|
+
`Name: ${name}`,
|
|
417
|
+
...(forBot ? [`For: @${redactSecretsInText(forBot.name)} — each run uses that bot's engine and permissions`] : []),
|
|
418
|
+
`Schedule: ${when}`,
|
|
419
|
+
`Next run: ${nextDescription}`,
|
|
420
|
+
`Runs on: ${destination}`,
|
|
421
|
+
`Run limit: ${definition.timeoutMinutes === undefined ? "No limit" : `${definition.timeoutMinutes} minutes`}`,
|
|
422
|
+
"",
|
|
423
|
+
"Instructions:",
|
|
424
|
+
visibleInstructions,
|
|
425
|
+
].join("\n"),
|
|
426
|
+
nextRunAt,
|
|
427
|
+
tool: operation.action === "create" ? "schedule_routine" : "manage_routine",
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function inputFromDefinition(definition, botId, now) {
|
|
431
|
+
return {
|
|
432
|
+
name: definition.name,
|
|
433
|
+
prompt: definition.instructions,
|
|
434
|
+
botId,
|
|
435
|
+
runOn: definition.runOn,
|
|
436
|
+
enabled: true,
|
|
437
|
+
schedule: asSchedule(definition.schedule, now),
|
|
438
|
+
durationMinutes: definition.durationMinutes,
|
|
439
|
+
...(definition.timeoutMinutes === undefined ? {} : { timeoutMinutes: definition.timeoutMinutes }),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function updateFromChanges(changes, now) {
|
|
443
|
+
const patch = {};
|
|
444
|
+
if (changes.name !== undefined)
|
|
445
|
+
patch.name = changes.name;
|
|
446
|
+
if (changes.instructions !== undefined)
|
|
447
|
+
patch.prompt = changes.instructions;
|
|
448
|
+
if (changes.schedule !== undefined)
|
|
449
|
+
patch.schedule = asSchedule(changes.schedule, now);
|
|
450
|
+
if (changes.runOn !== undefined)
|
|
451
|
+
patch.runOn = changes.runOn;
|
|
452
|
+
if (changes.durationMinutes !== undefined)
|
|
453
|
+
patch.durationMinutes = changes.durationMinutes;
|
|
454
|
+
if (changes.timeoutMinutes !== undefined)
|
|
455
|
+
patch.timeoutMinutes = changes.timeoutMinutes;
|
|
456
|
+
return patch;
|
|
457
|
+
}
|
|
458
|
+
function canonicalValue(value) {
|
|
459
|
+
if (Array.isArray(value))
|
|
460
|
+
return value.map(canonicalValue);
|
|
461
|
+
const parsedObject = jsonObjectSchema.safeParse(value);
|
|
462
|
+
if (!parsedObject.success)
|
|
463
|
+
return value;
|
|
464
|
+
const sorted = {};
|
|
465
|
+
for (const key of Object.keys(parsedObject.data).sort()) {
|
|
466
|
+
const child = parsedObject.data[key];
|
|
467
|
+
if (child !== undefined)
|
|
468
|
+
sorted[key] = canonicalValue(child);
|
|
469
|
+
}
|
|
470
|
+
return sorted;
|
|
471
|
+
}
|
|
472
|
+
/** Bind exact-once recovery to the immutable card owner as well as its
|
|
473
|
+
* operation. Recursive key sorting keeps old receipts valid if a future
|
|
474
|
+
* schema refactor changes object construction order. */
|
|
475
|
+
export function routineRequestFingerprint(payload, messageId) {
|
|
476
|
+
const document = parseJson(JSON.stringify({
|
|
477
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
478
|
+
cardVersion: payload.version,
|
|
479
|
+
requestId: payload.requestId,
|
|
480
|
+
messageId,
|
|
481
|
+
botId: payload.botId,
|
|
482
|
+
threadId: payload.threadId,
|
|
483
|
+
operation: payload.operation,
|
|
484
|
+
}));
|
|
485
|
+
return createHash("sha256").update(JSON.stringify(canonicalValue(document))).digest("hex");
|
|
486
|
+
}
|
|
487
|
+
function verifyManageSnapshot(operation, manager, botId) {
|
|
488
|
+
const current = ownedRoutine(manager, operation.routineId, botId);
|
|
489
|
+
if (!current)
|
|
490
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
491
|
+
if (current.updatedAt !== operation.expectedUpdatedAt) {
|
|
492
|
+
throw new RoutineRequestError("That routine changed after this confirmation card was prepared. Ask the bot to review it and propose the action again.", 409);
|
|
493
|
+
}
|
|
494
|
+
return current;
|
|
495
|
+
}
|
|
496
|
+
function requestCommit(payload, messageId) {
|
|
497
|
+
return {
|
|
498
|
+
requestId: payload.requestId,
|
|
499
|
+
messageId,
|
|
500
|
+
botId: payload.botId,
|
|
501
|
+
threadId: payload.threadId,
|
|
502
|
+
action: payload.operation.action,
|
|
503
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
504
|
+
fingerprint: routineRequestFingerprint(payload, messageId),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function revalidateOperation(operation, manager, botId, now) {
|
|
508
|
+
const current = operation.action === "create"
|
|
509
|
+
? null
|
|
510
|
+
: verifyManageSnapshot(operation, manager, botId);
|
|
511
|
+
const schedule = operation.action === "create"
|
|
512
|
+
? operation.routine.schedule
|
|
513
|
+
: operation.action === "update"
|
|
514
|
+
? operation.changes.schedule
|
|
515
|
+
: undefined;
|
|
516
|
+
if (schedule?.type === "once" && schedule.at <= now) {
|
|
517
|
+
throw new RoutineRequestError("That one-time schedule is now in the past. Ask the bot to propose a new time.", 409);
|
|
518
|
+
}
|
|
519
|
+
if (operation.action === "resume") {
|
|
520
|
+
if (!current)
|
|
521
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
522
|
+
if (nextOccurrence(current.schedule, now) === null) {
|
|
523
|
+
throw new RoutineRequestError("That one-time routine's scheduled time has passed. Update it to a new future time before resuming.", 409);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
export class RoutineRequestService {
|
|
528
|
+
store;
|
|
529
|
+
routines;
|
|
530
|
+
now;
|
|
531
|
+
timeZone;
|
|
532
|
+
cloudReady;
|
|
533
|
+
canPersist;
|
|
534
|
+
validateTarget;
|
|
535
|
+
constructor(options) {
|
|
536
|
+
this.store = options.store;
|
|
537
|
+
this.routines = options.routines;
|
|
538
|
+
this.now = options.now ?? Date.now;
|
|
539
|
+
this.timeZone = options.timeZone ?? (() => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC");
|
|
540
|
+
this.cloudReady = options.cloudReady;
|
|
541
|
+
this.canPersist = options.canPersist;
|
|
542
|
+
this.validateTarget = options.validateTarget;
|
|
543
|
+
}
|
|
544
|
+
async propose(args) {
|
|
545
|
+
const botId = text(args.botId, "botId", 128);
|
|
546
|
+
const threadId = text(args.threadId, "threadId", 128);
|
|
547
|
+
const at = this.now();
|
|
548
|
+
const parsedProposal = routineProposalSchema.safeParse(args.proposal);
|
|
549
|
+
if (!parsedProposal.success) {
|
|
550
|
+
throw new RoutineRequestError(schemaIssue(parsedProposal.error, "Invalid routine proposal"));
|
|
551
|
+
}
|
|
552
|
+
const operation = normalizedOperation(this.routines, botId, parsedProposal.data, at);
|
|
553
|
+
if (operation.action === "create" && operation.forBot && this.validateTarget) {
|
|
554
|
+
const refusal = this.validateTarget(botId, operation.forBot);
|
|
555
|
+
if (refusal)
|
|
556
|
+
throw new RoutineRequestError(refusal, 403);
|
|
557
|
+
}
|
|
558
|
+
await this.requireCloudReadiness(operation);
|
|
559
|
+
// The readiness probe is asynchronous. Another request can edit or
|
|
560
|
+
// delete the routine while it is in flight, so re-check the captured
|
|
561
|
+
// revision before rendering and persisting the confirmation snapshot.
|
|
562
|
+
const cardAt = this.now();
|
|
563
|
+
revalidateOperation(operation, this.routines, botId, cardAt);
|
|
564
|
+
const requestId = newId();
|
|
565
|
+
const payload = {
|
|
566
|
+
version: 1,
|
|
567
|
+
requestId,
|
|
568
|
+
botId,
|
|
569
|
+
threadId,
|
|
570
|
+
createdAt: cardAt,
|
|
571
|
+
operation,
|
|
572
|
+
};
|
|
573
|
+
const timeZone = this.timeZone();
|
|
574
|
+
const copy = cardCopy(operation, this.routines, timeZone, cardAt);
|
|
575
|
+
const messageInput = {
|
|
576
|
+
role: "bot",
|
|
577
|
+
kind: "options",
|
|
578
|
+
card: {
|
|
579
|
+
title: copy.title,
|
|
580
|
+
subtitle: copy.detail,
|
|
581
|
+
options: ["Confirm", "Cancel"],
|
|
582
|
+
requestId,
|
|
583
|
+
tool: copy.tool,
|
|
584
|
+
routineRequest: payload,
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
if (args.from)
|
|
588
|
+
messageInput.from = args.from;
|
|
589
|
+
// This check and append are deliberately adjacent and synchronous. JS
|
|
590
|
+
// cannot interleave another completed proposal between the capacity /
|
|
591
|
+
// ownership decision and the durable transcript write.
|
|
592
|
+
const persistence = this.canPersist?.(botId, threadId);
|
|
593
|
+
if (persistence && !persistence.ok) {
|
|
594
|
+
throw new RoutineRequestError(persistence.error, persistence.status);
|
|
595
|
+
}
|
|
596
|
+
if (args.canCommit && !args.canCommit()) {
|
|
597
|
+
throw new RoutineRequestError("The requesting turn ended before this proposal could be saved", 401);
|
|
598
|
+
}
|
|
599
|
+
const message = this.store.appendMessage(threadId, messageInput);
|
|
600
|
+
return {
|
|
601
|
+
requestId,
|
|
602
|
+
messageId: message.id,
|
|
603
|
+
title: copy.title,
|
|
604
|
+
summary: copy.summary,
|
|
605
|
+
detail: copy.detail,
|
|
606
|
+
nextRunAt: copy.nextRunAt,
|
|
607
|
+
timeZone,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async requireCloudReadiness(operation) {
|
|
611
|
+
if (!this.cloudReady || operation.action === "pause" || operation.action === "delete")
|
|
612
|
+
return;
|
|
613
|
+
const definition = effectiveDefinition(operation, this.routines);
|
|
614
|
+
if (definition?.runOn !== "cloud")
|
|
615
|
+
return;
|
|
616
|
+
let readiness;
|
|
617
|
+
try {
|
|
618
|
+
readiness = await this.cloudReady();
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
622
|
+
throw new RoutineRequestError(`Could not verify cloud readiness: ${detail}`, 503);
|
|
623
|
+
}
|
|
624
|
+
if (readiness.ready)
|
|
625
|
+
return;
|
|
626
|
+
throw new RoutineRequestError(readiness.reason?.trim() || "Cloud execution is not configured yet. Set up a cloud computer first.", 409);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Claims a routine card even after it was settled. That distinction is
|
|
630
|
+
* important: duplicate clicks must never fall through to a provider that
|
|
631
|
+
* did not create the request id.
|
|
632
|
+
*/
|
|
633
|
+
resolve(args) {
|
|
634
|
+
const message = this.store
|
|
635
|
+
.messagesFor(args.threadId)
|
|
636
|
+
.find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.routineRequest);
|
|
637
|
+
const card = message?.card;
|
|
638
|
+
const rawPayload = card?.routineRequest;
|
|
639
|
+
if (!message || !card || !rawPayload)
|
|
640
|
+
return { claimed: false, state: "not_found" };
|
|
641
|
+
if (args.behavior !== "allow" && args.behavior !== "deny") {
|
|
642
|
+
return {
|
|
643
|
+
claimed: true,
|
|
644
|
+
state: "invalid",
|
|
645
|
+
error: "Routine confirmations must be confirmed or cancelled",
|
|
646
|
+
status: 400,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const parsedPayload = routineRequestCardDataSchema.safeParse(rawPayload);
|
|
650
|
+
if (!parsedPayload.success) {
|
|
651
|
+
if (card.answered)
|
|
652
|
+
return { claimed: true, state: "already_settled", behavior: card.answered };
|
|
653
|
+
const recovered = this.settleCommittedReceipt(args, message.id, card);
|
|
654
|
+
if (recovered)
|
|
655
|
+
return recovered;
|
|
656
|
+
// Cancelling is always safe and must remain possible even if an older
|
|
657
|
+
// persisted payload no longer passes today's schema. Otherwise that
|
|
658
|
+
// durable card would own the composer forever with no escape hatch.
|
|
659
|
+
if (args.behavior === "deny") {
|
|
660
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: "deny", held: undefined } });
|
|
661
|
+
return { claimed: true, state: "denied" };
|
|
662
|
+
}
|
|
663
|
+
const detail = schemaIssue(parsedPayload.error, "This routine request is invalid");
|
|
664
|
+
this.store.patchMessage(args.threadId, message.id, {
|
|
665
|
+
card: { ...card, held: redactSecretsInText(detail).slice(0, 500) },
|
|
666
|
+
});
|
|
667
|
+
return { claimed: true, state: "invalid", error: detail, status: 400 };
|
|
668
|
+
}
|
|
669
|
+
const payload = parsedPayload.data;
|
|
670
|
+
if (payload.requestId !== args.requestId) {
|
|
671
|
+
const recovered = this.settleCommittedReceipt(args, message.id, card);
|
|
672
|
+
if (recovered)
|
|
673
|
+
return recovered;
|
|
674
|
+
if (args.behavior === "deny") {
|
|
675
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: "deny", held: undefined } });
|
|
676
|
+
return { claimed: true, state: "denied" };
|
|
677
|
+
}
|
|
678
|
+
return { claimed: true, state: "invalid", error: "This routine request id does not match its confirmation card", status: 400 };
|
|
679
|
+
}
|
|
680
|
+
if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
|
|
681
|
+
const recovered = this.settleCommittedReceipt(args, message.id, card);
|
|
682
|
+
if (recovered)
|
|
683
|
+
return recovered;
|
|
684
|
+
if (args.behavior === "deny") {
|
|
685
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: "deny", held: undefined } });
|
|
686
|
+
return { claimed: true, state: "denied" };
|
|
687
|
+
}
|
|
688
|
+
return { claimed: true, state: "invalid", error: "This routine request belongs to another conversation", status: 403 };
|
|
689
|
+
}
|
|
690
|
+
if (card.answered) {
|
|
691
|
+
this.forgetSettledReceipt(payload, message.id);
|
|
692
|
+
return { claimed: true, state: "already_settled", behavior: card.answered };
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
const fingerprint = routineRequestFingerprint(payload, message.id);
|
|
696
|
+
const receipt = this.routines.routineRequestReceipt(payload.requestId);
|
|
697
|
+
if (receipt) {
|
|
698
|
+
if (receipt.botId !== payload.botId ||
|
|
699
|
+
receipt.threadId !== payload.threadId ||
|
|
700
|
+
receipt.messageId !== message.id ||
|
|
701
|
+
receipt.action !== payload.operation.action ||
|
|
702
|
+
receipt.fingerprintVersion !== ROUTINE_REQUEST_FINGERPRINT_VERSION ||
|
|
703
|
+
receipt.fingerprint !== fingerprint) {
|
|
704
|
+
throw new RoutineRequestError("This routine request does not match its durable commit receipt", 409);
|
|
705
|
+
}
|
|
706
|
+
return this.settleApplied(args.threadId, message.id, card, payload, receipt.resultId, receipt.appliedAt);
|
|
707
|
+
}
|
|
708
|
+
if (args.behavior === "deny") {
|
|
709
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: "deny", held: undefined } });
|
|
710
|
+
return { claimed: true, state: "denied" };
|
|
711
|
+
}
|
|
712
|
+
revalidateOperation(payload.operation, this.routines, payload.botId, this.now());
|
|
713
|
+
if (payload.operation.action === "create" && payload.operation.forBot && this.validateTarget) {
|
|
714
|
+
const refusal = this.validateTarget(payload.botId, payload.operation.forBot);
|
|
715
|
+
if (refusal)
|
|
716
|
+
throw new RoutineRequestError(refusal, 404);
|
|
717
|
+
}
|
|
718
|
+
const resultId = this.apply(payload, message.id, fingerprint);
|
|
719
|
+
return this.settleApplied(args.threadId, message.id, card, payload, resultId);
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
const status = error instanceof RoutineRequestError ? error.status : 400;
|
|
723
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
724
|
+
this.store.patchMessage(args.threadId, message.id, {
|
|
725
|
+
card: { ...card, held: redactSecretsInText(detail).slice(0, 500) },
|
|
726
|
+
});
|
|
727
|
+
return {
|
|
728
|
+
claimed: true,
|
|
729
|
+
state: "invalid",
|
|
730
|
+
error: detail,
|
|
731
|
+
status,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
settleApplied(threadId, messageId, card, payload, resultId, appliedAt = this.now()) {
|
|
736
|
+
const applied = {
|
|
737
|
+
...payload,
|
|
738
|
+
appliedAt,
|
|
739
|
+
resultId,
|
|
740
|
+
};
|
|
741
|
+
const settled = this.store.patchMessage(threadId, messageId, {
|
|
742
|
+
card: { ...card, answered: "allow", held: undefined, routineRequest: applied },
|
|
743
|
+
});
|
|
744
|
+
if (!settled)
|
|
745
|
+
throw new RoutineRequestError("This routine confirmation card is no longer available", 409);
|
|
746
|
+
this.forgetSettledReceipt(payload, messageId);
|
|
747
|
+
return { claimed: true, state: "applied", action: payload.operation.action, resultId };
|
|
748
|
+
}
|
|
749
|
+
forgetSettledReceipt(payload, messageId) {
|
|
750
|
+
this.forgetReceipt(requestCommit(payload, messageId));
|
|
751
|
+
}
|
|
752
|
+
settleCommittedReceipt(args, messageId, card) {
|
|
753
|
+
const receipt = this.routines.routineRequestReceipt(args.requestId);
|
|
754
|
+
if (!receipt)
|
|
755
|
+
return null;
|
|
756
|
+
if (receipt.botId !== args.botId || receipt.threadId !== args.threadId || receipt.messageId !== messageId) {
|
|
757
|
+
return {
|
|
758
|
+
claimed: true,
|
|
759
|
+
state: "invalid",
|
|
760
|
+
error: "This committed routine request belongs to another conversation",
|
|
761
|
+
status: 403,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const settled = this.store.patchMessage(args.threadId, messageId, {
|
|
765
|
+
card: { ...card, answered: "allow", held: undefined },
|
|
766
|
+
});
|
|
767
|
+
if (!settled) {
|
|
768
|
+
return {
|
|
769
|
+
claimed: true,
|
|
770
|
+
state: "invalid",
|
|
771
|
+
error: "This routine confirmation card is no longer available",
|
|
772
|
+
status: 409,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
this.forgetReceipt(receipt);
|
|
776
|
+
return {
|
|
777
|
+
claimed: true,
|
|
778
|
+
state: "applied",
|
|
779
|
+
action: receipt.action,
|
|
780
|
+
resultId: receipt.resultId,
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
forgetReceipt(request) {
|
|
784
|
+
try {
|
|
785
|
+
this.routines.forgetRoutineRequestReceipt(request);
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
// The transcript is already durably settled. Retaining a redundant
|
|
789
|
+
// receipt after a cleanup write failure is safe and a later duplicate
|
|
790
|
+
// response will retry this cleanup.
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
apply(payload, messageId, fingerprint) {
|
|
794
|
+
const operation = payload.operation;
|
|
795
|
+
const confirmationAt = this.now();
|
|
796
|
+
switch (operation.action) {
|
|
797
|
+
case "create":
|
|
798
|
+
return this.routines.create(inputFromDefinition(operation.routine, operation.forBot?.botId ?? payload.botId, confirmationAt), {
|
|
799
|
+
requestId: payload.requestId,
|
|
800
|
+
messageId,
|
|
801
|
+
botId: payload.botId,
|
|
802
|
+
threadId: payload.threadId,
|
|
803
|
+
action: "create",
|
|
804
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
805
|
+
fingerprint,
|
|
806
|
+
}).id;
|
|
807
|
+
case "update": {
|
|
808
|
+
verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
809
|
+
const updated = this.routines.update(operation.routineId, updateFromChanges(operation.changes, confirmationAt), {
|
|
810
|
+
requestId: payload.requestId,
|
|
811
|
+
messageId,
|
|
812
|
+
botId: payload.botId,
|
|
813
|
+
threadId: payload.threadId,
|
|
814
|
+
action: "update",
|
|
815
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
816
|
+
fingerprint,
|
|
817
|
+
});
|
|
818
|
+
if (!updated)
|
|
819
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
820
|
+
return updated.id;
|
|
821
|
+
}
|
|
822
|
+
case "pause":
|
|
823
|
+
case "resume": {
|
|
824
|
+
verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
825
|
+
const updated = this.routines.update(operation.routineId, { enabled: operation.action === "resume" }, {
|
|
826
|
+
requestId: payload.requestId,
|
|
827
|
+
messageId,
|
|
828
|
+
botId: payload.botId,
|
|
829
|
+
threadId: payload.threadId,
|
|
830
|
+
action: operation.action,
|
|
831
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
832
|
+
fingerprint,
|
|
833
|
+
});
|
|
834
|
+
if (!updated)
|
|
835
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
836
|
+
return updated.id;
|
|
837
|
+
}
|
|
838
|
+
case "run_now": {
|
|
839
|
+
verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
840
|
+
const run = this.routines.runNow(operation.routineId, {
|
|
841
|
+
requestId: payload.requestId,
|
|
842
|
+
messageId,
|
|
843
|
+
botId: payload.botId,
|
|
844
|
+
threadId: payload.threadId,
|
|
845
|
+
action: "run_now",
|
|
846
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
847
|
+
fingerprint,
|
|
848
|
+
});
|
|
849
|
+
if (!run)
|
|
850
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
851
|
+
return run.id;
|
|
852
|
+
}
|
|
853
|
+
case "delete":
|
|
854
|
+
verifyManageSnapshot(operation, this.routines, payload.botId);
|
|
855
|
+
if (!this.routines.remove(operation.routineId, {
|
|
856
|
+
requestId: payload.requestId,
|
|
857
|
+
messageId,
|
|
858
|
+
botId: payload.botId,
|
|
859
|
+
threadId: payload.threadId,
|
|
860
|
+
action: "delete",
|
|
861
|
+
fingerprintVersion: ROUTINE_REQUEST_FINGERPRINT_VERSION,
|
|
862
|
+
fingerprint,
|
|
863
|
+
})) {
|
|
864
|
+
throw new RoutineRequestError("That routine no longer exists", 404);
|
|
865
|
+
}
|
|
866
|
+
return operation.routineId;
|
|
867
|
+
default:
|
|
868
|
+
throw new RoutineRequestError("Unsupported persisted routine action");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|