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,855 @@
|
|
|
1
|
+
// pi — the pi coding agent (@earendil-works/pi-coding-agent) as a native engine.
|
|
2
|
+
//
|
|
3
|
+
// pi exposes a JSON-RPC mode over stdio (`pi --mode rpc --no-session`) rather
|
|
4
|
+
// than ACP, so — like the Claude Code and Codex CLIs — it gets a native driver
|
|
5
|
+
// that speaks its own protocol and emits canonical RuntimeEvents. pi is a
|
|
6
|
+
// BYOK agent: credentials live in ~/.pi/agent/auth.json and are injected by
|
|
7
|
+
// the pi binary itself, so this driver holds no API key and needs no sign-in.
|
|
8
|
+
//
|
|
9
|
+
// Conversation continuity: the first turn sends `new_session` and remembers
|
|
10
|
+
// the returned `sessionFile`; later turns send `switch_session` with that
|
|
11
|
+
// path (the way Claude Code resumes by session id). `sessionFile` is the
|
|
12
|
+
// resumeCursor the harness persists per thread.
|
|
13
|
+
//
|
|
14
|
+
// Model ids in the picker are `provider/modelId` composites (e.g.
|
|
15
|
+
// `ollama-cloud/glm-5.2`); `set_model` splits that into pi's separate
|
|
16
|
+
// `{provider, modelId}` fields. Live local hosts (oMLX / Ollama / EXO /
|
|
17
|
+
// LM Studio / Unsloth) land as `host::model` inject ids the same way the
|
|
18
|
+
// other engines do: mergeLocalInject lists them in Custom, and a pick
|
|
19
|
+
// upserts ~/.pi/agent/models.json so pi can reach the host. The live
|
|
20
|
+
// catalog is probed from `get_available_models` and every entry is flagged
|
|
21
|
+
// `custom` because pi is a custom-only (BYOK) engine — the model picker's
|
|
22
|
+
// Local pane only lists `custom` options for custom-only engines.
|
|
23
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { homedir, tmpdir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { PROVIDER_CREDENTIAL_ENV, stripWorkspaceCredentialEnv } from "../config.js";
|
|
27
|
+
import { computerProxyEnv } from "../container-computer.js";
|
|
28
|
+
import { augmentedPath } from "../env-path.js";
|
|
29
|
+
import { describeSpawnFailure, killCliTree, spawnCli } from "../procs.js";
|
|
30
|
+
import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
31
|
+
import { EFFORT_LEVELS, newEventId, newId } from "../contracts.js";
|
|
32
|
+
import { decodeInjectId, encodeInjectId, hostApiKey, localHost, mergeLocalInject, } from "./local-inject.js";
|
|
33
|
+
import { appendNative } from "./native.js";
|
|
34
|
+
const DRIVER_KIND = "piAgent";
|
|
35
|
+
const PI_ARGS = ["--mode", "rpc", "--no-session"];
|
|
36
|
+
const NODE_ENV_FLAG = { ELECTRON_RUN_AS_NODE: "1" };
|
|
37
|
+
function readPiPromptImages(turn) {
|
|
38
|
+
return (turn.images ?? []).map((image) => ({
|
|
39
|
+
type: "image",
|
|
40
|
+
data: readFileSync(image.path).toString("base64"),
|
|
41
|
+
mimeType: image.mime,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/** Provider-native logs are designed for bug reports. Preserve the RPC
|
|
45
|
+
* shape and encoded size, but never persist a user's image bytes in them. */
|
|
46
|
+
function piNativeLogMessage(message) {
|
|
47
|
+
if (!Array.isArray(message.images))
|
|
48
|
+
return message;
|
|
49
|
+
return {
|
|
50
|
+
...message,
|
|
51
|
+
images: message.images.map((image) => {
|
|
52
|
+
if (!image || typeof image !== "object")
|
|
53
|
+
return image;
|
|
54
|
+
const value = image;
|
|
55
|
+
return typeof value.data === "string"
|
|
56
|
+
? { ...value, data: `[image data: ${value.data.length} base64 chars]` }
|
|
57
|
+
: value;
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Harness effort → pi thinking level (`set_thinking_level`). The sets match
|
|
62
|
+
* one-for-one except for the name of the lowest rung: the harness calls it
|
|
63
|
+
* "none", pi calls it "off". Exported for the test. */
|
|
64
|
+
export function piThinkingLevel(effort) {
|
|
65
|
+
return effort === "none" ? "off" : effort;
|
|
66
|
+
}
|
|
67
|
+
/** Mirror of the Claude driver's integration → stdio MCP mount: every entry is
|
|
68
|
+
* a JSON-RPC 2.0 stdio server the pi-mcp-extension consumes. Returns null when
|
|
69
|
+
* there is nothing to mount (the common case). */
|
|
70
|
+
export function buildMcpServers(turn) {
|
|
71
|
+
const servers = {};
|
|
72
|
+
if (turn.integrations?.composio)
|
|
73
|
+
servers.composio = { ...turn.integrations.composio };
|
|
74
|
+
if (turn.integrations?.computer) {
|
|
75
|
+
servers.computer = {
|
|
76
|
+
command: process.execPath,
|
|
77
|
+
args: [SPAWNED_PROXIES.computer],
|
|
78
|
+
env: { ...NODE_ENV_FLAG, ...computerProxyEnv(turn.integrations.computer) },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
else if (turn.integrations?.localComputer) {
|
|
82
|
+
const local = turn.integrations.localComputer;
|
|
83
|
+
servers.computer = {
|
|
84
|
+
command: local.command,
|
|
85
|
+
args: local.args,
|
|
86
|
+
env: local.env,
|
|
87
|
+
// Host control carries scope so the extension gates every call behind
|
|
88
|
+
// a permission card; isolated computers deliberately omit it.
|
|
89
|
+
...(local.scope ? { scope: local.scope } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (turn.integrations?.agents)
|
|
93
|
+
servers.agents = { ...turn.integrations.agents };
|
|
94
|
+
if (turn.integrations?.phone)
|
|
95
|
+
servers.phone = { ...turn.integrations.phone };
|
|
96
|
+
if (turn.integrations?.dweb) {
|
|
97
|
+
servers.dweb = {
|
|
98
|
+
command: process.execPath,
|
|
99
|
+
args: [SPAWNED_PROXIES.dweb],
|
|
100
|
+
env: { ...NODE_ENV_FLAG, DWEB_URL: turn.integrations.dweb.url },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return Object.keys(servers).length ? servers : null;
|
|
104
|
+
}
|
|
105
|
+
/** Pure parser: turn a `get_available_models` stdout blob into a catalog.
|
|
106
|
+
* Every option is `custom` (pi is BYOK) and id is the `provider/modelId`
|
|
107
|
+
* composite the picker and `set_model` both use. Exported for the test. */
|
|
108
|
+
export function parsePiCatalog(stdout, fallbackDefault = "") {
|
|
109
|
+
const options = [];
|
|
110
|
+
let def = fallbackDefault;
|
|
111
|
+
for (const line of stdout.split("\n")) {
|
|
112
|
+
if (!line.trim())
|
|
113
|
+
continue;
|
|
114
|
+
let msg;
|
|
115
|
+
try {
|
|
116
|
+
msg = JSON.parse(line);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const res = msg;
|
|
122
|
+
if (res?.type !== "response" || res.command !== "get_available_models" || !res.success)
|
|
123
|
+
continue;
|
|
124
|
+
for (const m of res.data?.models ?? []) {
|
|
125
|
+
if (!m?.provider || !m?.id)
|
|
126
|
+
continue;
|
|
127
|
+
const id = `${m.provider}/${m.id}`;
|
|
128
|
+
options.push({ id, label: m.name ?? m.id, custom: true, provider: m.provider });
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (!def && options.length)
|
|
133
|
+
def = options[0].id;
|
|
134
|
+
return { default: def, options };
|
|
135
|
+
}
|
|
136
|
+
/** Split a picker id into pi's `{provider, modelId}`. Accepts both the
|
|
137
|
+
* native `provider/modelId` composite and a live-host `host::model`
|
|
138
|
+
* inject id. */
|
|
139
|
+
export function splitPiModel(id) {
|
|
140
|
+
const inject = decodeInjectId(id);
|
|
141
|
+
if (inject)
|
|
142
|
+
return { provider: inject.host, modelId: inject.model };
|
|
143
|
+
if (!id.includes("/"))
|
|
144
|
+
return null;
|
|
145
|
+
const [provider, ...rest] = id.split("/");
|
|
146
|
+
if (!provider || !rest.length)
|
|
147
|
+
return null;
|
|
148
|
+
return { provider, modelId: rest.join("/") };
|
|
149
|
+
}
|
|
150
|
+
/** Prefer live `host::model` inject rows over the same model already
|
|
151
|
+
* listed as `host/model` from ~/.pi/agent/models.json, so Custom does
|
|
152
|
+
* not show duplicates. */
|
|
153
|
+
export function preferPiInjectRows(catalog) {
|
|
154
|
+
const injectIds = new Set(catalog.options.filter((option) => decodeInjectId(option.id)).map((option) => option.id));
|
|
155
|
+
if (!injectIds.size)
|
|
156
|
+
return catalog;
|
|
157
|
+
const options = catalog.options.filter((option) => {
|
|
158
|
+
if (decodeInjectId(option.id))
|
|
159
|
+
return true;
|
|
160
|
+
const slash = option.id.indexOf("/");
|
|
161
|
+
if (slash <= 0)
|
|
162
|
+
return true;
|
|
163
|
+
return !injectIds.has(encodeInjectId(option.id.slice(0, slash), option.id.slice(slash + 1)));
|
|
164
|
+
});
|
|
165
|
+
let def = catalog.default;
|
|
166
|
+
if (def && !options.some((option) => option.id === def)) {
|
|
167
|
+
const slash = def.indexOf("/");
|
|
168
|
+
const mapped = slash > 0 ? encodeInjectId(def.slice(0, slash), def.slice(slash + 1)) : "";
|
|
169
|
+
def = injectIds.has(mapped) ? mapped : (options[0]?.id ?? "");
|
|
170
|
+
}
|
|
171
|
+
return { default: def, options };
|
|
172
|
+
}
|
|
173
|
+
export async function applyPiLocalCatalog(catalog, env = process.env, fetchImpl = fetch) {
|
|
174
|
+
return preferPiInjectRows(await mergeLocalInject(catalog, env, fetchImpl));
|
|
175
|
+
}
|
|
176
|
+
function piAgentDir(env) {
|
|
177
|
+
return join(env.HOME || env.USERPROFILE || homedir(), ".pi", "agent");
|
|
178
|
+
}
|
|
179
|
+
/** Upsert a live local host into ~/.pi/agent/models.json so `set_model`
|
|
180
|
+
* can reach it. Existing providers and models are kept. Returns the
|
|
181
|
+
* `{provider, modelId}` pair pi's RPC expects, or null when the picker
|
|
182
|
+
* id is not a model at all. */
|
|
183
|
+
export function ensurePiInjectModel(modelId, env = process.env) {
|
|
184
|
+
const split = splitPiModel(modelId);
|
|
185
|
+
if (!split)
|
|
186
|
+
return null;
|
|
187
|
+
const inject = decodeInjectId(modelId);
|
|
188
|
+
if (!inject)
|
|
189
|
+
return split;
|
|
190
|
+
const host = localHost(inject.host);
|
|
191
|
+
if (!host)
|
|
192
|
+
return split;
|
|
193
|
+
const dir = piAgentDir(env);
|
|
194
|
+
mkdirSync(dir, { recursive: true });
|
|
195
|
+
const path = join(dir, "models.json");
|
|
196
|
+
let root = { providers: {} };
|
|
197
|
+
if (existsSync(path)) {
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
200
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
201
|
+
root = parsed;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Malformed — do not destroy the file; still return the split so
|
|
205
|
+
// set_model can try.
|
|
206
|
+
return split;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return split;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const providers = root.providers && typeof root.providers === "object" && !Array.isArray(root.providers)
|
|
214
|
+
? { ...root.providers }
|
|
215
|
+
: {};
|
|
216
|
+
const previous = providers[inject.host];
|
|
217
|
+
const existing = previous && typeof previous === "object" && !Array.isArray(previous)
|
|
218
|
+
? { ...previous }
|
|
219
|
+
: {
|
|
220
|
+
baseUrl: host.baseUrl,
|
|
221
|
+
api: "openai-completions",
|
|
222
|
+
apiKey: hostApiKey(host, env),
|
|
223
|
+
compat: { supportsDeveloperRole: false, supportsReasoningEffort: true },
|
|
224
|
+
models: [],
|
|
225
|
+
};
|
|
226
|
+
existing.baseUrl = host.baseUrl;
|
|
227
|
+
existing.api = typeof existing.api === "string" && existing.api ? existing.api : "openai-completions";
|
|
228
|
+
existing.apiKey = hostApiKey(host, env);
|
|
229
|
+
if (!existing.compat) {
|
|
230
|
+
existing.compat = { supportsDeveloperRole: false, supportsReasoningEffort: true };
|
|
231
|
+
}
|
|
232
|
+
const models = Array.isArray(existing.models)
|
|
233
|
+
? existing.models.filter((row) => Boolean(row) && typeof row === "object" && !Array.isArray(row))
|
|
234
|
+
: [];
|
|
235
|
+
if (!models.some((row) => row.id === inject.model)) {
|
|
236
|
+
models.push({
|
|
237
|
+
id: inject.model,
|
|
238
|
+
name: inject.model,
|
|
239
|
+
reasoning: true,
|
|
240
|
+
input: ["text"],
|
|
241
|
+
contextWindow: 131072,
|
|
242
|
+
maxTokens: 16384,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
existing.models = models;
|
|
246
|
+
providers[inject.host] = existing;
|
|
247
|
+
root.providers = providers;
|
|
248
|
+
writeFileSync(path, `${JSON.stringify(root, null, 2)}\n`, { mode: 0o600 });
|
|
249
|
+
try {
|
|
250
|
+
chmodSync(path, 0o600);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Windows ignores POSIX modes; keep the inject even if chmod is unsupported.
|
|
254
|
+
}
|
|
255
|
+
return split;
|
|
256
|
+
}
|
|
257
|
+
/** The pi-side default model, read from ~/.pi/agent/settings.json so the
|
|
258
|
+
* catalog's `default` matches what `pi` would actually run. Missing file →
|
|
259
|
+
* empty string, and the first option is used instead. */
|
|
260
|
+
function readPiDefaultModel(env) {
|
|
261
|
+
const home = env.HOME || env.USERPROFILE || homedir();
|
|
262
|
+
try {
|
|
263
|
+
const s = JSON.parse(readFileSync(`${home}/.pi/agent/settings.json`, "utf8"));
|
|
264
|
+
return s.defaultProvider && s.defaultModel ? `${s.defaultProvider}/${s.defaultModel}` : "";
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return "";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Probe the live catalog by spawning `pi --mode rpc --no-session`, sending
|
|
271
|
+
* `get_available_models`, and parsing the response. A failed probe resolves
|
|
272
|
+
* with an empty catalog — the instance reports unavailable via snapshot. */
|
|
273
|
+
export async function fetchPiModels(cli, env) {
|
|
274
|
+
const child = spawnCli(cli, PI_ARGS, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
let buf = "";
|
|
277
|
+
let done = false;
|
|
278
|
+
const fallbackDefault = readPiDefaultModel(env);
|
|
279
|
+
const finish = (catalog) => {
|
|
280
|
+
if (done)
|
|
281
|
+
return;
|
|
282
|
+
done = true;
|
|
283
|
+
try {
|
|
284
|
+
killCliTree(child);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
/* already gone */
|
|
288
|
+
}
|
|
289
|
+
resolve(catalog);
|
|
290
|
+
};
|
|
291
|
+
const timer = setTimeout(() => finish({ default: "", options: [] }), 15_000);
|
|
292
|
+
timer.unref?.();
|
|
293
|
+
child.stdout.setEncoding("utf8");
|
|
294
|
+
child.stdout.on("data", (chunk) => {
|
|
295
|
+
buf += chunk;
|
|
296
|
+
let nl;
|
|
297
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
298
|
+
const line = buf.slice(0, nl);
|
|
299
|
+
buf = buf.slice(nl + 1);
|
|
300
|
+
if (!line.trim())
|
|
301
|
+
continue;
|
|
302
|
+
const parsed = parsePiCatalog(line + "\n", fallbackDefault);
|
|
303
|
+
if (parsed.options.length || line.includes('"get_available_models"')) {
|
|
304
|
+
clearTimeout(timer);
|
|
305
|
+
finish(parsed);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
child.on("error", () => finish({ default: "", options: [] }));
|
|
311
|
+
child.on("close", () => finish({ default: "", options: [] }));
|
|
312
|
+
try {
|
|
313
|
+
child.stdin.write(JSON.stringify({ id: "catalog", type: "get_available_models" }) + "\n");
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
finish({ default: "", options: [] });
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function decodeConfig(raw) {
|
|
321
|
+
if (raw === null || raw === undefined)
|
|
322
|
+
return { cli: "pi", fullAuto: false };
|
|
323
|
+
if (typeof raw !== "object")
|
|
324
|
+
throw new Error("pi config must be an object");
|
|
325
|
+
const obj = raw;
|
|
326
|
+
if (obj.cli !== undefined && typeof obj.cli !== "string")
|
|
327
|
+
throw new Error("pi config `cli` must be a string");
|
|
328
|
+
if (obj.fullAuto !== undefined && typeof obj.fullAuto !== "boolean")
|
|
329
|
+
throw new Error("pi config `fullAuto` must be a boolean");
|
|
330
|
+
return {
|
|
331
|
+
cli: obj.cli && obj.cli.trim() ? obj.cli.trim() : "pi",
|
|
332
|
+
fullAuto: obj.fullAuto === true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const EMPTY = { default: "", options: [] };
|
|
336
|
+
function piEnvironment(source) {
|
|
337
|
+
const env = { ...source, PATH: augmentedPath() };
|
|
338
|
+
// pi is BYOK and reads provider keys straight from its environment: an
|
|
339
|
+
// inherited key would silently flip billing onto one the user never granted
|
|
340
|
+
// pi, and workspace credentials are the harness's secrets, not pi's. The
|
|
341
|
+
// keys pi may use live in its own settings file, so the child inherits
|
|
342
|
+
// neither list.
|
|
343
|
+
stripWorkspaceCredentialEnv(env);
|
|
344
|
+
for (const key of PROVIDER_CREDENTIAL_ENV)
|
|
345
|
+
delete env[key];
|
|
346
|
+
return env;
|
|
347
|
+
}
|
|
348
|
+
export const PiDriver = {
|
|
349
|
+
driverKind: DRIVER_KIND,
|
|
350
|
+
metadata: { displayName: "pi", supportsMultipleInstances: true, access: "custom" },
|
|
351
|
+
install: {
|
|
352
|
+
command: {
|
|
353
|
+
darwin: "npm install -g @earendil-works/pi-coding-agent",
|
|
354
|
+
linux: "npm install -g @earendil-works/pi-coding-agent",
|
|
355
|
+
win32: "npm install -g @earendil-works/pi-coding-agent",
|
|
356
|
+
},
|
|
357
|
+
needsNode: true,
|
|
358
|
+
docsUrl: "https://pi.dev",
|
|
359
|
+
},
|
|
360
|
+
models: EMPTY,
|
|
361
|
+
decodeConfig,
|
|
362
|
+
defaultConfig: () => decodeConfig({}),
|
|
363
|
+
async create(input) {
|
|
364
|
+
const { instanceId, config } = input;
|
|
365
|
+
const catalogEnv = piEnvironment({ ...process.env, ...input.environment });
|
|
366
|
+
let models = EMPTY;
|
|
367
|
+
const refreshModels = async () => {
|
|
368
|
+
let base = models;
|
|
369
|
+
try {
|
|
370
|
+
const resolved = await fetchPiModels(config.cli, catalogEnv);
|
|
371
|
+
if (resolved.options.length)
|
|
372
|
+
base = resolved;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// Keep the last usable catalog when the probe fails.
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const next = await applyPiLocalCatalog(base, catalogEnv);
|
|
379
|
+
if (next.options.length)
|
|
380
|
+
models = next;
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
if (base.options.length)
|
|
384
|
+
models = base;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
await refreshModels();
|
|
388
|
+
const listeners = new Set();
|
|
389
|
+
// one active turn per thread
|
|
390
|
+
const active = new Map();
|
|
391
|
+
const emit = (event) => {
|
|
392
|
+
for (const l of [...listeners])
|
|
393
|
+
l(event);
|
|
394
|
+
};
|
|
395
|
+
const base = (threadId, turnId) => ({
|
|
396
|
+
eventId: newEventId(),
|
|
397
|
+
provider: DRIVER_KIND,
|
|
398
|
+
providerInstanceId: instanceId,
|
|
399
|
+
threadId,
|
|
400
|
+
turnId,
|
|
401
|
+
createdAt: new Date().toISOString(),
|
|
402
|
+
});
|
|
403
|
+
const sendTurn = async (turn) => {
|
|
404
|
+
const { threadId } = turn;
|
|
405
|
+
if (active.has(threadId))
|
|
406
|
+
throw new Error("a turn is already running on this thread");
|
|
407
|
+
// Per-bot Ask/Auto is authoritative for harness turns. Preserve the
|
|
408
|
+
// legacy instance flag only for direct adapter callers that omit it.
|
|
409
|
+
const fullAuto = turn.approvalMode === undefined ? config.fullAuto : false;
|
|
410
|
+
// Host control always routes through the permission card; full-auto must
|
|
411
|
+
// never get unapproved hands on the user's machine (same guard as the
|
|
412
|
+
// Claude and ACP drivers).
|
|
413
|
+
const controlsHost = turn.integrations?.localComputer?.scope === "local-computer";
|
|
414
|
+
if (controlsHost && fullAuto) {
|
|
415
|
+
throw new Error("local computer control requires the interactive approval broker");
|
|
416
|
+
}
|
|
417
|
+
const turnId = newId();
|
|
418
|
+
const pending = new Map();
|
|
419
|
+
let settled = false;
|
|
420
|
+
// pi's RPC surface accepts image content directly. Read before spawning
|
|
421
|
+
// so an attachment that disappeared produces one clear dispatch error
|
|
422
|
+
// instead of starting a child that can never receive its prompt.
|
|
423
|
+
const images = readPiPromptImages(turn);
|
|
424
|
+
// Write ~/.pi/agent/models.json before creating any credential-bearing
|
|
425
|
+
// MCP temp files. If model setup fails, there is nothing sensitive to
|
|
426
|
+
// clean up yet.
|
|
427
|
+
if (typeof turn.model === "string" && turn.model) {
|
|
428
|
+
ensurePiInjectModel(turn.model, { ...process.env, ...input.environment });
|
|
429
|
+
}
|
|
430
|
+
// integrations → stdio MCP servers for the pi-mcp-extension. The config
|
|
431
|
+
// carries credentials (box token, composio key, comms token), so it goes
|
|
432
|
+
// into a 0600 temp file removed when the turn settles — never on argv.
|
|
433
|
+
const mcpServers = buildMcpServers(turn);
|
|
434
|
+
let mcpTempDir = null;
|
|
435
|
+
if (mcpServers) {
|
|
436
|
+
mcpTempDir = mkdtempSync(join(tmpdir(), "omb-pi-mcp-"));
|
|
437
|
+
try {
|
|
438
|
+
writeFileSync(join(mcpTempDir, "mcp.json"), JSON.stringify({ mcpServers }), { mode: 0o600 });
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
// A failed write must not leave the temp dir behind — a partial file
|
|
442
|
+
// could still hold the box token / composio key / comms token.
|
|
443
|
+
try {
|
|
444
|
+
rmSync(mcpTempDir, { recursive: true, force: true });
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
/* best effort */
|
|
448
|
+
}
|
|
449
|
+
throw err;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const childArgs = mcpServers ? [...PI_ARGS, "-e", SPAWNED_PROXIES.piMcpExtension] : PI_ARGS;
|
|
453
|
+
// spawnCli can throw synchronously (unresolvable CLI); if it does, the
|
|
454
|
+
// 0600 temp file with the box token / composio key / comms token must
|
|
455
|
+
// not be left on disk — settle() never runs because no child existed.
|
|
456
|
+
const child = (() => {
|
|
457
|
+
try {
|
|
458
|
+
return spawnCli(config.cli, childArgs, {
|
|
459
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
460
|
+
cwd: turn.cwd,
|
|
461
|
+
env: piEnvironment({
|
|
462
|
+
...process.env,
|
|
463
|
+
...input.environment,
|
|
464
|
+
...(mcpServers && mcpTempDir ? { OMB_MCP_CONFIG: join(mcpTempDir, "mcp.json") } : {}),
|
|
465
|
+
}),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
if (mcpTempDir) {
|
|
470
|
+
try {
|
|
471
|
+
rmSync(mcpTempDir, { recursive: true, force: true });
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
/* best effort */
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
throw err;
|
|
478
|
+
}
|
|
479
|
+
})();
|
|
480
|
+
let buf = "";
|
|
481
|
+
let assistantText = "";
|
|
482
|
+
// resolve one-shot RPC responses (new_session / switch_session / set_model)
|
|
483
|
+
const responseWaiters = new Map();
|
|
484
|
+
const rejectWaiters = (err) => {
|
|
485
|
+
for (const waiter of responseWaiters.values()) {
|
|
486
|
+
clearTimeout(waiter.timer);
|
|
487
|
+
waiter.reject(err);
|
|
488
|
+
}
|
|
489
|
+
responseWaiters.clear();
|
|
490
|
+
};
|
|
491
|
+
const awaitResponse = (command, timeoutMs = 20_000) => new Promise((resolve, reject) => {
|
|
492
|
+
const timer = setTimeout(() => {
|
|
493
|
+
responseWaiters.delete(command);
|
|
494
|
+
reject(new Error(`pi ${command} timed out`));
|
|
495
|
+
}, timeoutMs);
|
|
496
|
+
timer.unref?.();
|
|
497
|
+
responseWaiters.set(command, { resolve, reject, timer });
|
|
498
|
+
});
|
|
499
|
+
child.stdin.on("error", () => rejectWaiters(new Error("pi stdin closed")));
|
|
500
|
+
const send = (obj) => {
|
|
501
|
+
appendNative(threadId, { dir: "out", source: "pi.rpc", msg: piNativeLogMessage(obj) });
|
|
502
|
+
child.stdin.write(JSON.stringify(obj) + "\n");
|
|
503
|
+
};
|
|
504
|
+
/** Emit buffered assistant text as its own item, then clear it. */
|
|
505
|
+
const flushAssistantText = () => {
|
|
506
|
+
const text = assistantText;
|
|
507
|
+
assistantText = "";
|
|
508
|
+
if (!text.trim())
|
|
509
|
+
return;
|
|
510
|
+
emit({ ...base(threadId, turnId), type: "item.completed", itemType: "assistant_text", text });
|
|
511
|
+
};
|
|
512
|
+
const settle = (ok, stopReason, usage) => {
|
|
513
|
+
if (settled)
|
|
514
|
+
return;
|
|
515
|
+
settled = true;
|
|
516
|
+
flushAssistantText();
|
|
517
|
+
emit({
|
|
518
|
+
...base(threadId, turnId),
|
|
519
|
+
type: "turn.completed",
|
|
520
|
+
ok,
|
|
521
|
+
stopReason: stopReason ?? (ok ? "end_turn" : "failed"),
|
|
522
|
+
...(usage ? { usage: { input: usage.input ?? 0, output: usage.output ?? 0 } } : {}),
|
|
523
|
+
});
|
|
524
|
+
try {
|
|
525
|
+
child.stdin.end();
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
/* already closed */
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
killCliTree(child);
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
/* already gone */
|
|
535
|
+
}
|
|
536
|
+
if (mcpTempDir) {
|
|
537
|
+
try {
|
|
538
|
+
rmSync(mcpTempDir, { recursive: true, force: true });
|
|
539
|
+
}
|
|
540
|
+
catch {
|
|
541
|
+
/* best effort */
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
active.delete(threadId);
|
|
545
|
+
};
|
|
546
|
+
const stop = () => {
|
|
547
|
+
try {
|
|
548
|
+
send({ type: "abort" });
|
|
549
|
+
}
|
|
550
|
+
catch {
|
|
551
|
+
/* ignore */
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
killCliTree(child);
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
/* ignore */
|
|
558
|
+
}
|
|
559
|
+
settle(true, "cancelled");
|
|
560
|
+
};
|
|
561
|
+
active.set(threadId, { stop, turnId, pending, child });
|
|
562
|
+
const onEvent = (evt) => {
|
|
563
|
+
appendNative(threadId, { dir: "in", source: "pi.rpc", msg: evt });
|
|
564
|
+
switch (evt.type) {
|
|
565
|
+
case "response": {
|
|
566
|
+
if (evt.command && responseWaiters.has(evt.command)) {
|
|
567
|
+
const waiter = responseWaiters.get(evt.command);
|
|
568
|
+
responseWaiters.delete(evt.command);
|
|
569
|
+
clearTimeout(waiter.timer);
|
|
570
|
+
if (evt.success)
|
|
571
|
+
waiter.resolve(evt.data);
|
|
572
|
+
else
|
|
573
|
+
waiter.reject(new Error(`pi ${evt.command} failed`));
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
case "message_update": {
|
|
578
|
+
const e = evt.assistantMessageEvent;
|
|
579
|
+
if (!e)
|
|
580
|
+
return;
|
|
581
|
+
if (e.type === "text_delta" && typeof e.delta === "string") {
|
|
582
|
+
assistantText += e.delta;
|
|
583
|
+
emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "assistant_text", delta: e.delta });
|
|
584
|
+
}
|
|
585
|
+
else if (e.type === "thinking_delta" && typeof e.delta === "string") {
|
|
586
|
+
emit({ ...base(threadId, turnId), type: "content.delta", streamKind: "reasoning_text", delta: e.delta });
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
case "tool_execution_start": {
|
|
591
|
+
flushAssistantText();
|
|
592
|
+
emit({
|
|
593
|
+
...base(threadId, turnId),
|
|
594
|
+
type: "item.started",
|
|
595
|
+
itemType: "tool",
|
|
596
|
+
itemId: evt.toolCallId,
|
|
597
|
+
title: String(evt.toolName ?? "tool").slice(0, 80),
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
case "tool_execution_end": {
|
|
602
|
+
emit({
|
|
603
|
+
...base(threadId, turnId),
|
|
604
|
+
type: "item.completed",
|
|
605
|
+
itemType: "tool",
|
|
606
|
+
itemId: evt.toolCallId,
|
|
607
|
+
ok: !evt.isError,
|
|
608
|
+
});
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
case "extension_ui_request": {
|
|
612
|
+
// pi floods setWidget/setStatus for TUI bookkeeping; only
|
|
613
|
+
// select/confirm/input are questions that wait for an answer.
|
|
614
|
+
if (evt.method === "select" || evt.method === "confirm" || evt.method === "input") {
|
|
615
|
+
flushAssistantText();
|
|
616
|
+
const reqId = evt.id ?? newId();
|
|
617
|
+
const isQuestion = evt.method === "input";
|
|
618
|
+
// Register BEFORE emitting: the harness may auto-approve from
|
|
619
|
+
// inside its synchronous request.opened listener. Emitting first
|
|
620
|
+
// made respondToRequest see no pending ask, return unavailable,
|
|
621
|
+
// then fall back to a human card on every "Always allow" call.
|
|
622
|
+
pending.set(reqId, (decision) => {
|
|
623
|
+
if (decision.behavior === "deny")
|
|
624
|
+
send({ type: "extension_ui_response", id: reqId, cancelled: true });
|
|
625
|
+
else if (isQuestion)
|
|
626
|
+
send({ type: "extension_ui_response", id: reqId, value: decision.message ?? "" });
|
|
627
|
+
else
|
|
628
|
+
send({ type: "extension_ui_response", id: reqId, confirmed: true });
|
|
629
|
+
});
|
|
630
|
+
emit({
|
|
631
|
+
...base(threadId, turnId),
|
|
632
|
+
requestId: reqId,
|
|
633
|
+
type: "request.opened",
|
|
634
|
+
requestType: isQuestion ? "question" : "permission",
|
|
635
|
+
tool: String(evt.title ?? "pi"),
|
|
636
|
+
summary: String(evt.title ?? "pi wants confirmation"),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
case "turn_end":
|
|
642
|
+
case "agent_end": {
|
|
643
|
+
const sr = evt.message?.stopReason;
|
|
644
|
+
// toolUse means pi ran a tool and auto-continues next turn to
|
|
645
|
+
// answer — settling now would drop the final reply.
|
|
646
|
+
if (sr === "toolUse" || sr === "tool_use" || sr === "tool_calls")
|
|
647
|
+
return;
|
|
648
|
+
const usage = evt.usage ?? evt.message?.usage;
|
|
649
|
+
if (sr === "error" || sr === "failed") {
|
|
650
|
+
emit({
|
|
651
|
+
...base(threadId, turnId),
|
|
652
|
+
type: "runtime.error",
|
|
653
|
+
message: String(evt.message?.errorMessage ?? "pi turn failed").slice(0, 2_000),
|
|
654
|
+
});
|
|
655
|
+
settle(false, "failed", usage);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
settle(true, sr === "cancelled" || sr === "aborted" ? "cancelled" : "end_turn", usage);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
default:
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
child.stdout.setEncoding("utf8");
|
|
666
|
+
child.stdout.on("data", (chunk) => {
|
|
667
|
+
buf += chunk;
|
|
668
|
+
let nl;
|
|
669
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
670
|
+
const line = buf.slice(0, nl);
|
|
671
|
+
buf = buf.slice(nl + 1);
|
|
672
|
+
if (!line.trim())
|
|
673
|
+
continue;
|
|
674
|
+
try {
|
|
675
|
+
onEvent(JSON.parse(line));
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
/* skip non-JSON line */
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
child.on("error", (err) => {
|
|
683
|
+
const fail = describeSpawnFailure(err, config.cli);
|
|
684
|
+
rejectWaiters(new Error(fail.message));
|
|
685
|
+
emit({ ...base(threadId, turnId), type: "runtime.error", message: fail.message, setup: fail.setup });
|
|
686
|
+
settle(false);
|
|
687
|
+
});
|
|
688
|
+
child.on("close", () => {
|
|
689
|
+
// a clean close without a terminal event is a failed turn, never a hang
|
|
690
|
+
rejectWaiters(new Error("pi process exited before replying"));
|
|
691
|
+
settle(false);
|
|
692
|
+
});
|
|
693
|
+
emit({ ...base(threadId, turnId), type: "turn.started" });
|
|
694
|
+
// handshake: resume the remembered session or start a fresh one. The
|
|
695
|
+
// harness persists session.started.sessionId as the resumeCursor and
|
|
696
|
+
// hands it back next turn, so that id IS the resume handle — pi's
|
|
697
|
+
// sessionFile, which switch_session expects as `sessionPath`.
|
|
698
|
+
const sessionPath = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
699
|
+
let sessionFile = sessionPath;
|
|
700
|
+
try {
|
|
701
|
+
const command = sessionPath ? "switch_session" : "new_session";
|
|
702
|
+
const hsPromise = awaitResponse(command);
|
|
703
|
+
send(sessionPath ? { type: "switch_session", sessionPath } : { type: "new_session" });
|
|
704
|
+
const hs = (await hsPromise);
|
|
705
|
+
if (hs?.sessionFile)
|
|
706
|
+
sessionFile = hs.sessionFile;
|
|
707
|
+
emit({
|
|
708
|
+
...base(threadId, turnId),
|
|
709
|
+
type: "session.started",
|
|
710
|
+
sessionId: sessionFile ?? hs?.sessionId ?? null,
|
|
711
|
+
model: turn.model ?? null,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
// without a session we can still try a bare prompt; pi --no-session
|
|
716
|
+
// accepts a prompt without an explicit session.
|
|
717
|
+
}
|
|
718
|
+
// pin the chosen model (composite id or host::model inject → provider + modelId)
|
|
719
|
+
const chosen = typeof turn.model === "string" ? splitPiModel(turn.model) : null;
|
|
720
|
+
if (chosen) {
|
|
721
|
+
try {
|
|
722
|
+
const modelPromise = awaitResponse("set_model");
|
|
723
|
+
send({ type: "set_model", provider: chosen.provider, modelId: chosen.modelId });
|
|
724
|
+
await modelPromise;
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
/* keep going on the default model */
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// pin reasoning effort after the model (the supported level set is
|
|
731
|
+
// model-dependent); a rejection keeps the engine default
|
|
732
|
+
if (turn.effort) {
|
|
733
|
+
try {
|
|
734
|
+
const levelPromise = awaitResponse("set_thinking_level");
|
|
735
|
+
send({ type: "set_thinking_level", level: piThinkingLevel(turn.effort) });
|
|
736
|
+
await levelPromise;
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
739
|
+
/* keep going on the engine default */
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const message = turn.system ? `${turn.system}\n\n${turn.text}` : turn.text;
|
|
743
|
+
try {
|
|
744
|
+
send({ type: "prompt", message, ...(images.length ? { images } : {}) });
|
|
745
|
+
}
|
|
746
|
+
catch {
|
|
747
|
+
settle(false);
|
|
748
|
+
}
|
|
749
|
+
return { turnId };
|
|
750
|
+
};
|
|
751
|
+
const snapshot = async () => {
|
|
752
|
+
const version = await new Promise((resolve) => {
|
|
753
|
+
const child = spawnCli(config.cli, ["--version"], {
|
|
754
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
755
|
+
env: piEnvironment({ ...process.env, ...input.environment }),
|
|
756
|
+
});
|
|
757
|
+
let out = "";
|
|
758
|
+
child.stdout?.setEncoding("utf8");
|
|
759
|
+
child.stdout?.on("data", (c) => (out += c));
|
|
760
|
+
const timer = setTimeout(() => {
|
|
761
|
+
try {
|
|
762
|
+
killCliTree(child);
|
|
763
|
+
}
|
|
764
|
+
catch {
|
|
765
|
+
/* ignore */
|
|
766
|
+
}
|
|
767
|
+
resolve(null);
|
|
768
|
+
}, 8000);
|
|
769
|
+
timer.unref?.();
|
|
770
|
+
child.on("error", () => {
|
|
771
|
+
clearTimeout(timer);
|
|
772
|
+
resolve(null);
|
|
773
|
+
});
|
|
774
|
+
child.on("close", () => {
|
|
775
|
+
clearTimeout(timer);
|
|
776
|
+
resolve(out.trim() || null);
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
if (!version)
|
|
780
|
+
return { state: "unavailable", reason: `\`${config.cli}\` CLI not found` };
|
|
781
|
+
// pi manages its own credentials (~/.pi/agent/auth.json); there is no
|
|
782
|
+
// separate sign-in step the harness can probe, so treat it as authed.
|
|
783
|
+
return { state: "available", version, authenticated: true };
|
|
784
|
+
};
|
|
785
|
+
return {
|
|
786
|
+
instanceId,
|
|
787
|
+
driverKind: DRIVER_KIND,
|
|
788
|
+
displayName: input.displayName,
|
|
789
|
+
enabled: input.enabled,
|
|
790
|
+
get models() {
|
|
791
|
+
return models;
|
|
792
|
+
},
|
|
793
|
+
refreshModels,
|
|
794
|
+
snapshot,
|
|
795
|
+
adapter: {
|
|
796
|
+
provider: DRIVER_KIND,
|
|
797
|
+
capabilities: {
|
|
798
|
+
// model is set per turn via set_model before prompt
|
|
799
|
+
sessionModelSwitch: "in-session",
|
|
800
|
+
// Integrations arrive as stdio MCP servers mounted by the
|
|
801
|
+
// pi-mcp-extension (pi core has no MCP client of its own).
|
|
802
|
+
agentsMcp: true,
|
|
803
|
+
computerMcp: true,
|
|
804
|
+
composioMcp: true,
|
|
805
|
+
phoneMcp: true,
|
|
806
|
+
// Host control (the user's real Mac) rides the pi-native permission
|
|
807
|
+
// card (`ctx.ui.confirm` → extension_ui_request) gated in the
|
|
808
|
+
// extension, so it is offered exactly when the other engines offer
|
|
809
|
+
// it: enabled unless the bot is in full-auto.
|
|
810
|
+
localComputerMcp: true,
|
|
811
|
+
// Images ride pi's native RPC prompt as base64 content blocks, so a
|
|
812
|
+
// vision model can inspect them without a separate file-read tool.
|
|
813
|
+
images: true,
|
|
814
|
+
nativeImageInput: true,
|
|
815
|
+
// Reasoning effort pins pi's thinking level per turn (none → off).
|
|
816
|
+
// xhigh/max only land on models that expose them; pi rejects an
|
|
817
|
+
// unsupported level and the turn keeps the engine default.
|
|
818
|
+
effortLevels: EFFORT_LEVELS,
|
|
819
|
+
},
|
|
820
|
+
sendTurn,
|
|
821
|
+
interruptTurn: async (threadId) => active.get(threadId)?.stop(),
|
|
822
|
+
respondToRequest: async (threadId, requestId, decision) => {
|
|
823
|
+
const entry = active.get(threadId);
|
|
824
|
+
const answer = entry?.pending.get(requestId);
|
|
825
|
+
if (!entry || !answer)
|
|
826
|
+
return "unavailable";
|
|
827
|
+
entry.pending.delete(requestId);
|
|
828
|
+
answer({ behavior: decision.behavior, message: decision.message });
|
|
829
|
+
emit({
|
|
830
|
+
...base(threadId, entry.turnId),
|
|
831
|
+
requestId,
|
|
832
|
+
type: "request.resolved",
|
|
833
|
+
behavior: decision.behavior,
|
|
834
|
+
source: "user",
|
|
835
|
+
});
|
|
836
|
+
return decision.behavior === "allow" ? "allowed-once" : decision.behavior === "answer" ? "answered" : "rejected";
|
|
837
|
+
},
|
|
838
|
+
hasSession: (threadId) => active.has(threadId),
|
|
839
|
+
stopAll: async () => {
|
|
840
|
+
for (const { stop } of active.values())
|
|
841
|
+
stop();
|
|
842
|
+
},
|
|
843
|
+
onEvent: (listener) => {
|
|
844
|
+
listeners.add(listener);
|
|
845
|
+
return () => listeners.delete(listener);
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
dispose: async () => {
|
|
849
|
+
for (const { stop } of active.values())
|
|
850
|
+
stop();
|
|
851
|
+
listeners.clear();
|
|
852
|
+
},
|
|
853
|
+
};
|
|
854
|
+
},
|
|
855
|
+
};
|