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,1116 @@
|
|
|
1
|
+
// Codex driver — upstream CodexDriver skeleton over agentcal's
|
|
2
|
+
// drivers/codex.js runtime: the official `codex` CLI headless over its
|
|
3
|
+
// app-server JSON-RPC protocol (newline-delimited JSON on stdio).
|
|
4
|
+
// Completion is a real `turn/completed` notification; approval requests
|
|
5
|
+
// arrive as in-process server→client JSON-RPC requests and surface as
|
|
6
|
+
// canonical request.opened events (answered via respondToRequest — no MCP
|
|
7
|
+
// proxy or unix socket needed, unlike claude). Verified against
|
|
8
|
+
// codex-cli 0.144.4 by agentcal.
|
|
9
|
+
//
|
|
10
|
+
// resumeCursor is the codex thread id; a later turn tries thread/resume
|
|
11
|
+
// and falls back to a fresh thread/start.
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { stripWorkspaceCredentialEnv } from "../config.js";
|
|
15
|
+
import { computerProxyEnv } from "../container-computer.js";
|
|
16
|
+
import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
17
|
+
import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
18
|
+
import { isHarnessOwnedMcpEnvName } from "../mcp-registry.js";
|
|
19
|
+
import { newEventId, newId } from "../contracts.js";
|
|
20
|
+
import { decodeCodexSelection, readCodexModelCatalog, STATIC_CODEX_MODELS } from "./codex-catalog.js";
|
|
21
|
+
import { codexLocalProviderArgs } from "./local-inject.js";
|
|
22
|
+
import { augmentedPath, splitCliString } from "../env-path.js";
|
|
23
|
+
import { classifyError, computeBackoff, RETRY_MAX_ATTEMPTS } from "./retry.js";
|
|
24
|
+
import { appendNative } from "./native.js";
|
|
25
|
+
export { decodeCodexSelection, readCodexModelCatalog, STATIC_CODEX_MODELS } from "./codex-catalog.js";
|
|
26
|
+
const DRIVER_KIND = "codex";
|
|
27
|
+
const ASTRA_MODEL_ID = "gpt-6-astra";
|
|
28
|
+
const ASTRA_MIN_CODEX_VERSION = [0, 153, 1];
|
|
29
|
+
/** Whether an installed Codex predates the release that exposes GPT-6 Astra
|
|
30
|
+
* through app-server. Unknown version formats stay quiet: a bad guess should
|
|
31
|
+
* never nag someone whose custom build may already support the model. */
|
|
32
|
+
export function codexPredatesAstra(version) {
|
|
33
|
+
const value = version.trim();
|
|
34
|
+
const match = /\bcodex-cli\s+v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9a-z.-]+)?(?![\d.])\b/i.exec(value)
|
|
35
|
+
?? /^v?(\d+)\.(\d+)\.(\d+)(?:[-+][0-9a-z.-]+)?$/i.exec(value);
|
|
36
|
+
if (!match)
|
|
37
|
+
return false;
|
|
38
|
+
const installed = match.slice(1, 4).map(Number);
|
|
39
|
+
for (let i = 0; i < ASTRA_MIN_CODEX_VERSION.length; i += 1) {
|
|
40
|
+
if (installed[i] !== ASTRA_MIN_CODEX_VERSION[i]) {
|
|
41
|
+
return installed[i] < ASTRA_MIN_CODEX_VERSION[i];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/** Ask the configured executable to update itself. This matters when the user
|
|
47
|
+
* selected a non-PATH Codex: installing a second global copy would leave
|
|
48
|
+
* OpenMausBot pointing at the old binary. */
|
|
49
|
+
export function codexUpdateCommand(cli, platform = process.platform) {
|
|
50
|
+
if (cli === "codex")
|
|
51
|
+
return "codex update";
|
|
52
|
+
const trimmed = cli.trim();
|
|
53
|
+
// Match resolveCliSpawn's one tokenizer pass, including its exception for
|
|
54
|
+
// real unquoted paths containing spaces. A wrapper's fixed arguments must
|
|
55
|
+
// precede `update`, just as they precede `app-server` and `--version`.
|
|
56
|
+
const tokens = trimmed.includes(" ") && existsSync(trimmed)
|
|
57
|
+
? [trimmed]
|
|
58
|
+
: splitCliString(trimmed);
|
|
59
|
+
const quote = platform === "win32"
|
|
60
|
+
? (token) => `'${token.replaceAll("'", "''")}'`
|
|
61
|
+
: (token) => `'${token.replaceAll("'", `'\\''`)}'`;
|
|
62
|
+
const command = (tokens.length > 0 ? tokens : [trimmed]).map(quote).join(" ");
|
|
63
|
+
return platform === "win32" ? `& ${command} update` : `${command} update`;
|
|
64
|
+
}
|
|
65
|
+
function codexAstraUpdate(version, models, cli) {
|
|
66
|
+
if (models.options.some((model) => model.id === ASTRA_MODEL_ID) || !codexPredatesAstra(version)) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
title: "Update Codex for GPT-6 Astra",
|
|
71
|
+
message: "This Codex version predates Astra support. Update it, then refresh models. Astra must also be available to your signed-in ChatGPT account.",
|
|
72
|
+
command: codexUpdateCommand(cli),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function decodeConfig(raw) {
|
|
76
|
+
const o = (raw ?? {});
|
|
77
|
+
return {
|
|
78
|
+
cli: typeof o.cli === "string" ? o.cli : "codex",
|
|
79
|
+
fullAuto: o.fullAuto === true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const QUESTION_TIMEOUT_NOTE = "No answer was given — use your best judgment.";
|
|
83
|
+
const DENY_TIMEOUT_NOTE = "OpenMausBot: nobody answered this permission request in time. Skip this action and finish what you can without it.";
|
|
84
|
+
/** RequestPermissionProfile uses null for permission families that were not
|
|
85
|
+
* requested; GrantedPermissionProfile requires those keys to be absent. */
|
|
86
|
+
function grantedPermissions(raw) {
|
|
87
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
88
|
+
return {};
|
|
89
|
+
return Object.fromEntries(Object.entries(raw).filter(([, value]) => value !== null && value !== undefined));
|
|
90
|
+
}
|
|
91
|
+
function additionalPermissionSummary(permissions, reason) {
|
|
92
|
+
const requested = grantedPermissions(permissions);
|
|
93
|
+
const exact = JSON.stringify(requested);
|
|
94
|
+
const prefix = typeof reason === "string" && reason.trim() ? `${reason.trim()} — ` : "";
|
|
95
|
+
return `${prefix}Requested permissions: ${exact}`;
|
|
96
|
+
}
|
|
97
|
+
const plainRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
98
|
+
? value
|
|
99
|
+
: null;
|
|
100
|
+
const containsControlCharacter = (value) => {
|
|
101
|
+
for (const character of value) {
|
|
102
|
+
const code = character.charCodeAt(0);
|
|
103
|
+
if (code <= 0x1f || code === 0x7f)
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
function boundedLabel(value) {
|
|
109
|
+
if (typeof value !== "string")
|
|
110
|
+
return null;
|
|
111
|
+
const label = value.trim();
|
|
112
|
+
return label && label.length <= 160 && !containsControlCharacter(label) ? label : null;
|
|
113
|
+
}
|
|
114
|
+
function ordinaryApprovalValue(value) {
|
|
115
|
+
const normalized = value.trim().toLowerCase();
|
|
116
|
+
if (/session|always|permanent|forever|persistent/.test(normalized))
|
|
117
|
+
return false;
|
|
118
|
+
return normalized === "once" || /^(?:accept|approve|allow)(?:ed|[-_]?once)?$/.test(normalized);
|
|
119
|
+
}
|
|
120
|
+
/** Recognize only schema-backed app-access approvals. Arbitrary MCP forms
|
|
121
|
+
* (credentials, free text, URLs, or required fields without a one-time enum)
|
|
122
|
+
* remain user input and are declined; Full access never fabricates them. */
|
|
123
|
+
function mcpAppApprovalForm(params) {
|
|
124
|
+
const request = plainRecord(params);
|
|
125
|
+
if (!request || request.mode !== "form")
|
|
126
|
+
return null;
|
|
127
|
+
const metadata = plainRecord(request._meta);
|
|
128
|
+
const target = plainRecord(metadata?.target);
|
|
129
|
+
const toolParams = plainRecord(metadata?.tool_params);
|
|
130
|
+
const message = boundedLabel(request.message) ?? "App access requested";
|
|
131
|
+
const appName = [
|
|
132
|
+
metadata?.app_name,
|
|
133
|
+
metadata?.appName,
|
|
134
|
+
metadata?.app,
|
|
135
|
+
target?.app,
|
|
136
|
+
target?.name,
|
|
137
|
+
toolParams?.app_name,
|
|
138
|
+
toolParams?.app,
|
|
139
|
+
metadata?.connector_name,
|
|
140
|
+
metadata?.connectorName,
|
|
141
|
+
].map(boundedLabel).find(Boolean) ?? message.match(/^Allow ChatGPT to use (.+?)\?$/i)?.[1]?.trim();
|
|
142
|
+
// The application identity is the second half of the discriminator. A
|
|
143
|
+
// required approval-looking enum by itself must not turn an arbitrary form
|
|
144
|
+
// into a permission prompt.
|
|
145
|
+
if (!appName)
|
|
146
|
+
return null;
|
|
147
|
+
const schema = plainRecord(request.requestedSchema);
|
|
148
|
+
const properties = plainRecord(schema?.properties);
|
|
149
|
+
const required = schema?.required;
|
|
150
|
+
if (!properties ||
|
|
151
|
+
!Array.isArray(required) ||
|
|
152
|
+
required.length === 0 ||
|
|
153
|
+
required.length > 8 ||
|
|
154
|
+
!required.every((key) => typeof key === "string" && key.length > 0 && key.length <= 100))
|
|
155
|
+
return null;
|
|
156
|
+
const content = {};
|
|
157
|
+
for (const key of required) {
|
|
158
|
+
const field = plainRecord(properties[key]);
|
|
159
|
+
if (!field)
|
|
160
|
+
return null;
|
|
161
|
+
const enumValues = Array.isArray(field.enum)
|
|
162
|
+
? field.enum.filter((value) => typeof value === "string")
|
|
163
|
+
: [];
|
|
164
|
+
const oneOfValues = Array.isArray(field.oneOf)
|
|
165
|
+
? field.oneOf
|
|
166
|
+
.map((option) => boundedLabel(plainRecord(option)?.const))
|
|
167
|
+
.filter((value) => Boolean(value))
|
|
168
|
+
: [];
|
|
169
|
+
const chosen = [...oneOfValues, ...enumValues].find(ordinaryApprovalValue);
|
|
170
|
+
if (!chosen)
|
|
171
|
+
return null;
|
|
172
|
+
content[key] = chosen;
|
|
173
|
+
}
|
|
174
|
+
const tool = boundedLabel(appName) ?? boundedLabel(request.serverName) ?? "app_access";
|
|
175
|
+
return { tool, summary: message, allowResult: { action: "accept", content } };
|
|
176
|
+
}
|
|
177
|
+
/** Codex persists these values on its native thread. Keep them explicit on
|
|
178
|
+
* start, resume, and every turn so switching modes cannot leave a more
|
|
179
|
+
* permissive sandbox/reviewer stuck to the next request. */
|
|
180
|
+
function namedApprovalParams(mode) {
|
|
181
|
+
if (mode === "full") {
|
|
182
|
+
return {
|
|
183
|
+
thread: {
|
|
184
|
+
approvalPolicy: "never",
|
|
185
|
+
approvalsReviewer: "user",
|
|
186
|
+
sandbox: "danger-full-access",
|
|
187
|
+
},
|
|
188
|
+
turn: {
|
|
189
|
+
approvalPolicy: "never",
|
|
190
|
+
approvalsReviewer: "user",
|
|
191
|
+
sandboxPolicy: { type: "dangerFullAccess" },
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
thread: {
|
|
197
|
+
approvalPolicy: "on-request",
|
|
198
|
+
approvalsReviewer: mode === "auto" ? "auto_review" : "user",
|
|
199
|
+
sandbox: "workspace-write",
|
|
200
|
+
},
|
|
201
|
+
turn: {
|
|
202
|
+
approvalPolicy: "on-request",
|
|
203
|
+
approvalsReviewer: mode === "auto" ? "auto_review" : "user",
|
|
204
|
+
sandboxPolicy: { type: "workspaceWrite" },
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function effectiveApprovalPolicy(value) {
|
|
209
|
+
if (value === "untrusted" || value === "on-request" || value === "never")
|
|
210
|
+
return value;
|
|
211
|
+
const granular = plainRecord(plainRecord(value)?.granular);
|
|
212
|
+
if (granular &&
|
|
213
|
+
typeof granular.mcp_elicitations === "boolean" &&
|
|
214
|
+
typeof granular.rules === "boolean" &&
|
|
215
|
+
typeof granular.sandbox_approval === "boolean" &&
|
|
216
|
+
(granular.request_permissions === undefined || typeof granular.request_permissions === "boolean") &&
|
|
217
|
+
(granular.skill_approval === undefined || typeof granular.skill_approval === "boolean")) {
|
|
218
|
+
return {
|
|
219
|
+
granular: {
|
|
220
|
+
mcp_elicitations: granular.mcp_elicitations,
|
|
221
|
+
rules: granular.rules,
|
|
222
|
+
sandbox_approval: granular.sandbox_approval,
|
|
223
|
+
...(typeof granular.request_permissions === "boolean"
|
|
224
|
+
? { request_permissions: granular.request_permissions }
|
|
225
|
+
: {}),
|
|
226
|
+
...(typeof granular.skill_approval === "boolean"
|
|
227
|
+
? { skill_approval: granular.skill_approval }
|
|
228
|
+
: {}),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return "on-request";
|
|
233
|
+
}
|
|
234
|
+
function effectiveApprovalsReviewer(value) {
|
|
235
|
+
return value === "auto_review" || value === "guardian_subagent" ? value : "user";
|
|
236
|
+
}
|
|
237
|
+
function legacyCustomApprovalParams(config) {
|
|
238
|
+
const approvalPolicy = effectiveApprovalPolicy(config.approval_policy);
|
|
239
|
+
const approvalsReviewer = effectiveApprovalsReviewer(config.approvals_reviewer);
|
|
240
|
+
const sandbox = config.sandbox_mode === "workspace-write" ||
|
|
241
|
+
config.sandbox_mode === "danger-full-access" ||
|
|
242
|
+
config.sandbox_mode === "read-only"
|
|
243
|
+
? config.sandbox_mode
|
|
244
|
+
: "read-only";
|
|
245
|
+
let sandboxPolicy;
|
|
246
|
+
if (sandbox === "danger-full-access")
|
|
247
|
+
sandboxPolicy = { type: "dangerFullAccess" };
|
|
248
|
+
else if (sandbox === "read-only")
|
|
249
|
+
sandboxPolicy = { type: "readOnly" };
|
|
250
|
+
else {
|
|
251
|
+
const workspace = config.sandbox_workspace_write && typeof config.sandbox_workspace_write === "object"
|
|
252
|
+
? config.sandbox_workspace_write
|
|
253
|
+
: {};
|
|
254
|
+
sandboxPolicy = {
|
|
255
|
+
type: "workspaceWrite",
|
|
256
|
+
...(Array.isArray(workspace.writable_roots) ? { writableRoots: workspace.writable_roots } : {}),
|
|
257
|
+
...(typeof workspace.network_access === "boolean" ? { networkAccess: workspace.network_access } : {}),
|
|
258
|
+
...(typeof workspace.exclude_slash_tmp === "boolean" ? { excludeSlashTmp: workspace.exclude_slash_tmp } : {}),
|
|
259
|
+
...(typeof workspace.exclude_tmpdir_env_var === "boolean"
|
|
260
|
+
? { excludeTmpdirEnvVar: workspace.exclude_tmpdir_env_var }
|
|
261
|
+
: {}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
thread: { approvalPolicy, approvalsReviewer, sandbox },
|
|
266
|
+
turn: { approvalPolicy, approvalsReviewer, sandboxPolicy },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/** config/read is the app-server's parsed, effective config boundary. Keep the
|
|
270
|
+
* remaining wire validation deliberately small so quoted user profile ids are
|
|
271
|
+
* not accidentally reinterpreted or logged as arbitrary config. */
|
|
272
|
+
function configuredPermissionProfile(config) {
|
|
273
|
+
if (typeof config.default_permissions !== "string")
|
|
274
|
+
return null;
|
|
275
|
+
const profile = config.default_permissions.trim();
|
|
276
|
+
if (!profile || profile.length > 240 || containsControlCharacter(profile))
|
|
277
|
+
return null;
|
|
278
|
+
return profile;
|
|
279
|
+
}
|
|
280
|
+
function customApprovalParams(raw) {
|
|
281
|
+
const config = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
282
|
+
? raw
|
|
283
|
+
: {};
|
|
284
|
+
const fallback = legacyCustomApprovalParams(config);
|
|
285
|
+
const permissions = configuredPermissionProfile(config);
|
|
286
|
+
const approvalPolicy = effectiveApprovalPolicy(config.approval_policy);
|
|
287
|
+
const approvalsReviewer = effectiveApprovalsReviewer(config.approvals_reviewer);
|
|
288
|
+
// Codex 0.151 profiles define the sandbox, but approval policy remains an
|
|
289
|
+
// independent setting. Reassert both approval fields so a resumed Full
|
|
290
|
+
// thread cannot keep `never`; omit only the mutually-exclusive sandboxes.
|
|
291
|
+
return permissions
|
|
292
|
+
? {
|
|
293
|
+
thread: { permissions, approvalPolicy, approvalsReviewer },
|
|
294
|
+
turn: { permissions, approvalPolicy, approvalsReviewer },
|
|
295
|
+
fallback,
|
|
296
|
+
}
|
|
297
|
+
: fallback;
|
|
298
|
+
}
|
|
299
|
+
function permissionProfileUnsupported(error) {
|
|
300
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
301
|
+
return /(?:experimental api|invalid params|unknown field|unknown.*permissions|permissions.*(?:unsupported|sandbox)|cannot.*permissions)/i.test(message);
|
|
302
|
+
}
|
|
303
|
+
/** Keep private host paths out of diagnostics while preserving enough of the
|
|
304
|
+
* app-server input shape to debug image delivery. The unmodified request is
|
|
305
|
+
* still written to the provider immediately after this log copy is made. */
|
|
306
|
+
function codexNativeLogMessage(message) {
|
|
307
|
+
if (!message || typeof message !== "object" || Array.isArray(message))
|
|
308
|
+
return message;
|
|
309
|
+
const record = message;
|
|
310
|
+
if (record.method !== "turn/start")
|
|
311
|
+
return message;
|
|
312
|
+
const params = record.params;
|
|
313
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
314
|
+
return message;
|
|
315
|
+
const input = params.input;
|
|
316
|
+
if (!Array.isArray(input))
|
|
317
|
+
return message;
|
|
318
|
+
return {
|
|
319
|
+
...record,
|
|
320
|
+
params: {
|
|
321
|
+
...params,
|
|
322
|
+
input: input.map((item) => {
|
|
323
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
324
|
+
return item;
|
|
325
|
+
const entry = item;
|
|
326
|
+
return entry.type === "localImage"
|
|
327
|
+
? { ...entry, path: "[private attachment path omitted]" }
|
|
328
|
+
: item;
|
|
329
|
+
}),
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
/** Sanitize provider responses before the native diagnostic tee. Sensitive
|
|
334
|
+
* request ids outlive the request promise, so a response arriving after its
|
|
335
|
+
* timeout is still omitted rather than becoming a secret-bearing orphan. */
|
|
336
|
+
export function codexNativeIncomingLogMessage(message, sensitiveResponseIds) {
|
|
337
|
+
if (message?.id !== undefined && sensitiveResponseIds.has(message.id)) {
|
|
338
|
+
return {
|
|
339
|
+
jsonrpc: message.jsonrpc,
|
|
340
|
+
id: message.id,
|
|
341
|
+
...(message.error !== undefined
|
|
342
|
+
? { error: "[config/read error omitted]" }
|
|
343
|
+
: { result: "[effective config omitted]" }),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (message?.method === "item/completed" && message.params?.item?.type === "imageGeneration") {
|
|
347
|
+
return {
|
|
348
|
+
...message,
|
|
349
|
+
params: {
|
|
350
|
+
...message.params,
|
|
351
|
+
item: {
|
|
352
|
+
...message.params.item,
|
|
353
|
+
result: `[generated image omitted · ${String(message.params.item.result ?? "").length} base64 chars]`,
|
|
354
|
+
savedPath: undefined,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return message;
|
|
360
|
+
}
|
|
361
|
+
function mountMcpServer(appServerArgs, env, name, server, preApproved = true) {
|
|
362
|
+
Object.assign(env, server.env);
|
|
363
|
+
const prefix = `mcp_servers.${name}`;
|
|
364
|
+
appServerArgs.push("-c", `${prefix}.command=${JSON.stringify(server.command)}`, "-c", `${prefix}.args=${JSON.stringify(server.args)}`,
|
|
365
|
+
// Values stay in the child environment; argv contains names only so
|
|
366
|
+
// credentials never appear in process listings or diagnostics.
|
|
367
|
+
"-c", `${prefix}.env_vars=${JSON.stringify(Object.keys(server.env))}`);
|
|
368
|
+
// Harness-owned servers are pre-quieted; a user-configured server keeps
|
|
369
|
+
// codex's on-request policy so its tool calls become approval cards.
|
|
370
|
+
if (preApproved) {
|
|
371
|
+
appServerArgs.push("-c", `${prefix}.default_tools_approval_mode="auto"`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export const CodexDriver = {
|
|
375
|
+
driverKind: DRIVER_KIND,
|
|
376
|
+
metadata: { displayName: "Codex", supportsMultipleInstances: true },
|
|
377
|
+
install: {
|
|
378
|
+
command: {
|
|
379
|
+
darwin: "npm install -g @openai/codex",
|
|
380
|
+
linux: "npm install -g @openai/codex",
|
|
381
|
+
win32: "npm install -g @openai/codex",
|
|
382
|
+
},
|
|
383
|
+
needsNode: true,
|
|
384
|
+
docsUrl: "https://github.com/openai/codex",
|
|
385
|
+
signInCommand: "codex login",
|
|
386
|
+
},
|
|
387
|
+
models: STATIC_CODEX_MODELS,
|
|
388
|
+
decodeConfig,
|
|
389
|
+
defaultConfig: () => decodeConfig({}),
|
|
390
|
+
async create(input) {
|
|
391
|
+
const { instanceId, config } = input;
|
|
392
|
+
const childEnv = () => {
|
|
393
|
+
const env = {
|
|
394
|
+
...process.env,
|
|
395
|
+
...input.environment,
|
|
396
|
+
PATH: augmentedPath(),
|
|
397
|
+
NPM_CONFIG_LOGLEVEL: "error",
|
|
398
|
+
};
|
|
399
|
+
// The CLI owns its own ChatGPT login; a leaked API key silently flips
|
|
400
|
+
// billing to pay-as-you-go (agentcal).
|
|
401
|
+
delete env.OPENAI_API_KEY;
|
|
402
|
+
// The harness process may hold workspace credentials (xai/box/voice
|
|
403
|
+
// keys, env-injected at boot); none of them are this CLI's to see.
|
|
404
|
+
stripWorkspaceCredentialEnv(env);
|
|
405
|
+
return env;
|
|
406
|
+
};
|
|
407
|
+
const catalogEnv = childEnv();
|
|
408
|
+
let models = STATIC_CODEX_MODELS;
|
|
409
|
+
const refreshModels = async () => {
|
|
410
|
+
try {
|
|
411
|
+
const resolved = await readCodexModelCatalog(catalogEnv, fetch, config.cli);
|
|
412
|
+
if (resolved.options.length)
|
|
413
|
+
models = resolved;
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Keep the last usable catalog when a local provider is down.
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
await refreshModels();
|
|
420
|
+
const listeners = new Set();
|
|
421
|
+
const active = new Map();
|
|
422
|
+
const emit = (event) => {
|
|
423
|
+
for (const l of [...listeners])
|
|
424
|
+
l(event);
|
|
425
|
+
};
|
|
426
|
+
const base = (threadId, turnId) => ({
|
|
427
|
+
eventId: newEventId(),
|
|
428
|
+
provider: DRIVER_KIND,
|
|
429
|
+
threadId,
|
|
430
|
+
turnId,
|
|
431
|
+
createdAt: new Date().toISOString(),
|
|
432
|
+
});
|
|
433
|
+
const sendTurn = async (turn) => {
|
|
434
|
+
// One driver instance serves many threads. Interrupt state belongs to
|
|
435
|
+
// this turn so activity elsewhere cannot cancel or revive its retry.
|
|
436
|
+
let stopRequested = false;
|
|
437
|
+
const { threadId } = turn;
|
|
438
|
+
// Direct adapter callers predating the per-bot selector retain the
|
|
439
|
+
// instance's legacy fullAuto setting. Harness turns always send an
|
|
440
|
+
// explicit mode, which takes precedence.
|
|
441
|
+
const approvalMode = turn.approvalMode ?? (config.fullAuto ? "full" : "ask");
|
|
442
|
+
for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
|
|
443
|
+
const reserved = Object.keys(server.env).find(isHarnessOwnedMcpEnvName);
|
|
444
|
+
if (reserved) {
|
|
445
|
+
throw new Error(`Custom MCP server “${name}” cannot set reserved environment variable “${reserved}”`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
let autoAcceptPermissions = approvalMode === "full";
|
|
449
|
+
if (active.has(threadId))
|
|
450
|
+
throw new Error("a turn is already running on this thread");
|
|
451
|
+
const turnId = newId();
|
|
452
|
+
// a retry relaunches the whole app-server; the backoff is scaled down in
|
|
453
|
+
// tests so a fake's transient failures don't stall real seconds
|
|
454
|
+
const retryScale = Number(process.env.FAKE_CODEX_RETRY_SCALE ?? "1");
|
|
455
|
+
const launchAttempt = async (attempt) => {
|
|
456
|
+
const env = childEnv();
|
|
457
|
+
const appServerArgs = ["app-server", ...codexLocalProviderArgs(env, turn.model)];
|
|
458
|
+
if (turn.integrations?.composio) {
|
|
459
|
+
mountMcpServer(appServerArgs, env, "openmausbot_connectors", turn.integrations.composio);
|
|
460
|
+
}
|
|
461
|
+
if (turn.integrations?.agents) {
|
|
462
|
+
mountMcpServer(appServerArgs, env, "agents", turn.integrations.agents);
|
|
463
|
+
}
|
|
464
|
+
if (turn.integrations?.computer) {
|
|
465
|
+
const proxyEnv = computerProxyEnv(turn.integrations.computer);
|
|
466
|
+
mountMcpServer(appServerArgs, env, "computer", {
|
|
467
|
+
command: process.execPath,
|
|
468
|
+
args: [SPAWNED_PROXIES.computer],
|
|
469
|
+
env: {
|
|
470
|
+
ELECTRON_RUN_AS_NODE: "1",
|
|
471
|
+
OGB_BOX_ID: proxyEnv.OGB_BOX_ID ?? "",
|
|
472
|
+
OGB_BOX_TOKEN: proxyEnv.OGB_BOX_TOKEN ?? "",
|
|
473
|
+
// who-is-driving endpoint, so a person taking the wheel in the
|
|
474
|
+
// panel pauses this bot's hands mid-turn
|
|
475
|
+
OMB_CONTROL_URL: proxyEnv.OMB_CONTROL_URL ?? "",
|
|
476
|
+
OMB_CONTROL_TOKEN: proxyEnv.OMB_CONTROL_TOKEN ?? "",
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else if (turn.integrations?.localComputer) {
|
|
481
|
+
// The host daemon and isolated Local VM both arrive as a direct Cua
|
|
482
|
+
// Driver stdio MCP server. Codex sees the same computer tool surface.
|
|
483
|
+
mountMcpServer(appServerArgs, env, "computer", turn.integrations.localComputer);
|
|
484
|
+
}
|
|
485
|
+
if (turn.integrations?.browser) {
|
|
486
|
+
mountMcpServer(appServerArgs, env, "browser", turn.integrations.browser);
|
|
487
|
+
}
|
|
488
|
+
for (const [name, server] of Object.entries(turn.integrations?.custom ?? {})) {
|
|
489
|
+
mountMcpServer(appServerArgs, env, name, server, false);
|
|
490
|
+
}
|
|
491
|
+
if (turn.integrations?.phone) {
|
|
492
|
+
const bridge = turn.integrations.phone;
|
|
493
|
+
Object.assign(env, bridge.env);
|
|
494
|
+
const prefix = "mcp_servers.openmausbot_phone";
|
|
495
|
+
appServerArgs.push("-c", `${prefix}.command=${JSON.stringify(bridge.command)}`, "-c", `${prefix}.args=${JSON.stringify(bridge.args)}`, "-c", `${prefix}.env_vars=${JSON.stringify(Object.keys(bridge.env))}`, "-c", `${prefix}.default_tools_approval_mode="auto"`);
|
|
496
|
+
}
|
|
497
|
+
const child = spawnCli(config.cli, appServerArgs, {
|
|
498
|
+
cwd: turn.cwd ?? homedir(),
|
|
499
|
+
env,
|
|
500
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
501
|
+
});
|
|
502
|
+
let abandoned = false;
|
|
503
|
+
const state = {
|
|
504
|
+
settled: false,
|
|
505
|
+
lastText: "",
|
|
506
|
+
sawStreamDelta: false,
|
|
507
|
+
// codex reports token usage as a running THREAD total; the harness
|
|
508
|
+
// wants this turn's figure, so the last report is banked on settle
|
|
509
|
+
usage: undefined,
|
|
510
|
+
};
|
|
511
|
+
const asks = new Map();
|
|
512
|
+
let nextId = 1;
|
|
513
|
+
const sensitiveResponseIds = new Set();
|
|
514
|
+
const rpcPending = new Map();
|
|
515
|
+
const send = (obj) => {
|
|
516
|
+
try {
|
|
517
|
+
child.stdin.write(JSON.stringify(obj) + "\n");
|
|
518
|
+
}
|
|
519
|
+
catch { }
|
|
520
|
+
appendNative(threadId, {
|
|
521
|
+
dir: "out",
|
|
522
|
+
source: "codex.app-server",
|
|
523
|
+
msg: codexNativeLogMessage(obj),
|
|
524
|
+
});
|
|
525
|
+
};
|
|
526
|
+
const request = (method, params, timeoutMs = 60_000) => new Promise((resolve, reject) => {
|
|
527
|
+
const id = nextId++;
|
|
528
|
+
if (method === "config/read")
|
|
529
|
+
sensitiveResponseIds.add(id);
|
|
530
|
+
// a wedged app-server can accept stdin and never reply; without this
|
|
531
|
+
// the handshake await hangs forever and the bot stays busy for good
|
|
532
|
+
const timer = setTimeout(() => {
|
|
533
|
+
if (rpcPending.delete(id))
|
|
534
|
+
reject(new Error(`codex ${method} timed out after ${timeoutMs}ms`));
|
|
535
|
+
}, timeoutMs);
|
|
536
|
+
if (typeof timer.unref === "function")
|
|
537
|
+
timer.unref();
|
|
538
|
+
rpcPending.set(id, {
|
|
539
|
+
resolve: (v) => {
|
|
540
|
+
clearTimeout(timer);
|
|
541
|
+
resolve(v);
|
|
542
|
+
},
|
|
543
|
+
reject: (e) => {
|
|
544
|
+
clearTimeout(timer);
|
|
545
|
+
reject(e);
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
send({ jsonrpc: "2.0", id, method, params });
|
|
549
|
+
});
|
|
550
|
+
const stop = () => {
|
|
551
|
+
stopRequested = true;
|
|
552
|
+
killCliTree(child);
|
|
553
|
+
};
|
|
554
|
+
const settle = (ok, stopReason) => {
|
|
555
|
+
if (state.settled)
|
|
556
|
+
return;
|
|
557
|
+
state.settled = true;
|
|
558
|
+
for (const finish of [...asks.values()])
|
|
559
|
+
finish("deny", "OpenMausBot: the turn ended", "system");
|
|
560
|
+
for (const p of rpcPending.values())
|
|
561
|
+
p.reject(new Error("turn settled"));
|
|
562
|
+
rpcPending.clear();
|
|
563
|
+
active.delete(threadId);
|
|
564
|
+
emit({ ...base(threadId, turnId), type: "turn.completed", ok, stopReason, cost: null, ...(state.usage ? { usage: state.usage } : {}) });
|
|
565
|
+
stop(); // the app-server never exits on its own
|
|
566
|
+
};
|
|
567
|
+
// server→client approval request → canonical request.opened
|
|
568
|
+
// Host-scope tagging mirrors claude.ts: when this turn mounts the real
|
|
569
|
+
// Mac (not a VM), every card carries approvalScope so the harness's
|
|
570
|
+
// local-computer-block backstop applies to remembered always-allows.
|
|
571
|
+
const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
|
|
572
|
+
const handleServerRequest = (msg) => {
|
|
573
|
+
const method = msg.method;
|
|
574
|
+
const params = msg.params ?? {};
|
|
575
|
+
const legacy = method === "execCommandApproval" || method === "applyPatchApproval";
|
|
576
|
+
const isMcpElicitation = method === "mcpServer/elicitation/request";
|
|
577
|
+
const isLegacyMcpPermission = method === "mcpServer/elicitation/request" &&
|
|
578
|
+
params?._meta?.codex_approval_kind === "mcp_tool_call";
|
|
579
|
+
const mcpAppApproval = isMcpElicitation ? mcpAppApprovalForm(params) : null;
|
|
580
|
+
const isMcpPermission = isLegacyMcpPermission || mcpAppApproval !== null;
|
|
581
|
+
const isQuestion = method === "item/tool/requestUserInput";
|
|
582
|
+
const isAdditionalPermission = method === "item/permissions/requestApproval";
|
|
583
|
+
const isPermission = legacy || isMcpPermission || isAdditionalPermission ||
|
|
584
|
+
method === "item/commandExecution/requestApproval" ||
|
|
585
|
+
method === "item/fileChange/requestApproval";
|
|
586
|
+
// A normal MCP elicitation is a form or URL asking for real user input,
|
|
587
|
+
// not a permission. We cannot safely synthesize its structured answer.
|
|
588
|
+
// Unknown future server requests also fail closed instead of being
|
|
589
|
+
// mistaken for commands and accepted by Full Access.
|
|
590
|
+
if (!isQuestion && !isPermission) {
|
|
591
|
+
if (isMcpElicitation) {
|
|
592
|
+
send({ jsonrpc: "2.0", id: msg.id, result: { action: "decline" } });
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
send({
|
|
596
|
+
jsonrpc: "2.0",
|
|
597
|
+
id: msg.id,
|
|
598
|
+
error: { code: -32601, message: `Unsupported server request: ${method}` },
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const mcpTool = isLegacyMcpPermission
|
|
604
|
+
? String(params.message ?? "").match(/tool "([^"]+)"/)?.[1]
|
|
605
|
+
: undefined;
|
|
606
|
+
const tool = mcpAppApproval
|
|
607
|
+
? mcpAppApproval.tool
|
|
608
|
+
: isLegacyMcpPermission
|
|
609
|
+
? (mcpTool ?? "mcp")
|
|
610
|
+
: isAdditionalPermission
|
|
611
|
+
? "permissions"
|
|
612
|
+
: method === "item/fileChange/requestApproval" || method === "applyPatchApproval"
|
|
613
|
+
? "edit"
|
|
614
|
+
: isQuestion
|
|
615
|
+
? "ask_user"
|
|
616
|
+
: "shell";
|
|
617
|
+
const permissionResult = (allow) => isMcpPermission
|
|
618
|
+
? allow
|
|
619
|
+
? (mcpAppApproval?.allowResult ?? { action: "accept", content: {} })
|
|
620
|
+
: { action: "decline" }
|
|
621
|
+
: isAdditionalPermission
|
|
622
|
+
? { permissions: allow ? grantedPermissions(params.permissions) : {}, scope: "turn" }
|
|
623
|
+
: { decision: allow ? (legacy ? "approved" : "accept") : legacy ? "denied" : "decline" };
|
|
624
|
+
if (autoAcceptPermissions && isPermission) {
|
|
625
|
+
return send({
|
|
626
|
+
jsonrpc: "2.0",
|
|
627
|
+
id: msg.id,
|
|
628
|
+
result: permissionResult(true),
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
const requestId = newId();
|
|
632
|
+
const summary = isAdditionalPermission
|
|
633
|
+
? additionalPermissionSummary(params.permissions, params.reason)
|
|
634
|
+
: isMcpPermission
|
|
635
|
+
? (mcpAppApproval?.summary ?? (typeof params.message === "string" ? params.message : "MCP access requested"))
|
|
636
|
+
: typeof params.command === "string"
|
|
637
|
+
? params.command
|
|
638
|
+
: Array.isArray(params.questions)
|
|
639
|
+
? params.questions.map((q) => q.question ?? q.header).filter(Boolean).join(" · ")
|
|
640
|
+
: typeof params.reason === "string"
|
|
641
|
+
? params.reason
|
|
642
|
+
: tool;
|
|
643
|
+
const choices = isQuestion
|
|
644
|
+
? (params.questions?.[0]?.options ?? []).map((o) => o.label).slice(0, 5)
|
|
645
|
+
: undefined;
|
|
646
|
+
const finish = (behavior, message, source = "user") => {
|
|
647
|
+
if (!asks.delete(requestId))
|
|
648
|
+
return;
|
|
649
|
+
clearTimeout(timer);
|
|
650
|
+
if (isQuestion) {
|
|
651
|
+
const answers = {};
|
|
652
|
+
for (const q of Array.isArray(params.questions) ? params.questions : []) {
|
|
653
|
+
answers[q.id] = { answers: [message || QUESTION_TIMEOUT_NOTE] };
|
|
654
|
+
}
|
|
655
|
+
send({ jsonrpc: "2.0", id: msg.id, result: { answers } });
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
send({
|
|
659
|
+
jsonrpc: "2.0",
|
|
660
|
+
id: msg.id,
|
|
661
|
+
result: permissionResult(behavior === "allow"),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
emit({ ...base(threadId, turnId), type: "request.resolved", requestId, behavior, source });
|
|
665
|
+
};
|
|
666
|
+
const timer = setTimeout(() => (isQuestion ? finish("answer", QUESTION_TIMEOUT_NOTE, "timeout") : finish("deny", DENY_TIMEOUT_NOTE, "timeout")), 15 * 60_000);
|
|
667
|
+
timer.unref?.();
|
|
668
|
+
asks.set(requestId, finish);
|
|
669
|
+
emit({
|
|
670
|
+
...base(threadId, turnId),
|
|
671
|
+
type: "request.opened",
|
|
672
|
+
requestId,
|
|
673
|
+
requestType: isQuestion ? "question" : "permission",
|
|
674
|
+
tool,
|
|
675
|
+
summary,
|
|
676
|
+
choices,
|
|
677
|
+
approvalScope: controlsHost ? "local-computer" : undefined,
|
|
678
|
+
requiresExplicitApproval: isAdditionalPermission || undefined,
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
const handleNotification = (msg) => {
|
|
682
|
+
const p = msg.params ?? {};
|
|
683
|
+
switch (msg.method) {
|
|
684
|
+
// token-level chat text; the item/completed frame follows with the
|
|
685
|
+
// whole message, so its delta is only a fallback when none streamed
|
|
686
|
+
case "item/agentMessage/delta": {
|
|
687
|
+
const delta = typeof p.delta === "string" ? p.delta : "";
|
|
688
|
+
if (delta) {
|
|
689
|
+
state.sawStreamDelta = true;
|
|
690
|
+
emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "assistant_text", delta });
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
case "item/reasoning/textDelta":
|
|
695
|
+
case "item/reasoning/summaryTextDelta": {
|
|
696
|
+
const delta = typeof p.delta === "string" ? p.delta : "";
|
|
697
|
+
if (delta)
|
|
698
|
+
emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "reasoning_text", delta });
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
case "item/started": {
|
|
702
|
+
const item = p.item ?? {};
|
|
703
|
+
const title = item.type === "commandExecution"
|
|
704
|
+
? String(item.command ?? "shell")
|
|
705
|
+
: item.type === "fileChange"
|
|
706
|
+
? "edit"
|
|
707
|
+
: item.type === "mcpToolCall"
|
|
708
|
+
? (item.tool ?? item.name ?? "mcp")
|
|
709
|
+
: item.type === "webSearch"
|
|
710
|
+
? "web_search"
|
|
711
|
+
: null;
|
|
712
|
+
if (title)
|
|
713
|
+
emit({ ...base(threadId, turnId), type: "item.started", itemType: "tool", itemId: item.id, title });
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
case "item/completed": {
|
|
717
|
+
const item = p.item ?? {};
|
|
718
|
+
if (item.type === "agentMessage") {
|
|
719
|
+
if (item.text?.trim()) {
|
|
720
|
+
state.lastText = item.text;
|
|
721
|
+
if (!state.sawStreamDelta) {
|
|
722
|
+
emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "assistant_text", delta: item.text });
|
|
723
|
+
}
|
|
724
|
+
state.sawStreamDelta = false;
|
|
725
|
+
emit({ ...base(threadId, turnId), type: "item.completed", itemType: "assistant_text", text: item.text });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if (item.type === "imageGeneration" && item.status !== "failed") {
|
|
729
|
+
// Current Codex app-server (the same schema consumed by T3
|
|
730
|
+
// Code) returns the generated raster as base64 `result` and
|
|
731
|
+
// may also expose a local `savedPath`. Use bytes, never the
|
|
732
|
+
// provider-owned path: the harness will validate and copy
|
|
733
|
+
// them into its private attachment store.
|
|
734
|
+
if (typeof item.result === "string" && item.result.trim()) {
|
|
735
|
+
emit({
|
|
736
|
+
...base(threadId, turnId),
|
|
737
|
+
type: "item.completed",
|
|
738
|
+
itemType: "assistant_image",
|
|
739
|
+
itemId: item.id,
|
|
740
|
+
data: item.result,
|
|
741
|
+
alt: typeof item.revisedPrompt === "string" ? item.revisedPrompt : undefined,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else if (["commandExecution", "fileChange", "mcpToolCall", "webSearch"].includes(item.type)) {
|
|
746
|
+
emit({
|
|
747
|
+
...base(threadId, turnId),
|
|
748
|
+
type: "item.completed",
|
|
749
|
+
itemType: "tool",
|
|
750
|
+
itemId: item.id,
|
|
751
|
+
ok: item.status !== "failed" && item.status !== "declined",
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
else if (item.type === "reasoning") {
|
|
755
|
+
emit({ ...base(threadId, turnId), type: "item.updated", itemType: "reasoning", tokens: null });
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
case "thread/tokenUsage/updated": {
|
|
760
|
+
// `last` is the most recent turn when the server sends it;
|
|
761
|
+
// `total` is the thread so far — a fresh app-server per turn
|
|
762
|
+
// makes that this turn's figure too
|
|
763
|
+
const turnUsage = p.tokenUsage?.last ?? p.tokenUsage?.total;
|
|
764
|
+
// codex's inputTokens already includes cachedInputTokens; the
|
|
765
|
+
// cached share is carried alongside so the UI can say how much
|
|
766
|
+
// of a turn was context re-read rather than new text
|
|
767
|
+
if (turnUsage) {
|
|
768
|
+
state.usage = {
|
|
769
|
+
input: turnUsage.inputTokens ?? 0,
|
|
770
|
+
output: turnUsage.outputTokens ?? 0,
|
|
771
|
+
...(typeof turnUsage.cachedInputTokens === "number"
|
|
772
|
+
? { cachedInput: turnUsage.cachedInputTokens }
|
|
773
|
+
: {}),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const t = p.tokenUsage?.total;
|
|
777
|
+
if (t) {
|
|
778
|
+
emit({
|
|
779
|
+
...base(threadId, turnId),
|
|
780
|
+
type: "thread.token-usage.updated",
|
|
781
|
+
input: t.inputTokens ?? 0,
|
|
782
|
+
output: t.outputTokens ?? 0,
|
|
783
|
+
...(typeof t.cachedInputTokens === "number"
|
|
784
|
+
? { cachedInput: t.cachedInputTokens }
|
|
785
|
+
: {}),
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
case "turn/completed": {
|
|
791
|
+
const t = p.turn ?? {};
|
|
792
|
+
settle(t.status === "completed", t.status === "completed" ? null : (t.error?.message ?? t.status ?? "failed"));
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
case "error":
|
|
796
|
+
// shape drift: 0.144 sends {message}, 0.139 nests it under
|
|
797
|
+
// {error:{message}} — surface either (agentcal armor)
|
|
798
|
+
{
|
|
799
|
+
const message = p.message ?? p.error?.message;
|
|
800
|
+
if (message)
|
|
801
|
+
emit({ ...base(threadId, turnId), type: "runtime.error", message: String(message).slice(0, 400) });
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
let buf = "";
|
|
807
|
+
// decode as UTF-8 across chunk boundaries — a raw `buf += chunk` splits
|
|
808
|
+
// multibyte characters that straddle two reads and corrupts the text
|
|
809
|
+
child.stdout.setEncoding("utf8");
|
|
810
|
+
child.stdout.on("data", (chunk) => {
|
|
811
|
+
buf += chunk;
|
|
812
|
+
let nl;
|
|
813
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
814
|
+
const line = buf.slice(0, nl);
|
|
815
|
+
buf = buf.slice(nl + 1);
|
|
816
|
+
if (!line.trim())
|
|
817
|
+
continue;
|
|
818
|
+
let msg;
|
|
819
|
+
try {
|
|
820
|
+
msg = JSON.parse(line);
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const loggedMessage = codexNativeIncomingLogMessage(msg, sensitiveResponseIds);
|
|
826
|
+
appendNative(threadId, { dir: "in", source: "codex.app-server", msg: loggedMessage });
|
|
827
|
+
if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) {
|
|
828
|
+
const pend = rpcPending.get(msg.id);
|
|
829
|
+
if (pend) {
|
|
830
|
+
rpcPending.delete(msg.id);
|
|
831
|
+
if (msg.error)
|
|
832
|
+
pend.reject(new Error(msg.error.message ?? JSON.stringify(msg.error)));
|
|
833
|
+
else
|
|
834
|
+
pend.resolve(msg.result);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else if (msg.id !== undefined && msg.method) {
|
|
838
|
+
handleServerRequest(msg);
|
|
839
|
+
}
|
|
840
|
+
else if (msg.method) {
|
|
841
|
+
handleNotification(msg);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
let stderr = "";
|
|
846
|
+
child.stderr.on("data", (c) => {
|
|
847
|
+
stderr += c;
|
|
848
|
+
if (stderr.length > 8192)
|
|
849
|
+
stderr = stderr.slice(-8192);
|
|
850
|
+
});
|
|
851
|
+
child.on("error", (e) => {
|
|
852
|
+
if (abandoned)
|
|
853
|
+
return;
|
|
854
|
+
emit({ ...base(threadId, turnId), type: "runtime.error", ...describeSpawnFailure(e, config.cli) });
|
|
855
|
+
settle(false, "spawn_error");
|
|
856
|
+
});
|
|
857
|
+
child.on("close", (code) => {
|
|
858
|
+
if (abandoned)
|
|
859
|
+
return;
|
|
860
|
+
if (!state.settled) {
|
|
861
|
+
emit({
|
|
862
|
+
...base(threadId, turnId),
|
|
863
|
+
type: "runtime.error",
|
|
864
|
+
message: `codex exited ${code} before turn/completed${stderr ? `: ${stderr.trim().slice(-300)}` : ""}`,
|
|
865
|
+
});
|
|
866
|
+
settle(false, "exit_before_result");
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
active.set(threadId, { stop, turnId, asks });
|
|
870
|
+
// Relaunching the app-server is still the same logical turn. Keep the
|
|
871
|
+
// active process current on every attempt, but announce the turn once.
|
|
872
|
+
if (attempt === 0)
|
|
873
|
+
emit({ ...base(threadId, turnId), type: "turn.started" });
|
|
874
|
+
// handshake + kickoff; a transient failure (5xx/overloaded/reset) gets
|
|
875
|
+
// one relaunch of the whole app-server after backoff — but only when
|
|
876
|
+
// nothing streamed yet, and never for auth/shape errors or interrupts
|
|
877
|
+
try {
|
|
878
|
+
await request("initialize", {
|
|
879
|
+
clientInfo: { name: "openmausbot", version: "1" },
|
|
880
|
+
// Named permission profiles are an experimental app-server field in
|
|
881
|
+
// Codex 0.151. Negotiate them explicitly; older servers ignore this
|
|
882
|
+
// capability and remain on the legacy Custom fallback below.
|
|
883
|
+
capabilities: { experimentalApi: true },
|
|
884
|
+
});
|
|
885
|
+
send({ jsonrpc: "2.0", method: "initialized", params: {} });
|
|
886
|
+
let approvalParams;
|
|
887
|
+
if (approvalMode === "custom") {
|
|
888
|
+
// config/read returns the effective global + project config for this
|
|
889
|
+
// cwd. Reasserting those values is essential: simply omitting them
|
|
890
|
+
// on a resumed thread would keep the previous named mode sticky.
|
|
891
|
+
let effectiveConfig;
|
|
892
|
+
try {
|
|
893
|
+
const configured = await request("config/read", {
|
|
894
|
+
cwd: turn.cwd ?? homedir(),
|
|
895
|
+
includeLayers: false,
|
|
896
|
+
});
|
|
897
|
+
effectiveConfig = configured?.config;
|
|
898
|
+
}
|
|
899
|
+
catch {
|
|
900
|
+
// Older app-servers and transient failures cannot prove the
|
|
901
|
+
// user's configured sandbox. Fall back to interactive read-only
|
|
902
|
+
// instead of inheriting stale Full or silently broadening a
|
|
903
|
+
// possibly read-only config to workspace write.
|
|
904
|
+
effectiveConfig = {};
|
|
905
|
+
}
|
|
906
|
+
approvalParams = customApprovalParams(effectiveConfig);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
approvalParams = namedApprovalParams(approvalMode);
|
|
910
|
+
}
|
|
911
|
+
// Codex's `never` means "do not ask to escalate", not "grant every
|
|
912
|
+
// requested permission". Only the user's explicit OpenMausBot Full
|
|
913
|
+
// mode may synthesize approvals; Custom must preserve the sandbox
|
|
914
|
+
// boundary from config.toml (for example never + read-only).
|
|
915
|
+
autoAcceptPermissions = approvalMode === "full";
|
|
916
|
+
const cursor = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
917
|
+
let codexThreadId = null;
|
|
918
|
+
let startedModel = null;
|
|
919
|
+
if (cursor) {
|
|
920
|
+
try {
|
|
921
|
+
const resumed = await request("thread/resume", {
|
|
922
|
+
threadId: cursor,
|
|
923
|
+
...approvalParams.thread,
|
|
924
|
+
});
|
|
925
|
+
codexThreadId = resumed?.thread?.id ?? cursor;
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
if (approvalParams.fallback && permissionProfileUnsupported(error)) {
|
|
929
|
+
// A server can understand config/read before it understands the
|
|
930
|
+
// profile selector. Retry the same resume safely rather than
|
|
931
|
+
// losing the native thread or inheriting its previous mode.
|
|
932
|
+
approvalParams = approvalParams.fallback;
|
|
933
|
+
try {
|
|
934
|
+
const resumed = await request("thread/resume", {
|
|
935
|
+
threadId: cursor,
|
|
936
|
+
...approvalParams.thread,
|
|
937
|
+
});
|
|
938
|
+
codexThreadId = resumed?.thread?.id ?? cursor;
|
|
939
|
+
}
|
|
940
|
+
catch {
|
|
941
|
+
/* thread gone or resume unsupported — start fresh below */
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/* thread gone or resume unsupported — start fresh below */
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (!codexThreadId) {
|
|
948
|
+
const selection = decodeCodexSelection(turn.model);
|
|
949
|
+
const startThread = () => request("thread/start", {
|
|
950
|
+
cwd: turn.cwd ?? homedir(),
|
|
951
|
+
model: selection.model,
|
|
952
|
+
...(selection.modelProvider ? { modelProvider: selection.modelProvider } : {}),
|
|
953
|
+
...approvalParams.thread,
|
|
954
|
+
ephemeral: false,
|
|
955
|
+
});
|
|
956
|
+
let started;
|
|
957
|
+
try {
|
|
958
|
+
started = await startThread();
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
if (!approvalParams.fallback || !permissionProfileUnsupported(error))
|
|
962
|
+
throw error;
|
|
963
|
+
approvalParams = approvalParams.fallback;
|
|
964
|
+
started = await startThread();
|
|
965
|
+
}
|
|
966
|
+
codexThreadId = started?.thread?.id ?? null;
|
|
967
|
+
startedModel = started?.model ?? null;
|
|
968
|
+
}
|
|
969
|
+
emit({ ...base(threadId, turnId), type: "session.started", sessionId: codexThreadId, model: startedModel ?? turn.model ?? null });
|
|
970
|
+
const promptText = turn.system ? `${turn.system}\n\n${turn.text}` : turn.text;
|
|
971
|
+
const turnInput = [
|
|
972
|
+
...(promptText ? [{ type: "text", text: promptText }] : []),
|
|
973
|
+
...(turn.images ?? []).map((image) => ({ type: "localImage", path: image.path })),
|
|
974
|
+
];
|
|
975
|
+
const startTurn = () => request("turn/start", {
|
|
976
|
+
threadId: codexThreadId,
|
|
977
|
+
input: turnInput,
|
|
978
|
+
...approvalParams.turn,
|
|
979
|
+
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
980
|
+
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
981
|
+
// — both leave the thread's current effort alone, emitting no
|
|
982
|
+
// thread/settings/updated, and thread/resume reads the old value
|
|
983
|
+
// back. The app-server offers no way to clear a level either:
|
|
984
|
+
// "" is rejected outright and thread/start takes no effort at
|
|
985
|
+
// all. So a thread keeps the last level it was sent until it is
|
|
986
|
+
// sent another, and choosing Default lands on the bot's next new
|
|
987
|
+
// thread rather than the current one.
|
|
988
|
+
...(turn.effort ? { effort: turn.effort } : {}),
|
|
989
|
+
});
|
|
990
|
+
try {
|
|
991
|
+
await startTurn();
|
|
992
|
+
}
|
|
993
|
+
catch (error) {
|
|
994
|
+
if (!approvalParams.fallback || !permissionProfileUnsupported(error))
|
|
995
|
+
throw error;
|
|
996
|
+
approvalParams = approvalParams.fallback;
|
|
997
|
+
await startTurn();
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch (e) {
|
|
1001
|
+
const failure = e instanceof Error ? e : { text: String(e) };
|
|
1002
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1003
|
+
const needsAuth = /(?:\b401\b|unauthorized|missing bearer|authentication required)/i.test(message);
|
|
1004
|
+
const verdict = classifyError(failure);
|
|
1005
|
+
if (!state.settled && !needsAuth && verdict.transient && attempt < RETRY_MAX_ATTEMPTS - 1 && state.sawStreamDelta === false) {
|
|
1006
|
+
const delayMs = computeBackoff(attempt);
|
|
1007
|
+
attempt++;
|
|
1008
|
+
emit({
|
|
1009
|
+
...base(threadId, turnId),
|
|
1010
|
+
type: "turn.retrying",
|
|
1011
|
+
attempt,
|
|
1012
|
+
delayMs,
|
|
1013
|
+
reason: verdict.reason,
|
|
1014
|
+
});
|
|
1015
|
+
// This app-server never exits by itself. Retire the failed attempt
|
|
1016
|
+
// and silence its late handlers before the replacement launches.
|
|
1017
|
+
abandoned = true;
|
|
1018
|
+
killCliTree(child);
|
|
1019
|
+
await new Promise((resolve) => {
|
|
1020
|
+
const timer = setTimeout(resolve, Math.max(1, Math.round(delayMs * retryScale)));
|
|
1021
|
+
timer.unref?.();
|
|
1022
|
+
});
|
|
1023
|
+
if (!stopRequested) {
|
|
1024
|
+
void launchAttempt(attempt).catch(() => { });
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
settle(false, "interrupted");
|
|
1028
|
+
}
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (!state.settled) {
|
|
1032
|
+
emit({
|
|
1033
|
+
...base(threadId, turnId),
|
|
1034
|
+
type: "runtime.error",
|
|
1035
|
+
message,
|
|
1036
|
+
...(needsAuth ? { setup: true } : {}),
|
|
1037
|
+
});
|
|
1038
|
+
settle(false, needsAuth ? "auth_required" : "rpc_error");
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
void launchAttempt(0).catch(() => { });
|
|
1043
|
+
return { turnId };
|
|
1044
|
+
};
|
|
1045
|
+
const snapshot = async () => {
|
|
1046
|
+
const env = childEnv();
|
|
1047
|
+
const version = await new Promise((resolve) => {
|
|
1048
|
+
execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim()));
|
|
1049
|
+
});
|
|
1050
|
+
if (!version)
|
|
1051
|
+
return { state: "unavailable", reason: `\`${config.cli}\` CLI not found` };
|
|
1052
|
+
const authenticated = await new Promise((resolve) => {
|
|
1053
|
+
execCli(config.cli, ["login", "status"], { timeout: 8000, env }, (err, stdout, stderr) => resolve(!err && /^logged in\b/im.test(`${stdout}\n${stderr ?? ""}`)));
|
|
1054
|
+
});
|
|
1055
|
+
// childEnv drops OPENAI_API_KEY on purpose — turns run on the ChatGPT login
|
|
1056
|
+
return {
|
|
1057
|
+
state: "available",
|
|
1058
|
+
version,
|
|
1059
|
+
authenticated,
|
|
1060
|
+
update: codexAstraUpdate(version, models, config.cli),
|
|
1061
|
+
billing: "subscription",
|
|
1062
|
+
};
|
|
1063
|
+
};
|
|
1064
|
+
return {
|
|
1065
|
+
instanceId,
|
|
1066
|
+
driverKind: DRIVER_KIND,
|
|
1067
|
+
displayName: input.displayName,
|
|
1068
|
+
enabled: input.enabled,
|
|
1069
|
+
get models() {
|
|
1070
|
+
return models;
|
|
1071
|
+
},
|
|
1072
|
+
refreshModels,
|
|
1073
|
+
snapshot,
|
|
1074
|
+
adapter: {
|
|
1075
|
+
provider: DRIVER_KIND,
|
|
1076
|
+
capabilities: {
|
|
1077
|
+
sessionModelSwitch: "unsupported",
|
|
1078
|
+
computerMcp: true,
|
|
1079
|
+
localComputerMcp: true,
|
|
1080
|
+
composioMcp: true,
|
|
1081
|
+
agentsMcp: true,
|
|
1082
|
+
customMcp: true,
|
|
1083
|
+
phoneMcp: true,
|
|
1084
|
+
browserMcp: true,
|
|
1085
|
+
images: true,
|
|
1086
|
+
nativeImageInput: true,
|
|
1087
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
1088
|
+
},
|
|
1089
|
+
sendTurn,
|
|
1090
|
+
interruptTurn: async (threadId) => active.get(threadId)?.stop(),
|
|
1091
|
+
respondToRequest: async (threadId, requestId, decision) => {
|
|
1092
|
+
const turn = active.get(threadId);
|
|
1093
|
+
const finish = turn?.asks.get(requestId);
|
|
1094
|
+
if (!finish)
|
|
1095
|
+
return "unavailable"; // settled, timed out, or turn gone
|
|
1096
|
+
finish(decision.behavior, decision.message, "user");
|
|
1097
|
+
return decision.behavior === "allow" ? "allowed-once" : decision.behavior === "answer" ? "answered" : "rejected";
|
|
1098
|
+
},
|
|
1099
|
+
hasSession: (threadId) => active.has(threadId),
|
|
1100
|
+
stopAll: async () => {
|
|
1101
|
+
for (const { stop } of active.values())
|
|
1102
|
+
stop();
|
|
1103
|
+
},
|
|
1104
|
+
onEvent: (listener) => {
|
|
1105
|
+
listeners.add(listener);
|
|
1106
|
+
return () => listeners.delete(listener);
|
|
1107
|
+
},
|
|
1108
|
+
},
|
|
1109
|
+
dispose: async () => {
|
|
1110
|
+
for (const { stop } of active.values())
|
|
1111
|
+
stop();
|
|
1112
|
+
listeners.clear();
|
|
1113
|
+
},
|
|
1114
|
+
};
|
|
1115
|
+
},
|
|
1116
|
+
};
|