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,945 @@
|
|
|
1
|
+
// A project API key (ak_…) creates/reuses one Composio Session. That
|
|
2
|
+
// Session owns connection state, auth links and the MCP endpoint.
|
|
3
|
+
import { saveConfig } from "./config.js";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { SPAWNED_PROXIES } from "./proxy-paths.js";
|
|
7
|
+
import { managedConnectorUnavailableReason } from "../shared/connector-availability.js";
|
|
8
|
+
const DEFAULT_BACKEND_ORIGIN = "https://backend.composio.dev";
|
|
9
|
+
function apiBase() {
|
|
10
|
+
return (process.env.OMB_COMPOSIO_API ?? `${DEFAULT_BACKEND_ORIGIN}/api/v3.1`).replace(/\/$/, "");
|
|
11
|
+
}
|
|
12
|
+
function toolkitBase() {
|
|
13
|
+
return (process.env.OMB_COMPOSIO_TOOLKITS_API ?? `${DEFAULT_BACKEND_ORIGIN}/api/v3`).replace(/\/$/, "");
|
|
14
|
+
}
|
|
15
|
+
const sessionResponseSchema = z.object({
|
|
16
|
+
session_id: z.string().min(1),
|
|
17
|
+
mcp: z.object({ type: z.enum(["http", "sse"]), url: z.string().min(1) }),
|
|
18
|
+
config: z.object({
|
|
19
|
+
user_id: z.string().optional(),
|
|
20
|
+
multi_account: z.object({
|
|
21
|
+
enable: z.boolean().optional(),
|
|
22
|
+
max_accounts_per_toolkit: z.number().optional(),
|
|
23
|
+
require_explicit_selection: z.boolean().optional(),
|
|
24
|
+
}).optional(),
|
|
25
|
+
/** toolkit slug → the project's own auth config the Session uses for it */
|
|
26
|
+
auth_configs: z.record(z.string(), z.string()).optional(),
|
|
27
|
+
}).optional(),
|
|
28
|
+
});
|
|
29
|
+
// A project's own auth configs (bring-your-own OAuth app, API-key toolkits
|
|
30
|
+
// such as twitter that Composio does not manage). A Session only uses one
|
|
31
|
+
// when it was created with the config's id under `auth_configs`.
|
|
32
|
+
const authConfigItemSchema = z.object({
|
|
33
|
+
id: z.string().optional(),
|
|
34
|
+
status: z.string().nullable().optional(),
|
|
35
|
+
is_composio_managed: z.boolean().optional(),
|
|
36
|
+
is_enabled_for_tool_router: z.boolean().nullable().optional(),
|
|
37
|
+
last_updated_at: z.string().nullable().optional(),
|
|
38
|
+
toolkit: z.object({ slug: z.string().optional() }).optional(),
|
|
39
|
+
});
|
|
40
|
+
const authConfigsPageSchema = z.object({
|
|
41
|
+
items: z.array(authConfigItemSchema).optional(),
|
|
42
|
+
next_cursor: z.string().nullable().optional(),
|
|
43
|
+
});
|
|
44
|
+
const MAX_AUTH_CONFIG_PAGES = 20;
|
|
45
|
+
const connectedAccountResponseSchema = z.object({
|
|
46
|
+
id: z.string().optional(),
|
|
47
|
+
alias: z.string().nullable().optional(),
|
|
48
|
+
status: z.string().optional(),
|
|
49
|
+
updated_at: z.string().optional(),
|
|
50
|
+
toolkit: z.object({ slug: z.string().optional() }).optional(),
|
|
51
|
+
});
|
|
52
|
+
const connectedAccountsPageSchema = z.object({
|
|
53
|
+
items: z.array(connectedAccountResponseSchema),
|
|
54
|
+
next_cursor: z.string().nullable().optional(),
|
|
55
|
+
});
|
|
56
|
+
const toolkitItemSchema = z.object({
|
|
57
|
+
slug: z.string().optional(),
|
|
58
|
+
is_no_auth: z.boolean().optional(),
|
|
59
|
+
connected_account: z.object({ id: z.string().optional(), status: z.string().optional() }).nullable().optional(),
|
|
60
|
+
});
|
|
61
|
+
const toolkitPageSchema = z.object({
|
|
62
|
+
items: z.array(toolkitItemSchema).optional(),
|
|
63
|
+
next_cursor: z.string().nullable().optional(),
|
|
64
|
+
});
|
|
65
|
+
const connectorServiceSchema = z.object({
|
|
66
|
+
connected: z.boolean(),
|
|
67
|
+
pending: z.boolean().optional(),
|
|
68
|
+
status: z.string().optional(),
|
|
69
|
+
accounts: z.array(z.object({ id: z.string(), alias: z.string().optional(), status: z.string() })).optional(),
|
|
70
|
+
});
|
|
71
|
+
const connectorServicesResponseSchema = z.object({ services: z.record(z.string(), connectorServiceSchema).optional() });
|
|
72
|
+
const removalResponseSchema = z.object({ removed: z.number() });
|
|
73
|
+
const authUrlResponseSchema = z.object({ url: z.string().optional() });
|
|
74
|
+
const linkResponseSchema = z.object({ redirect_url: z.string().optional() });
|
|
75
|
+
const MULTI_ACCOUNT_CONFIG = {
|
|
76
|
+
enable: true,
|
|
77
|
+
max_accounts_per_toolkit: 5,
|
|
78
|
+
require_explicit_selection: true,
|
|
79
|
+
};
|
|
80
|
+
const MAX_CONNECTED_ACCOUNT_PAGES = 100;
|
|
81
|
+
const ACCOUNT_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
82
|
+
const printableAliasSchema = z.string().min(1).max(64).refine((value) => {
|
|
83
|
+
for (const character of value) {
|
|
84
|
+
const codePoint = character.codePointAt(0);
|
|
85
|
+
if (codePoint === undefined || codePoint < 32 || codePoint === 127)
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
let managedBrokerAccess;
|
|
91
|
+
const managedBrokerMessageSchema = z.record(z.string(), z.unknown());
|
|
92
|
+
const managedBrokerToken = /^[0-9a-f]{64}$/;
|
|
93
|
+
function normalizeManagedBrokerUrl(value) {
|
|
94
|
+
const url = new URL(value);
|
|
95
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
96
|
+
throw new Error("The connected-apps service URL must not include credentials, a query, or a fragment");
|
|
97
|
+
}
|
|
98
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
99
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) {
|
|
100
|
+
throw new Error("The connected-apps service must use HTTPS");
|
|
101
|
+
}
|
|
102
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
103
|
+
}
|
|
104
|
+
export function applyManagedBrokerMessage(message) {
|
|
105
|
+
const parsed = managedBrokerMessageSchema.safeParse(message);
|
|
106
|
+
if (!parsed.success ||
|
|
107
|
+
parsed.data.type !== "openmausbot:managed-composio" ||
|
|
108
|
+
!Object.hasOwn(parsed.data, "access")) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
setManagedBrokerAccess(parsed.data.access);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
export function setManagedBrokerAccess(access) {
|
|
115
|
+
if (access === null) {
|
|
116
|
+
managedBrokerAccess = null;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const parsed = z.object({ url: z.string().url(), token: z.string().regex(managedBrokerToken) }).strict().parse(access);
|
|
120
|
+
managedBrokerAccess = { url: normalizeManagedBrokerUrl(parsed.url), token: parsed.token };
|
|
121
|
+
}
|
|
122
|
+
function brokerAccess() {
|
|
123
|
+
if (managedBrokerAccess !== undefined)
|
|
124
|
+
return managedBrokerAccess;
|
|
125
|
+
const url = process.env.OMB_COMPOSIO_BROKER_URL?.trim();
|
|
126
|
+
const token = process.env.OMB_COMPOSIO_BROKER_TOKEN?.trim();
|
|
127
|
+
if (!url || !token)
|
|
128
|
+
return null;
|
|
129
|
+
if (!managedBrokerToken.test(token))
|
|
130
|
+
throw new Error("The connected-apps service token is invalid");
|
|
131
|
+
return { url: normalizeManagedBrokerUrl(url), token };
|
|
132
|
+
}
|
|
133
|
+
/** A user-owned project is an explicit choice and must win over the packaged
|
|
134
|
+
* app's managed broker. An empty key means that choice was cleared, so the
|
|
135
|
+
* managed service may take over again without a restart. */
|
|
136
|
+
function projectApiKey(cfg) {
|
|
137
|
+
const apiKey = cfg.composio?.apiKey?.trim();
|
|
138
|
+
return apiKey || null;
|
|
139
|
+
}
|
|
140
|
+
/** Composio's toolkit slug is `twitter`. Keep accepting the old `x` alias at
|
|
141
|
+
* every local boundary so saved connector cards and model calls keep working. */
|
|
142
|
+
function canonicalToolkitSlug(slug) {
|
|
143
|
+
const normalized = slug.trim().toLowerCase();
|
|
144
|
+
return normalized === "x" ? "twitter" : normalized;
|
|
145
|
+
}
|
|
146
|
+
/** Keep credential-backed caches and transport sessions separated without
|
|
147
|
+
* retaining another plaintext copy of the credential as their identity. */
|
|
148
|
+
function backendFingerprint(kind, endpoint, credential) {
|
|
149
|
+
return createHash("sha256")
|
|
150
|
+
.update(kind)
|
|
151
|
+
.update("\0")
|
|
152
|
+
.update(endpoint)
|
|
153
|
+
.update("\0")
|
|
154
|
+
.update(credential)
|
|
155
|
+
.digest("hex");
|
|
156
|
+
}
|
|
157
|
+
const MAX_TRACKED_TRANSPORT_SESSIONS = 512;
|
|
158
|
+
const transportSessionBackends = new Map();
|
|
159
|
+
function rememberTransportSession(sessionId, identity) {
|
|
160
|
+
transportSessionBackends.delete(sessionId);
|
|
161
|
+
transportSessionBackends.set(sessionId, identity);
|
|
162
|
+
while (transportSessionBackends.size > MAX_TRACKED_TRANSPORT_SESSIONS) {
|
|
163
|
+
const oldest = transportSessionBackends.keys().next().value;
|
|
164
|
+
if (oldest === undefined)
|
|
165
|
+
break;
|
|
166
|
+
transportSessionBackends.delete(oldest);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function canonicalServiceRecord(services) {
|
|
170
|
+
return Object.fromEntries(Object.entries(services).map(([slug, state]) => [canonicalToolkitSlug(slug), state]));
|
|
171
|
+
}
|
|
172
|
+
/** Preserve the caller's key (`x` included) while speaking canonical toolkit
|
|
173
|
+
* slugs upstream. Old connector cards index status with their saved slug. */
|
|
174
|
+
function requestedServiceRecord(services, requested) {
|
|
175
|
+
const canonical = canonicalServiceRecord(services);
|
|
176
|
+
return Object.fromEntries(requested.flatMap((slug) => {
|
|
177
|
+
const state = canonical[canonicalToolkitSlug(slug)];
|
|
178
|
+
return state === undefined ? [] : [[slug, state]];
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
export function connectionMode(cfg) {
|
|
182
|
+
if (projectApiKey(cfg))
|
|
183
|
+
return "self-hosted";
|
|
184
|
+
if (brokerAccess())
|
|
185
|
+
return "managed";
|
|
186
|
+
return "unavailable";
|
|
187
|
+
}
|
|
188
|
+
export function configured(cfg) {
|
|
189
|
+
return connectionMode(cfg) !== "unavailable";
|
|
190
|
+
}
|
|
191
|
+
export function connectorAvailability(cfg, storeState = process.env.OMB_CREDENTIAL_STORE) {
|
|
192
|
+
if (configured(cfg))
|
|
193
|
+
return "configured";
|
|
194
|
+
return storeState === "unavailable" ? "unreadable" : "unconfigured";
|
|
195
|
+
}
|
|
196
|
+
async function brokerRequest(path, init) {
|
|
197
|
+
const broker = brokerAccess();
|
|
198
|
+
if (!broker)
|
|
199
|
+
throw new Error("The connected-apps service is unavailable");
|
|
200
|
+
const headers = new Headers(init?.headers);
|
|
201
|
+
headers.set("authorization", `Bearer ${broker.token}`);
|
|
202
|
+
if (init?.body)
|
|
203
|
+
headers.set("content-type", "application/json");
|
|
204
|
+
return fetch(`${broker.url}${path}`, {
|
|
205
|
+
...init,
|
|
206
|
+
headers,
|
|
207
|
+
signal: init?.signal ?? AbortSignal.timeout(30_000),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function projectHeaders(apiKey, json = false) {
|
|
211
|
+
const headers = new Headers({ "x-api-key": apiKey });
|
|
212
|
+
if (json)
|
|
213
|
+
headers.set("content-type", "application/json");
|
|
214
|
+
return headers;
|
|
215
|
+
}
|
|
216
|
+
async function responseError(res, fallback) {
|
|
217
|
+
const raw = await res.text().catch(() => "");
|
|
218
|
+
try {
|
|
219
|
+
const body = JSON.parse(raw);
|
|
220
|
+
return String(body?.message ?? body?.error?.message ?? body?.error ?? fallback);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return raw.trim().slice(0, 300) || fallback;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async function throwBrokerError(res, fallback) {
|
|
227
|
+
const status = res.status >= 400 && res.status < 500 ? res.status : 502;
|
|
228
|
+
throw Object.assign(new Error(await responseError(res, fallback)), { status });
|
|
229
|
+
}
|
|
230
|
+
function trustedAuthUrl(value, slug) {
|
|
231
|
+
if (!value)
|
|
232
|
+
throw new Error(`Connected-apps service returned no authorization link for ${slug}`);
|
|
233
|
+
const url = new URL(value);
|
|
234
|
+
if (url.protocol !== "https:" || (url.hostname !== "composio.dev" && !url.hostname.endsWith(".composio.dev"))) {
|
|
235
|
+
throw new Error("Connected-apps service returned an untrusted authorization link");
|
|
236
|
+
}
|
|
237
|
+
return url.toString();
|
|
238
|
+
}
|
|
239
|
+
function parseSessionResponse(session) {
|
|
240
|
+
const mcp = new URL(session.mcp.url);
|
|
241
|
+
if (mcp.protocol !== "https:" || (mcp.hostname !== "composio.dev" && !mcp.hostname.endsWith(".composio.dev"))) {
|
|
242
|
+
throw new Error("Composio returned an untrusted Session MCP URL");
|
|
243
|
+
}
|
|
244
|
+
return { ...session, mcp: { ...session.mcp, url: mcp.toString() } };
|
|
245
|
+
}
|
|
246
|
+
function supportsMultiAccount(session) {
|
|
247
|
+
// Only `enable` gates reuse. The cap and selection flags are what we ASK
|
|
248
|
+
// for at creation; if Composio clamps or omits them in the echo, recreating
|
|
249
|
+
// the Session would post the same config and get the same echo back — a
|
|
250
|
+
// strict equality check here can only manufacture a recreate-per-request
|
|
251
|
+
// loop, never fix anything.
|
|
252
|
+
return session.config?.multi_account?.enable === true;
|
|
253
|
+
}
|
|
254
|
+
/** Session ids this boot already tried to upgrade once. If the fresh Session
|
|
255
|
+
* STILL doesn't echo multi-account, Composio isn't granting it — run with
|
|
256
|
+
* what we have (single-account behavior) instead of recreating a Session and
|
|
257
|
+
* rewriting config.json on every request. */
|
|
258
|
+
const multiAccountUpgradeAttempted = new Set();
|
|
259
|
+
/** Session id + auth-config map pairs this boot already created a Session
|
|
260
|
+
* for. Same idea: if Composio does not echo `auth_configs`, recreating the
|
|
261
|
+
* Session on every check would loop without changing anything. */
|
|
262
|
+
const authConfigUpgradeAttempted = new Set();
|
|
263
|
+
function inputError(message, status = 400) {
|
|
264
|
+
return Object.assign(new Error(message), { status });
|
|
265
|
+
}
|
|
266
|
+
export function normalizeAccountAlias(value) {
|
|
267
|
+
if (value === undefined || value === null || value === "")
|
|
268
|
+
return undefined;
|
|
269
|
+
const parsed = z.string().safeParse(value);
|
|
270
|
+
if (!parsed.success)
|
|
271
|
+
throw inputError("Account alias must be text");
|
|
272
|
+
const alias = parsed.data.trim();
|
|
273
|
+
if (!printableAliasSchema.safeParse(alias).success) {
|
|
274
|
+
throw inputError("Account alias must be 1-64 printable characters");
|
|
275
|
+
}
|
|
276
|
+
return alias;
|
|
277
|
+
}
|
|
278
|
+
function validAccountId(value) {
|
|
279
|
+
return Boolean(value && ACCOUNT_ID.test(value));
|
|
280
|
+
}
|
|
281
|
+
async function getProjectSession(apiKey, sessionId) {
|
|
282
|
+
const res = await fetch(`${apiBase()}/tool_router/session/${encodeURIComponent(sessionId)}`, {
|
|
283
|
+
headers: projectHeaders(apiKey),
|
|
284
|
+
signal: AbortSignal.timeout(15_000),
|
|
285
|
+
});
|
|
286
|
+
if (res.status === 404)
|
|
287
|
+
return null;
|
|
288
|
+
if (!res.ok)
|
|
289
|
+
throw new Error(await responseError(res, `Composio session: HTTP ${res.status}`));
|
|
290
|
+
return parseSessionResponse(sessionResponseSchema.parse(await res.json()));
|
|
291
|
+
}
|
|
292
|
+
/** The project's own (non-Composio-managed) auth configs, one per toolkit.
|
|
293
|
+
* Disabled configs and ones switched off for Sessions are skipped; when a
|
|
294
|
+
* toolkit has several, the most recently updated wins. Ordinary Session
|
|
295
|
+
* preparation treats a denied list as "none"; an explicit auth retry surfaces
|
|
296
|
+
* the denial so it cannot replace a usable Session with an incomplete one. */
|
|
297
|
+
export async function listCustomAuthConfigs(apiKey) {
|
|
298
|
+
const chosen = new Map();
|
|
299
|
+
let cursor;
|
|
300
|
+
for (let page = 0; page < MAX_AUTH_CONFIG_PAGES; page++) {
|
|
301
|
+
const params = new URLSearchParams({ is_composio_managed: "false", limit: "100" });
|
|
302
|
+
if (cursor)
|
|
303
|
+
params.set("cursor", cursor);
|
|
304
|
+
const res = await fetch(`${apiBase()}/auth_configs?${params}`, {
|
|
305
|
+
headers: projectHeaders(apiKey),
|
|
306
|
+
signal: AbortSignal.timeout(15_000),
|
|
307
|
+
});
|
|
308
|
+
if (!res.ok)
|
|
309
|
+
throw new Error(await responseError(res, `Composio auth configs: HTTP ${res.status}`));
|
|
310
|
+
const body = authConfigsPageSchema.parse(await res.json());
|
|
311
|
+
for (const item of body.items ?? []) {
|
|
312
|
+
const slug = item.toolkit?.slug ? canonicalToolkitSlug(item.toolkit.slug) : "";
|
|
313
|
+
if (!slug || !item.id || item.is_composio_managed === true)
|
|
314
|
+
continue;
|
|
315
|
+
if (item.is_enabled_for_tool_router === false)
|
|
316
|
+
continue;
|
|
317
|
+
if (item.status && /^(disabled|inactive|expired|deleted)$/i.test(item.status))
|
|
318
|
+
continue;
|
|
319
|
+
const updated = item.last_updated_at ?? "";
|
|
320
|
+
const current = chosen.get(slug);
|
|
321
|
+
if (!current || updated > current.updated)
|
|
322
|
+
chosen.set(slug, { id: item.id, updated });
|
|
323
|
+
}
|
|
324
|
+
const next = body.next_cursor ?? undefined;
|
|
325
|
+
if (!next || next === cursor)
|
|
326
|
+
break;
|
|
327
|
+
cursor = next;
|
|
328
|
+
}
|
|
329
|
+
return Object.fromEntries([...chosen].sort(([a], [b]) => a.localeCompare(b)).map(([slug, { id }]) => [slug, id]));
|
|
330
|
+
}
|
|
331
|
+
/** True when the Session already routes every wanted toolkit through the
|
|
332
|
+
* project's own auth config. Extra configs on the Session are fine; a
|
|
333
|
+
* missing or different one means the Session predates the config. */
|
|
334
|
+
function sessionCoversAuthConfigs(session, wanted) {
|
|
335
|
+
const have = session.config?.auth_configs ?? {};
|
|
336
|
+
const haveLower = Object.fromEntries(Object.entries(have).map(([slug, id]) => [canonicalToolkitSlug(slug), id]));
|
|
337
|
+
return Object.entries(wanted).every(([slug, id]) => haveLower[slug] === id);
|
|
338
|
+
}
|
|
339
|
+
function authConfigsKey(sessionId, wanted) {
|
|
340
|
+
return `${sessionId}:${JSON.stringify(wanted)}`;
|
|
341
|
+
}
|
|
342
|
+
/** Validate a project key and return one reusable Session for this install. */
|
|
343
|
+
export async function prepareProjectSession(apiKey, current, knownAuthConfigs) {
|
|
344
|
+
const trimmed = apiKey.trim();
|
|
345
|
+
if (!trimmed)
|
|
346
|
+
throw new Error("Enter a Composio project API key");
|
|
347
|
+
if (!trimmed.startsWith("ak_"))
|
|
348
|
+
throw new Error("Composio project API keys start with ak_");
|
|
349
|
+
// The project's own auth configs must be named at creation — a Session
|
|
350
|
+
// cannot be edited later — so they are read before deciding whether the
|
|
351
|
+
// current Session is still the right one (issue #509: a twitter auth
|
|
352
|
+
// config created after the Session existed was never used).
|
|
353
|
+
const authConfigs = knownAuthConfigs
|
|
354
|
+
?? await listCustomAuthConfigs(trimmed).catch(() => ({}));
|
|
355
|
+
let priorUserId = current?.userId;
|
|
356
|
+
if (trimmed === current?.apiKey && current.sessionId) {
|
|
357
|
+
const existing = await getProjectSession(trimmed, current.sessionId);
|
|
358
|
+
if (existing
|
|
359
|
+
&& supportsMultiAccount(existing)
|
|
360
|
+
&& (sessionCoversAuthConfigs(existing, authConfigs)
|
|
361
|
+
|| authConfigUpgradeAttempted.has(authConfigsKey(existing.session_id, authConfigs)))) {
|
|
362
|
+
return {
|
|
363
|
+
apiKey: trimmed,
|
|
364
|
+
userId: existing.config?.user_id ?? current.userId ?? `openmausbot_${randomUUID()}`,
|
|
365
|
+
sessionId: existing.session_id,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
// Connections belong to the Composio user, not the Session. Recreate old
|
|
369
|
+
// single-account Sessions with the same user ID so every existing grant is
|
|
370
|
+
// retained while the new Session opts into explicit multi-account routing.
|
|
371
|
+
priorUserId = existing?.config?.user_id ?? priorUserId;
|
|
372
|
+
}
|
|
373
|
+
const userId = priorUserId ?? `openmausbot_${randomUUID()}`;
|
|
374
|
+
const sessionRequest = {
|
|
375
|
+
user_id: userId,
|
|
376
|
+
manage_connections: {
|
|
377
|
+
enable: true,
|
|
378
|
+
enable_wait_for_connections: true,
|
|
379
|
+
enable_connection_removal: true,
|
|
380
|
+
},
|
|
381
|
+
multi_account: MULTI_ACCOUNT_CONFIG,
|
|
382
|
+
};
|
|
383
|
+
if (Object.keys(authConfigs).length)
|
|
384
|
+
sessionRequest.auth_configs = authConfigs;
|
|
385
|
+
const res = await fetch(`${apiBase()}/tool_router/session`, {
|
|
386
|
+
method: "POST",
|
|
387
|
+
headers: projectHeaders(trimmed, true),
|
|
388
|
+
body: JSON.stringify(sessionRequest),
|
|
389
|
+
signal: AbortSignal.timeout(30_000),
|
|
390
|
+
});
|
|
391
|
+
if (!res.ok)
|
|
392
|
+
throw new Error(await responseError(res, `Composio rejected this key (HTTP ${res.status})`));
|
|
393
|
+
const session = parseSessionResponse(sessionResponseSchema.parse(await res.json()));
|
|
394
|
+
// If Composio does not echo the configs back, a later check would ask for
|
|
395
|
+
// the same creation again — remember this attempt so it happens once.
|
|
396
|
+
authConfigUpgradeAttempted.add(authConfigsKey(session.session_id, authConfigs));
|
|
397
|
+
return { apiKey: trimmed, userId, sessionId: session.session_id };
|
|
398
|
+
}
|
|
399
|
+
async function ensureProjectSession(cfg) {
|
|
400
|
+
const composio = cfg.composio;
|
|
401
|
+
const apiKey = projectApiKey(cfg);
|
|
402
|
+
if (!composio || !apiKey)
|
|
403
|
+
throw new Error("No Composio project key configured");
|
|
404
|
+
if (composio.sessionId) {
|
|
405
|
+
const existing = await getProjectSession(apiKey, composio.sessionId);
|
|
406
|
+
if (existing && (supportsMultiAccount(existing) || multiAccountUpgradeAttempted.has(existing.session_id))) {
|
|
407
|
+
return existing;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// A missing/deleted session is recreated and its non-secret identifiers are
|
|
411
|
+
// persisted so an edited config/env setup does not recreate it every launch.
|
|
412
|
+
const prepared = await prepareProjectSession(apiKey, composio);
|
|
413
|
+
multiAccountUpgradeAttempted.add(prepared.sessionId);
|
|
414
|
+
composio.userId = prepared.userId;
|
|
415
|
+
composio.sessionId = prepared.sessionId;
|
|
416
|
+
saveConfig({ composio: { userId: prepared.userId, sessionId: prepared.sessionId } });
|
|
417
|
+
const created = await getProjectSession(apiKey, prepared.sessionId);
|
|
418
|
+
if (!created)
|
|
419
|
+
throw new Error("Composio Session disappeared after creation");
|
|
420
|
+
return created;
|
|
421
|
+
}
|
|
422
|
+
/** Replace the current Session with a freshly created one — the only way to
|
|
423
|
+
* pick up an auth config the user added after the Session was made. The
|
|
424
|
+
* Composio user id is kept, so every existing connection survives. */
|
|
425
|
+
async function recreateProjectSession(cfg, userId, authConfigs) {
|
|
426
|
+
const composio = cfg.composio;
|
|
427
|
+
const apiKey = projectApiKey(cfg);
|
|
428
|
+
if (!composio || !apiKey)
|
|
429
|
+
throw new Error("No Composio project key configured");
|
|
430
|
+
const prepared = await prepareProjectSession(apiKey, { apiKey, userId }, authConfigs);
|
|
431
|
+
multiAccountUpgradeAttempted.add(prepared.sessionId);
|
|
432
|
+
composio.userId = prepared.userId;
|
|
433
|
+
composio.sessionId = prepared.sessionId;
|
|
434
|
+
saveConfig({ composio: { userId: prepared.userId, sessionId: prepared.sessionId } });
|
|
435
|
+
const created = await getProjectSession(apiKey, prepared.sessionId);
|
|
436
|
+
if (!created)
|
|
437
|
+
throw new Error("Composio Session disappeared after creation");
|
|
438
|
+
return created;
|
|
439
|
+
}
|
|
440
|
+
/** Composio's wording when a toolkit has no managed auth and the Session was
|
|
441
|
+
* not told which of the project's own auth configs to use. */
|
|
442
|
+
const NEEDS_AUTH_CONFIG = /does not manage auth|auth[_ ]?config/i;
|
|
443
|
+
export async function mcpIntegration(cfg, context) {
|
|
444
|
+
if (!configured(cfg))
|
|
445
|
+
return null;
|
|
446
|
+
return {
|
|
447
|
+
command: process.execPath,
|
|
448
|
+
args: [SPAWNED_PROXIES.connectors],
|
|
449
|
+
env: {
|
|
450
|
+
ELECTRON_RUN_AS_NODE: "1",
|
|
451
|
+
// The provider-facing bridge receives only this boot's loopback token.
|
|
452
|
+
// Project/broker credentials stay in the harness process, so a coding
|
|
453
|
+
// agent that prints its environment cannot export a durable secret.
|
|
454
|
+
OMB_CONNECTOR_UPSTREAM_URL: `${context.harnessUrl}/api/internal/connectors/mcp`,
|
|
455
|
+
OMB_CONNECTOR_UPSTREAM_HEADERS: JSON.stringify({ authorization: `Bearer ${context.commsToken}` }),
|
|
456
|
+
OMB_HARNESS_URL: context.harnessUrl,
|
|
457
|
+
// Distinct from the agents proxy token: Codex flattens mounted MCP env
|
|
458
|
+
// variables into one process environment, so a shared name would let
|
|
459
|
+
// the later agents mount overwrite this connector-scoped capability.
|
|
460
|
+
OMB_CONNECTOR_TOKEN: context.commsToken,
|
|
461
|
+
OMB_BOT_ID: context.botId,
|
|
462
|
+
OMB_THREAD_ID: context.threadId,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
export async function relayMcp(cfg, payload, transportSessionId) {
|
|
467
|
+
const apiKey = projectApiKey(cfg);
|
|
468
|
+
let url;
|
|
469
|
+
let identity;
|
|
470
|
+
const headers = new Headers({
|
|
471
|
+
"content-type": "application/json",
|
|
472
|
+
accept: "application/json, text/event-stream",
|
|
473
|
+
});
|
|
474
|
+
if (apiKey) {
|
|
475
|
+
const session = await ensureProjectSession(cfg);
|
|
476
|
+
url = session.mcp.url;
|
|
477
|
+
headers.set("x-api-key", apiKey);
|
|
478
|
+
identity = backendFingerprint("project-mcp", url, apiKey);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
const broker = brokerAccess();
|
|
482
|
+
if (!broker)
|
|
483
|
+
throw new Error("Connected apps are unavailable");
|
|
484
|
+
url = `${broker.url}/v1/mcp`;
|
|
485
|
+
headers.set("authorization", `Bearer ${broker.token}`);
|
|
486
|
+
identity = backendFingerprint("managed-mcp", url, broker.token);
|
|
487
|
+
}
|
|
488
|
+
const knownIdentity = transportSessionId
|
|
489
|
+
? transportSessionBackends.get(transportSessionId)
|
|
490
|
+
: undefined;
|
|
491
|
+
const forwardedTransportSessionId = transportSessionId
|
|
492
|
+
&& knownIdentity === identity
|
|
493
|
+
? transportSessionId
|
|
494
|
+
: undefined;
|
|
495
|
+
if (forwardedTransportSessionId) {
|
|
496
|
+
headers.set("mcp-session-id", forwardedTransportSessionId);
|
|
497
|
+
}
|
|
498
|
+
const response = await fetch(url, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers,
|
|
501
|
+
body: JSON.stringify(payload),
|
|
502
|
+
signal: AbortSignal.timeout(10 * 60_000),
|
|
503
|
+
});
|
|
504
|
+
const declared = Number(response.headers.get("content-length") ?? "0");
|
|
505
|
+
if (declared > 20 * 1024 * 1024)
|
|
506
|
+
throw new Error("Connected-app response exceeded 20 MB");
|
|
507
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
508
|
+
if (bytes.byteLength > 20 * 1024 * 1024)
|
|
509
|
+
throw new Error("Connected-app response exceeded 20 MB");
|
|
510
|
+
if (forwardedTransportSessionId)
|
|
511
|
+
rememberTransportSession(forwardedTransportSessionId, identity);
|
|
512
|
+
const nextTransportSessionId = response.headers.get("mcp-session-id") ?? undefined;
|
|
513
|
+
if (nextTransportSessionId)
|
|
514
|
+
rememberTransportSession(nextTransportSessionId, identity);
|
|
515
|
+
return {
|
|
516
|
+
status: response.status,
|
|
517
|
+
bytes,
|
|
518
|
+
contentType: response.headers.get("content-type") ?? "application/json",
|
|
519
|
+
transportSessionId: nextTransportSessionId,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async function listConnectedAccounts(apiKey, userId, slugs) {
|
|
523
|
+
const accounts = [];
|
|
524
|
+
const seenCursors = new Set();
|
|
525
|
+
let cursor;
|
|
526
|
+
// Five accounts per toolkit can exceed one provider page when a user has
|
|
527
|
+
// many apps. Follow Composio's cursor instead of silently dropping entries.
|
|
528
|
+
for (let page = 0; page < MAX_CONNECTED_ACCOUNT_PAGES; page += 1) {
|
|
529
|
+
const params = new URLSearchParams({
|
|
530
|
+
limit: "50",
|
|
531
|
+
user_ids: userId,
|
|
532
|
+
order_by: "updated_at",
|
|
533
|
+
order_direction: "desc",
|
|
534
|
+
});
|
|
535
|
+
if (slugs.length)
|
|
536
|
+
params.set("toolkit_slugs", slugs.join(","));
|
|
537
|
+
if (cursor)
|
|
538
|
+
params.set("cursor", cursor);
|
|
539
|
+
const response = await fetch(`${apiBase()}/connected_accounts?${params}`, {
|
|
540
|
+
headers: projectHeaders(apiKey),
|
|
541
|
+
signal: AbortSignal.timeout(15_000),
|
|
542
|
+
});
|
|
543
|
+
if (!response.ok)
|
|
544
|
+
throw new Error(await responseError(response, `Composio accounts: HTTP ${response.status}`));
|
|
545
|
+
const body = connectedAccountsPageSchema.parse(await response.json());
|
|
546
|
+
accounts.push(...body.items);
|
|
547
|
+
const next = body.next_cursor || undefined;
|
|
548
|
+
if (!next || seenCursors.has(next))
|
|
549
|
+
return accounts;
|
|
550
|
+
seenCursors.add(next);
|
|
551
|
+
cursor = next;
|
|
552
|
+
}
|
|
553
|
+
throw new Error("Composio account inventory exceeded the pagination safety limit");
|
|
554
|
+
}
|
|
555
|
+
async function listSessionToolkits(apiKey, sessionId) {
|
|
556
|
+
const toolkits = [];
|
|
557
|
+
const seenCursors = new Set();
|
|
558
|
+
let cursor;
|
|
559
|
+
for (let page = 0; page < MAX_CONNECTED_ACCOUNT_PAGES; page += 1) {
|
|
560
|
+
// The unfiltered endpoint contains the entire Composio marketplace and is
|
|
561
|
+
// cursor-paginated in 50-item pages. The Connected tab only needs the
|
|
562
|
+
// user's connected toolkits, so avoid scanning hundreds of unrelated apps.
|
|
563
|
+
const params = new URLSearchParams({ limit: "50", is_connected: "true" });
|
|
564
|
+
if (cursor)
|
|
565
|
+
params.set("cursor", cursor);
|
|
566
|
+
const response = await fetch(`${apiBase()}/tool_router/session/${encodeURIComponent(sessionId)}/toolkits?${params}`, { headers: projectHeaders(apiKey), signal: AbortSignal.timeout(15_000) });
|
|
567
|
+
if (!response.ok)
|
|
568
|
+
throw new Error(await responseError(response, `Composio toolkits: HTTP ${response.status}`));
|
|
569
|
+
const body = toolkitPageSchema.parse(await response.json());
|
|
570
|
+
toolkits.push(...(body.items ?? []));
|
|
571
|
+
const next = body.next_cursor || undefined;
|
|
572
|
+
if (!next || seenCursors.has(next))
|
|
573
|
+
return toolkits;
|
|
574
|
+
seenCursors.add(next);
|
|
575
|
+
cursor = next;
|
|
576
|
+
}
|
|
577
|
+
throw new Error("Composio toolkit inventory exceeded the pagination safety limit");
|
|
578
|
+
}
|
|
579
|
+
function summarizeAccounts(accounts, slugs) {
|
|
580
|
+
const requested = new Set(slugs.map(canonicalToolkitSlug));
|
|
581
|
+
const bySlug = new Map();
|
|
582
|
+
for (const account of accounts) {
|
|
583
|
+
const slug = account.toolkit?.slug ? canonicalToolkitSlug(account.toolkit.slug) : "";
|
|
584
|
+
if (!slug || (requested.size && !requested.has(slug)) || !validAccountId(account.id))
|
|
585
|
+
continue;
|
|
586
|
+
const alias = account.alias?.trim() ?? "";
|
|
587
|
+
const summary = {
|
|
588
|
+
id: account.id,
|
|
589
|
+
status: account.status || "UNKNOWN",
|
|
590
|
+
updatedAt: account.updated_at ?? "",
|
|
591
|
+
};
|
|
592
|
+
if (printableAliasSchema.safeParse(alias).success)
|
|
593
|
+
summary.alias = alias;
|
|
594
|
+
const list = bySlug.get(slug) ?? [];
|
|
595
|
+
list.push(summary);
|
|
596
|
+
bySlug.set(slug, list);
|
|
597
|
+
}
|
|
598
|
+
for (const list of bySlug.values())
|
|
599
|
+
list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
600
|
+
return bySlug;
|
|
601
|
+
}
|
|
602
|
+
function publicAccount({ id, alias, status }) {
|
|
603
|
+
const account = { id, status };
|
|
604
|
+
if (alias)
|
|
605
|
+
account.alias = alias;
|
|
606
|
+
return account;
|
|
607
|
+
}
|
|
608
|
+
function serviceStateFromAccounts(accounts) {
|
|
609
|
+
const active = accounts.find((account) => /^active$/i.test(account.status));
|
|
610
|
+
const pending = accounts.find((account) => /^(initiated|initializing|pending)$/i.test(account.status));
|
|
611
|
+
const selected = active ?? pending ?? accounts[0];
|
|
612
|
+
return {
|
|
613
|
+
connected: Boolean(active),
|
|
614
|
+
pending: Boolean(pending),
|
|
615
|
+
status: selected?.status ?? "not_connected",
|
|
616
|
+
accounts: accounts.map(publicAccount),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function allServiceStates(accountsBySlug, toolkits) {
|
|
620
|
+
const services = new Map([...accountsBySlug].map(([slug, accounts]) => [slug, serviceStateFromAccounts(accounts)]));
|
|
621
|
+
for (const toolkit of toolkits) {
|
|
622
|
+
const slug = toolkit.slug ? canonicalToolkitSlug(toolkit.slug) : "";
|
|
623
|
+
const selected = toolkit.connected_account;
|
|
624
|
+
const selectedId = validAccountId(selected?.id) ? selected.id : undefined;
|
|
625
|
+
if (!slug || (!toolkit.is_no_auth && !selectedId))
|
|
626
|
+
continue;
|
|
627
|
+
const existingAccounts = accountsBySlug.get(slug) ?? [];
|
|
628
|
+
const accounts = [...existingAccounts];
|
|
629
|
+
if (selectedId && !accounts.some((account) => account.id === selectedId)) {
|
|
630
|
+
accounts.push({ id: selectedId, status: selected?.status ?? "ACTIVE" });
|
|
631
|
+
}
|
|
632
|
+
const accountState = serviceStateFromAccounts(accounts);
|
|
633
|
+
const status = toolkit.is_no_auth ? "ACTIVE" : selected?.status ?? accountState.status;
|
|
634
|
+
services.set(slug, {
|
|
635
|
+
connected: toolkit.is_no_auth === true || accountState.connected || /^active$/i.test(status),
|
|
636
|
+
pending: accountState.pending || /^(initiated|initializing|pending)$/i.test(status),
|
|
637
|
+
status,
|
|
638
|
+
accounts: accountState.accounts,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
return Object.fromEntries(services);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Enumerate the user's complete connected-account inventory without depending
|
|
645
|
+
* on marketplace ordering or catalog pagination.
|
|
646
|
+
*/
|
|
647
|
+
export async function connectedServices(cfg) {
|
|
648
|
+
const apiKey = projectApiKey(cfg);
|
|
649
|
+
if (!apiKey) {
|
|
650
|
+
const response = await brokerRequest("/v1/connectors/connected");
|
|
651
|
+
if (!response.ok)
|
|
652
|
+
await throwBrokerError(response, `Connected apps: HTTP ${response.status}`);
|
|
653
|
+
const body = connectorServicesResponseSchema.parse(await response.json());
|
|
654
|
+
const services = canonicalServiceRecord(body.services ?? {});
|
|
655
|
+
return Object.fromEntries(Object.entries(services).map(([slug, state]) => [slug, {
|
|
656
|
+
connected: state.connected,
|
|
657
|
+
pending: state.pending ?? false,
|
|
658
|
+
status: state.status ?? (state.connected ? "ACTIVE" : "not_connected"),
|
|
659
|
+
accounts: state.accounts ?? [],
|
|
660
|
+
}]));
|
|
661
|
+
}
|
|
662
|
+
const session = await ensureProjectSession(cfg);
|
|
663
|
+
const userId = session.config?.user_id ?? cfg.composio?.userId;
|
|
664
|
+
if (!userId)
|
|
665
|
+
throw new Error("Composio Session returned no user ID");
|
|
666
|
+
const [toolkits, accounts] = await Promise.all([
|
|
667
|
+
listSessionToolkits(apiKey, session.session_id),
|
|
668
|
+
// Scoped project keys can grant Session reads without granting the raw
|
|
669
|
+
// connected-account list. The Session still proves which selected/no-auth
|
|
670
|
+
// toolkits belong to this installation, so retain that safe fallback.
|
|
671
|
+
listConnectedAccounts(apiKey, userId, []).catch(() => []),
|
|
672
|
+
]);
|
|
673
|
+
return allServiceStates(summarizeAccounts(accounts, []), toolkits);
|
|
674
|
+
}
|
|
675
|
+
export async function connectionStatus(cfg, slugs) {
|
|
676
|
+
const canonicalSlugs = [...new Set(slugs.map(canonicalToolkitSlug))];
|
|
677
|
+
const apiKey = projectApiKey(cfg);
|
|
678
|
+
if (!apiKey) {
|
|
679
|
+
const response = await brokerRequest(`/v1/connectors?${new URLSearchParams({ services: canonicalSlugs.join(",") })}`);
|
|
680
|
+
if (!response.ok)
|
|
681
|
+
await throwBrokerError(response, `Connected apps: HTTP ${response.status}`);
|
|
682
|
+
const body = connectorServicesResponseSchema.parse(await response.json());
|
|
683
|
+
return requestedServiceRecord(body.services ?? {}, slugs);
|
|
684
|
+
}
|
|
685
|
+
const session = await ensureProjectSession(cfg);
|
|
686
|
+
const params = new URLSearchParams({ limit: "50" });
|
|
687
|
+
if (canonicalSlugs.length)
|
|
688
|
+
params.set("toolkits", canonicalSlugs.join(","));
|
|
689
|
+
const userId = session.config?.user_id ?? cfg.composio?.userId;
|
|
690
|
+
const [res, accounts] = await Promise.all([
|
|
691
|
+
fetch(`${apiBase()}/tool_router/session/${encodeURIComponent(session.session_id)}/toolkits?${params}`, {
|
|
692
|
+
headers: projectHeaders(apiKey),
|
|
693
|
+
signal: AbortSignal.timeout(15_000),
|
|
694
|
+
}),
|
|
695
|
+
// Session toolkits only include an account once it is usable. Read the
|
|
696
|
+
// account lifecycle too so the UI can distinguish an OAuth flow that is
|
|
697
|
+
// still waiting in the browser from one that expired or failed. Scoped
|
|
698
|
+
// keys may omit connected-account read permission, so this is additive:
|
|
699
|
+
// the normal session result remains the fallback.
|
|
700
|
+
userId
|
|
701
|
+
? listConnectedAccounts(apiKey, userId, canonicalSlugs).catch(() => [])
|
|
702
|
+
: Promise.resolve([]),
|
|
703
|
+
]);
|
|
704
|
+
if (!res.ok)
|
|
705
|
+
throw new Error(await responseError(res, `Composio toolkits: HTTP ${res.status}`));
|
|
706
|
+
const body = toolkitPageSchema.parse(await res.json());
|
|
707
|
+
const bySlug = new Map((body.items ?? []).flatMap((item) => item.slug ? [[canonicalToolkitSlug(item.slug), item]] : []));
|
|
708
|
+
const accountsBySlug = summarizeAccounts(accounts, canonicalSlugs);
|
|
709
|
+
return Object.fromEntries(slugs.map((slug) => {
|
|
710
|
+
const canonicalSlug = canonicalToolkitSlug(slug);
|
|
711
|
+
const item = bySlug.get(canonicalSlug);
|
|
712
|
+
const serviceAccounts = accountsBySlug.get(canonicalSlug) ?? [];
|
|
713
|
+
// Mirror allServiceStates: a scoped key can be denied the raw account
|
|
714
|
+
// list while the Session still names its selected account. Synthesize
|
|
715
|
+
// that account here too, so a status poll never wipes the row the
|
|
716
|
+
// inventory paths render (merge replaces a slug's state wholesale).
|
|
717
|
+
const selected = item?.connected_account;
|
|
718
|
+
const selectedId = validAccountId(selected?.id) ? selected.id : undefined;
|
|
719
|
+
const withSelected = selectedId && !serviceAccounts.some((account) => account.id === selectedId)
|
|
720
|
+
? [...serviceAccounts, { id: selectedId, status: selected?.status ?? "ACTIVE" }]
|
|
721
|
+
: serviceAccounts;
|
|
722
|
+
const accountState = serviceStateFromAccounts(withSelected);
|
|
723
|
+
const state = item?.connected_account?.status
|
|
724
|
+
?? (item?.is_no_auth ? "ACTIVE" : accountState.status);
|
|
725
|
+
return [slug, {
|
|
726
|
+
connected: item?.is_no_auth === true || accountState.connected || /^active$/i.test(state),
|
|
727
|
+
pending: accountState.pending || /^(initiated|initializing|pending)$/i.test(state),
|
|
728
|
+
status: state,
|
|
729
|
+
accounts: accountState.accounts,
|
|
730
|
+
}];
|
|
731
|
+
}));
|
|
732
|
+
}
|
|
733
|
+
/** Backward-compatible service disconnect: removes the Session-selected account. */
|
|
734
|
+
export async function removeService(cfg, slug) {
|
|
735
|
+
const toolkit = canonicalToolkitSlug(slug);
|
|
736
|
+
const apiKey = projectApiKey(cfg);
|
|
737
|
+
if (!apiKey) {
|
|
738
|
+
const response = await brokerRequest(`/v1/connectors/${encodeURIComponent(toolkit)}`, { method: "DELETE" });
|
|
739
|
+
if (!response.ok)
|
|
740
|
+
await throwBrokerError(response, `Connected apps: HTTP ${response.status}`);
|
|
741
|
+
return removalResponseSchema.parse(await response.json());
|
|
742
|
+
}
|
|
743
|
+
const session = await ensureProjectSession(cfg);
|
|
744
|
+
const params = new URLSearchParams({ limit: "50", toolkits: toolkit });
|
|
745
|
+
const list = await fetch(`${apiBase()}/tool_router/session/${encodeURIComponent(session.session_id)}/toolkits?${params}`, { headers: projectHeaders(apiKey), signal: AbortSignal.timeout(15_000) });
|
|
746
|
+
if (!list.ok)
|
|
747
|
+
throw new Error(await responseError(list, `Composio toolkits: HTTP ${list.status}`));
|
|
748
|
+
const body = toolkitPageSchema.parse(await list.json());
|
|
749
|
+
const id = body.items?.find((item) => item.slug && canonicalToolkitSlug(item.slug) === toolkit)?.connected_account?.id;
|
|
750
|
+
if (!id)
|
|
751
|
+
return { removed: 0 };
|
|
752
|
+
const removed = await fetch(`${apiBase()}/connected_accounts/${encodeURIComponent(id)}?revoke_on_delete=true`, { method: "DELETE", headers: projectHeaders(apiKey), signal: AbortSignal.timeout(30_000) });
|
|
753
|
+
if (!removed.ok)
|
|
754
|
+
throw new Error(await responseError(removed, `Composio disconnect: HTTP ${removed.status}`));
|
|
755
|
+
return { removed: 1 };
|
|
756
|
+
}
|
|
757
|
+
/** Disconnect exactly one account after proving it belongs to this user/toolkit. */
|
|
758
|
+
export async function removeAccount(cfg, slug, accountId) {
|
|
759
|
+
if (!validAccountId(accountId))
|
|
760
|
+
throw inputError("Invalid connected-account ID");
|
|
761
|
+
const toolkit = canonicalToolkitSlug(slug);
|
|
762
|
+
const apiKey = projectApiKey(cfg);
|
|
763
|
+
if (!apiKey) {
|
|
764
|
+
const response = await brokerRequest(`/v1/connectors/${encodeURIComponent(toolkit)}/accounts/${encodeURIComponent(accountId)}`, { method: "DELETE" });
|
|
765
|
+
if (!response.ok)
|
|
766
|
+
await throwBrokerError(response, `Connected apps: HTTP ${response.status}`);
|
|
767
|
+
return removalResponseSchema.parse(await response.json());
|
|
768
|
+
}
|
|
769
|
+
const session = await ensureProjectSession(cfg);
|
|
770
|
+
const userId = session.config?.user_id ?? cfg.composio?.userId;
|
|
771
|
+
if (!userId)
|
|
772
|
+
throw new Error("Composio Session has no user ID");
|
|
773
|
+
const accounts = await listConnectedAccounts(apiKey, userId, [toolkit]);
|
|
774
|
+
const owned = accounts.some((account) => account.id === accountId
|
|
775
|
+
&& account.toolkit?.slug !== undefined
|
|
776
|
+
&& canonicalToolkitSlug(account.toolkit.slug) === toolkit);
|
|
777
|
+
if (!owned)
|
|
778
|
+
return { removed: 0 };
|
|
779
|
+
const removed = await fetch(`${apiBase()}/connected_accounts/${encodeURIComponent(accountId)}?revoke_on_delete=true`, { method: "DELETE", headers: projectHeaders(apiKey), signal: AbortSignal.timeout(30_000) });
|
|
780
|
+
if (!removed.ok)
|
|
781
|
+
throw new Error(await responseError(removed, `Composio disconnect: HTTP ${removed.status}`));
|
|
782
|
+
return { removed: 1 };
|
|
783
|
+
}
|
|
784
|
+
/** Mint a browser auth link for one service. Returns { url } or throws. */
|
|
785
|
+
export async function authorizeService(cfg, slug, requestedAlias) {
|
|
786
|
+
const alias = normalizeAccountAlias(requestedAlias);
|
|
787
|
+
const toolkit = canonicalToolkitSlug(slug);
|
|
788
|
+
const mode = connectionMode(cfg);
|
|
789
|
+
const unavailable = managedConnectorUnavailableReason(mode, toolkit);
|
|
790
|
+
if (unavailable)
|
|
791
|
+
throw inputError(unavailable, 409);
|
|
792
|
+
const apiKey = projectApiKey(cfg);
|
|
793
|
+
if (!apiKey) {
|
|
794
|
+
const request = { method: "POST" };
|
|
795
|
+
if (alias)
|
|
796
|
+
request.body = JSON.stringify({ alias });
|
|
797
|
+
const response = await brokerRequest(`/v1/connectors/${encodeURIComponent(toolkit)}/authorize`, request);
|
|
798
|
+
if (!response.ok)
|
|
799
|
+
await throwBrokerError(response, `Connected apps: HTTP ${response.status}`);
|
|
800
|
+
const body = authUrlResponseSchema.parse(await response.json());
|
|
801
|
+
return { url: trustedAuthUrl(body.url, toolkit) };
|
|
802
|
+
}
|
|
803
|
+
const session = await ensureProjectSession(cfg);
|
|
804
|
+
const userId = session.config?.user_id ?? cfg.composio?.userId;
|
|
805
|
+
if (!userId)
|
|
806
|
+
throw new Error("Composio Session has no user ID");
|
|
807
|
+
// A scoped key may be denied account listing — authorization must still
|
|
808
|
+
// work (it always did pre-multi-account), so the alias guardrails degrade
|
|
809
|
+
// to first-account behavior, the same fallback every inventory path takes.
|
|
810
|
+
const accounts = await listConnectedAccounts(apiKey, userId, [toolkit]).catch(() => []);
|
|
811
|
+
const serviceAccounts = accounts.filter((account) => account.toolkit?.slug !== undefined && canonicalToolkitSlug(account.toolkit.slug) === toolkit);
|
|
812
|
+
const usableAccounts = serviceAccounts.filter((account) => /^(active|initiated|initializing|pending)$/i.test(account.status ?? ""));
|
|
813
|
+
if (usableAccounts.length >= MULTI_ACCOUNT_CONFIG.max_accounts_per_toolkit) {
|
|
814
|
+
throw inputError(`${toolkit} already has the maximum of ${MULTI_ACCOUNT_CONFIG.max_accounts_per_toolkit} accounts`, 409);
|
|
815
|
+
}
|
|
816
|
+
if (usableAccounts.length > 0 && !alias) {
|
|
817
|
+
throw inputError("Add an account alias so the existing connection is not replaced");
|
|
818
|
+
}
|
|
819
|
+
if (alias && serviceAccounts.some((account) => account.alias?.trim().toLowerCase() === alias.toLowerCase())) {
|
|
820
|
+
throw inputError(`Account alias "${alias}" is already in use for ${toolkit}`, 409);
|
|
821
|
+
}
|
|
822
|
+
const linkRequest = { toolkit };
|
|
823
|
+
if (alias)
|
|
824
|
+
linkRequest.alias = alias;
|
|
825
|
+
const link = (sessionId) => fetch(`${apiBase()}/tool_router/session/${encodeURIComponent(sessionId)}/link`, {
|
|
826
|
+
method: "POST",
|
|
827
|
+
headers: projectHeaders(apiKey, true),
|
|
828
|
+
body: JSON.stringify(linkRequest),
|
|
829
|
+
signal: AbortSignal.timeout(30_000),
|
|
830
|
+
});
|
|
831
|
+
let res = await link(session.session_id);
|
|
832
|
+
if (!res.ok) {
|
|
833
|
+
const message = await responseError(res, `Composio authorization: HTTP ${res.status}`);
|
|
834
|
+
if (!NEEDS_AUTH_CONFIG.test(message))
|
|
835
|
+
throw new Error(message);
|
|
836
|
+
// The toolkit needs one of the project's own auth configs. The Session
|
|
837
|
+
// names those only at creation, so an auth config the user created after
|
|
838
|
+
// the Session existed is invisible to it: rebuild the Session once and
|
|
839
|
+
// retry. If the project has no config for this toolkit, say what to do
|
|
840
|
+
// instead of echoing Composio's "auth_config_override" hint.
|
|
841
|
+
const authConfigs = await listCustomAuthConfigs(apiKey);
|
|
842
|
+
const covered = Object.keys(authConfigs).some((key) => canonicalToolkitSlug(key) === toolkit);
|
|
843
|
+
if (!covered) {
|
|
844
|
+
throw inputError(`${toolkit} has no Composio-managed sign-in. In your Composio project, create an auth config for "${toolkit}" `
|
|
845
|
+
+ "(Auth Configs → Create) with your own app credentials, then click Connect again.");
|
|
846
|
+
}
|
|
847
|
+
const fresh = await recreateProjectSession(cfg, userId, authConfigs);
|
|
848
|
+
res = await link(fresh.session_id);
|
|
849
|
+
if (!res.ok)
|
|
850
|
+
throw new Error(await responseError(res, `Composio authorization: HTTP ${res.status}`));
|
|
851
|
+
}
|
|
852
|
+
const body = linkResponseSchema.parse(await res.json());
|
|
853
|
+
return { url: trustedAuthUrl(body.redirect_url, toolkit) };
|
|
854
|
+
}
|
|
855
|
+
// Curated fallback — the services agentcal's connectors page ships plus the
|
|
856
|
+
// long marketplace tail. Logos resolve client-side:
|
|
857
|
+
// logo → favicon(domain) → monogram.
|
|
858
|
+
const CURATED = [
|
|
859
|
+
{ slug: "slack", label: "Slack", blurb: "Post updates and read channels", domain: "slack.com", logo: null },
|
|
860
|
+
{ slug: "github", label: "GitHub", blurb: "Issues, pull requests, and code", domain: "github.com", logo: null },
|
|
861
|
+
{ slug: "gmail", label: "Gmail", blurb: "Read and send email", domain: "gmail.com", logo: null },
|
|
862
|
+
{ slug: "googlecalendar", label: "Google Calendar", blurb: "Read and create events", domain: "calendar.google.com", logo: null },
|
|
863
|
+
{ slug: "googlesheets", label: "Google Sheets", blurb: "Read and update spreadsheets", domain: "sheets.google.com", logo: null },
|
|
864
|
+
{ slug: "googledocs", label: "Google Docs", blurb: "Read and write documents", domain: "docs.google.com", logo: null },
|
|
865
|
+
{ slug: "googledrive", label: "Google Drive", blurb: "Browse and manage files", domain: "drive.google.com", logo: null },
|
|
866
|
+
{ slug: "notion", label: "Notion", blurb: "Pages and databases", domain: "notion.so", logo: null },
|
|
867
|
+
{ slug: "linear", label: "Linear", blurb: "Issues and project tracking", domain: "linear.app", logo: null },
|
|
868
|
+
{ slug: "sentry", label: "Sentry", blurb: "Errors and alerts", domain: "sentry.io", logo: null },
|
|
869
|
+
{ slug: "posthog", label: "PostHog", blurb: "Analytics, feature flags, experiments", domain: "posthog.com", logo: null },
|
|
870
|
+
{ slug: "discord", label: "Discord", blurb: "Messages and channels", domain: "discord.com", logo: null },
|
|
871
|
+
{ slug: "twitter", label: "X (Twitter)", blurb: "Post and read on X", domain: "x.com", logo: null },
|
|
872
|
+
{ slug: "reddit", label: "Reddit", blurb: "Browse and post", domain: "reddit.com", logo: null },
|
|
873
|
+
{ slug: "zapier", label: "Zapier", blurb: "Connect 9,000+ apps", domain: "zapier.com", logo: null },
|
|
874
|
+
{ slug: "hubspot", label: "HubSpot", blurb: "CRM search & updates", domain: "hubspot.com", logo: null },
|
|
875
|
+
{ slug: "salesforce", label: "Salesforce", blurb: "CRM records and reports", domain: "salesforce.com", logo: null },
|
|
876
|
+
{ slug: "jira", label: "Jira", blurb: "Issues and sprints", domain: "atlassian.com", logo: null },
|
|
877
|
+
{ slug: "asana", label: "Asana", blurb: "Tasks and projects", domain: "asana.com", logo: null },
|
|
878
|
+
{ slug: "trello", label: "Trello", blurb: "Boards and cards", domain: "trello.com", logo: null },
|
|
879
|
+
{ slug: "dropbox", label: "Dropbox", blurb: "Files and folders", domain: "dropbox.com", logo: null },
|
|
880
|
+
{ slug: "airtable", label: "Airtable", blurb: "Bases and records", domain: "airtable.com", logo: null },
|
|
881
|
+
{ slug: "figma", label: "Figma", blurb: "Files and comments", domain: "figma.com", logo: null },
|
|
882
|
+
{ slug: "stripe", label: "Stripe", blurb: "Payments and customers", domain: "stripe.com", logo: null },
|
|
883
|
+
];
|
|
884
|
+
let toolkitCache = null;
|
|
885
|
+
/**
|
|
886
|
+
* Marketplace catalog. Tries the v3 toolkits API (official names,
|
|
887
|
+
* descriptions, logos — cached 10 min); falls back to the curated list.
|
|
888
|
+
*/
|
|
889
|
+
export async function listToolkits(cfg) {
|
|
890
|
+
const backendKey = projectApiKey(cfg);
|
|
891
|
+
const broker = backendKey ? null : brokerAccess();
|
|
892
|
+
const identity = backendKey
|
|
893
|
+
? backendFingerprint("project-catalog", toolkitBase(), backendKey)
|
|
894
|
+
: broker
|
|
895
|
+
? backendFingerprint("managed-catalog", broker.url, broker.token)
|
|
896
|
+
: null;
|
|
897
|
+
if (identity && toolkitCache?.identity === identity && Date.now() - toolkitCache.at < 10 * 60_000) {
|
|
898
|
+
return { cards: toolkitCache.cards, source: "api" };
|
|
899
|
+
}
|
|
900
|
+
if (backendKey || broker) {
|
|
901
|
+
try {
|
|
902
|
+
const res = backendKey
|
|
903
|
+
? await fetch(`${toolkitBase()}/toolkits?limit=500&sort_by=usage`, {
|
|
904
|
+
headers: { "x-api-key": backendKey },
|
|
905
|
+
signal: AbortSignal.timeout(15_000),
|
|
906
|
+
})
|
|
907
|
+
: await brokerRequest("/v1/catalog", { signal: AbortSignal.timeout(15_000) });
|
|
908
|
+
if (res.ok) {
|
|
909
|
+
const json = await res.json();
|
|
910
|
+
const items = json.items ?? json.data ?? [];
|
|
911
|
+
if (Array.isArray(items) && items.length) {
|
|
912
|
+
const cards = items.map((t) => ({
|
|
913
|
+
slug: canonicalToolkitSlug(String(t.slug ?? t.key ?? t.name ?? "")),
|
|
914
|
+
label: t.name ?? t.slug ?? "",
|
|
915
|
+
blurb: (t.meta?.description ?? t.description ?? "").slice(0, 90),
|
|
916
|
+
logo: t.meta?.logo ?? t.logo ?? null,
|
|
917
|
+
noAuth: t.no_auth === true,
|
|
918
|
+
domain: null,
|
|
919
|
+
}));
|
|
920
|
+
const uniqueCards = cards.filter((card, index) => card.slug && cards.findIndex((candidate) => candidate.slug === card.slug) === index);
|
|
921
|
+
toolkitCache = { at: Date.now(), cards: uniqueCards, identity: identity };
|
|
922
|
+
return { cards: uniqueCards, source: "api" };
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
/* fall through to curated */
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return { cards: CURATED, source: "curated" };
|
|
931
|
+
}
|
|
932
|
+
export async function toolkitCard(cfg, slug) {
|
|
933
|
+
const normalized = canonicalToolkitSlug(slug);
|
|
934
|
+
const { cards } = await listToolkits(cfg);
|
|
935
|
+
return cards.find((card) => card.slug.toLowerCase() === normalized)
|
|
936
|
+
?? CURATED.find((card) => card.slug === normalized)
|
|
937
|
+
?? {
|
|
938
|
+
slug: normalized,
|
|
939
|
+
label: normalized.replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()),
|
|
940
|
+
blurb: "Connect this app so your bot can continue",
|
|
941
|
+
logo: null,
|
|
942
|
+
domain: null,
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
export const CURATED_SLUGS = CURATED.map((c) => c.slug);
|