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,195 @@
|
|
|
1
|
+
// The spoken register — turning an agent's answer into something worth
|
|
2
|
+
// hearing.
|
|
3
|
+
//
|
|
4
|
+
// Agents write for a screen: fenced code, file paths, tables, link soup,
|
|
5
|
+
// bullet scaffolding. Read aloud verbatim that is unbearable — a 40-line
|
|
6
|
+
// diff becomes four minutes of punctuation, and `server/drivers/acp/core.ts`
|
|
7
|
+
// becomes "server slash drivers slash a c p slash core dot t s".
|
|
8
|
+
//
|
|
9
|
+
// So every string is passed through here before it reaches a voice. The
|
|
10
|
+
// rule is: say the prose, name the artifacts, drop the syntax. A code block
|
|
11
|
+
// becomes "a code block" — the user is looking at the screen if they care,
|
|
12
|
+
// and this is the half of the feature that decides whether it is pleasant.
|
|
13
|
+
//
|
|
14
|
+
// Pure and synchronous on purpose: it is the piece most likely to need
|
|
15
|
+
// tuning against real transcripts, so it stays trivially testable.
|
|
16
|
+
/** A fenced block becomes a mention of itself, with its language if known. */
|
|
17
|
+
function describeCodeBlock(fence) {
|
|
18
|
+
const lang = fence.trim().split(/\s+/)[0]?.replace(/[^a-z0-9+#]/gi, "") ?? "";
|
|
19
|
+
const spoken = {
|
|
20
|
+
ts: "TypeScript",
|
|
21
|
+
tsx: "TypeScript",
|
|
22
|
+
js: "JavaScript",
|
|
23
|
+
jsx: "JavaScript",
|
|
24
|
+
py: "Python",
|
|
25
|
+
sh: "shell",
|
|
26
|
+
bash: "shell",
|
|
27
|
+
zsh: "shell",
|
|
28
|
+
json: "JSON",
|
|
29
|
+
yml: "YAML",
|
|
30
|
+
yaml: "YAML",
|
|
31
|
+
sql: "SQL",
|
|
32
|
+
rs: "Rust",
|
|
33
|
+
go: "Go",
|
|
34
|
+
swift: "Swift",
|
|
35
|
+
diff: "diff",
|
|
36
|
+
};
|
|
37
|
+
const name = spoken[lang.toLowerCase()];
|
|
38
|
+
return name ? `. (a ${name} code block) ` : ". (a code block) ";
|
|
39
|
+
}
|
|
40
|
+
/** `a/b/c/file.ts` → `file.ts`. A path's directories are for the eye. */
|
|
41
|
+
function shortenPaths(text) {
|
|
42
|
+
return text.replace(/(?:[\w.@-]+\/){1,}([\w.-]+\.\w{1,6})\b/g, "$1");
|
|
43
|
+
}
|
|
44
|
+
/** Trailing punctuation that already ends a sentence. */
|
|
45
|
+
const ENDS_SENTENCE = /[.!?:;]\s*$/;
|
|
46
|
+
/**
|
|
47
|
+
* Markdown (or any agent output) → a line a voice can read.
|
|
48
|
+
*
|
|
49
|
+
* Not a markdown parser: a sequence of narrow, ordered replacements, which
|
|
50
|
+
* is both fast enough to run per token-flush and easy to reason about when
|
|
51
|
+
* one of them misfires on a real transcript.
|
|
52
|
+
*/
|
|
53
|
+
export function speakable(input) {
|
|
54
|
+
if (!input)
|
|
55
|
+
return "";
|
|
56
|
+
let text = input;
|
|
57
|
+
// fenced code first — everything inside must survive none of the rules below
|
|
58
|
+
text = text.replace(/```([^\n]*)\n[\s\S]*?(?:```|$)/g, (_m, fence) => describeCodeBlock(fence));
|
|
59
|
+
text = text.replace(/~~~([^\n]*)\n[\s\S]*?(?:~~~|$)/g, (_m, fence) => describeCodeBlock(fence));
|
|
60
|
+
// images before links — the syntax differs by one character
|
|
61
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, (_m, alt) => (alt ? `. (image: ${alt}) ` : ". (an image) "));
|
|
62
|
+
// [label](url) → label; a bare url becomes a noun rather than an alphabet soup
|
|
63
|
+
text = text.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
|
|
64
|
+
text = text.replace(/<https?:\/\/[^>\s]+>/g, " a link ");
|
|
65
|
+
text = text.replace(/\bhttps?:\/\/\S+/g, " a link ");
|
|
66
|
+
// tables: a grid is hopeless aloud, and the separator row is pure noise
|
|
67
|
+
text = text.replace(/^\s*\|?[\s:-]*\|[\s|:-]*$/gm, "");
|
|
68
|
+
text = text.replace(/^\s*\|(.+)\|\s*$/gm, (_m, row) => row
|
|
69
|
+
.split("|")
|
|
70
|
+
.map((cell) => cell.trim())
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join(", "));
|
|
73
|
+
// inline code: keep short identifiers (they carry meaning), drop long ones
|
|
74
|
+
text = text.replace(/`([^`\n]+)`/g, (_m, code) => (code.length <= 40 ? code : " that snippet "));
|
|
75
|
+
// headings become sentences so the voice pauses instead of running on
|
|
76
|
+
text = text.replace(/^\s{0,3}#{1,6}\s+(.*)$/gm, (_m, head) => ENDS_SENTENCE.test(head) ? head : `${head.trim()}.`);
|
|
77
|
+
// list scaffolding: the marker is visual, the pause is what matters
|
|
78
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "");
|
|
79
|
+
text = text.replace(/^\s*\d+[.)]\s+/gm, "");
|
|
80
|
+
text = text.replace(/^\s*>\s?/gm, "");
|
|
81
|
+
text = text.replace(/^\s*(?:[-*_]\s*){3,}$/gm, "");
|
|
82
|
+
// emphasis / strikethrough markers
|
|
83
|
+
text = text.replace(/(\*\*|__)(.*?)\1/g, "$2");
|
|
84
|
+
text = text.replace(/(\*|_)(?=\S)(.*?)(?<=\S)\1/g, "$2");
|
|
85
|
+
text = text.replace(/~~(.*?)~~/g, "$1");
|
|
86
|
+
// checkboxes read as literal brackets otherwise
|
|
87
|
+
text = text.replace(/\[[ xX]\]\s*/g, "");
|
|
88
|
+
text = shortenPaths(text);
|
|
89
|
+
// emoji and the pictographic ranges: a voice either ignores them or,
|
|
90
|
+
// worse, announces them by name
|
|
91
|
+
text = text.replace(/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}]/gu, "");
|
|
92
|
+
// a blank line is a paragraph break — make it an audible one
|
|
93
|
+
text = text.replace(/\n{2,}/g, ". ");
|
|
94
|
+
text = text.replace(/\n/g, ". ");
|
|
95
|
+
// tidy the punctuation the substitutions above inevitably pile up
|
|
96
|
+
text = text.replace(/\s+/g, " ");
|
|
97
|
+
text = text.replace(/\s+([.,!?;:])/g, "$1");
|
|
98
|
+
text = text.replace(/(?:\.\s*){2,}/g, ". ");
|
|
99
|
+
text = text.replace(/,\s*\./g, ".");
|
|
100
|
+
text = text.trim();
|
|
101
|
+
// The paragraph→". " rule turns whitespace-only input into a lone full
|
|
102
|
+
// stop, which a synthesizer happily renders as a click. Nothing to say
|
|
103
|
+
// means nothing to send.
|
|
104
|
+
return /[\p{L}\p{N}]/u.test(text) ? text : "";
|
|
105
|
+
}
|
|
106
|
+
/** Sentence-ish boundary: `.`/`!`/`?` followed by space, but not inside a
|
|
107
|
+
* decimal, an ellipsis, or a common abbreviation. */
|
|
108
|
+
const BOUNDARY = /(?<!\b(?:e\.g|i\.e|etc|vs|Dr|Mr|Mrs|Ms|No|approx))(?<![.\d])([.!?])(["')\]]*)\s+/g;
|
|
109
|
+
/**
|
|
110
|
+
* Split speakable text into utterances a synthesizer can start on.
|
|
111
|
+
*
|
|
112
|
+
* Both tiers want this: cloud TTS charges and buffers per request, and the
|
|
113
|
+
* local model generates per utterance — so the unit of work is a sentence
|
|
114
|
+
* either way. Very short fragments are glued onto their neighbour, because
|
|
115
|
+
* a synthesizer given two words produces two words of flat, contextless
|
|
116
|
+
* prosody.
|
|
117
|
+
*/
|
|
118
|
+
export function toUtterances(input, { minChars = 12, maxChars = 320 } = {}) {
|
|
119
|
+
const text = speakable(input);
|
|
120
|
+
if (!text)
|
|
121
|
+
return [];
|
|
122
|
+
// Split on a sentinel, never on the whitespace itself: the boundary
|
|
123
|
+
// match consumes the trailing space, so splitting on " " would split
|
|
124
|
+
// every word in the text rather than every sentence.
|
|
125
|
+
const MARK = "\u0000";
|
|
126
|
+
const rough = text
|
|
127
|
+
.replace(BOUNDARY, `$1$2${MARK}`)
|
|
128
|
+
.split(MARK)
|
|
129
|
+
.map((s) => s.trim())
|
|
130
|
+
.filter(Boolean);
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const piece of rough) {
|
|
133
|
+
// a sentence longer than the cap is broken at a clause, never mid-word
|
|
134
|
+
const parts = piece.length <= maxChars ? [piece] : splitLong(piece, maxChars);
|
|
135
|
+
for (const part of parts) {
|
|
136
|
+
const prev = out[out.length - 1];
|
|
137
|
+
if (prev && (prev.length < minChars || part.length < minChars)) {
|
|
138
|
+
out[out.length - 1] = `${prev} ${part}`;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
out.push(part);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function splitLong(text, maxChars) {
|
|
148
|
+
const out = [];
|
|
149
|
+
let rest = text;
|
|
150
|
+
while (rest.length > maxChars) {
|
|
151
|
+
const window = rest.slice(0, maxChars);
|
|
152
|
+
// prefer a clause break, then any space; never cut a word in half
|
|
153
|
+
const at = Math.max(window.lastIndexOf(", "), window.lastIndexOf("; "), window.lastIndexOf(" — "));
|
|
154
|
+
const cut = at > maxChars / 2 ? at + 1 : window.lastIndexOf(" ");
|
|
155
|
+
if (cut <= 0)
|
|
156
|
+
break;
|
|
157
|
+
out.push(rest.slice(0, cut).trim());
|
|
158
|
+
rest = rest.slice(cut).trim();
|
|
159
|
+
}
|
|
160
|
+
if (rest)
|
|
161
|
+
out.push(rest);
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
/** A tool-activity chip as a spoken progress line ("reading core.ts").
|
|
165
|
+
* Returns null for chips not worth interrupting the ear for. */
|
|
166
|
+
export function narrateTool(toolName) {
|
|
167
|
+
const name = toolName.replace(/^mcp__[^_]+__/, "").trim();
|
|
168
|
+
if (!name)
|
|
169
|
+
return null;
|
|
170
|
+
// the harness writes these as chips too; they are already spoken elsewhere
|
|
171
|
+
if (/^(auto-approved|error):/i.test(name))
|
|
172
|
+
return null;
|
|
173
|
+
const bare = name.toLowerCase();
|
|
174
|
+
const verbs = [
|
|
175
|
+
[/^(bash|shell|terminal|run_command|execute|computer_exec)$/, "running a command"],
|
|
176
|
+
[/^(read|read_file|view)$/, "reading a file"],
|
|
177
|
+
[/^(write|create_file)$/, "writing a file"],
|
|
178
|
+
[/^(edit|apply_patch|str_replace|multiedit)$/, "editing a file"],
|
|
179
|
+
[/^(grep|search|glob|find)$/, "searching"],
|
|
180
|
+
[/^(web_?search|websearch)$/, "searching the web"],
|
|
181
|
+
[/^(web_?fetch|fetch)$/, "reading a page"],
|
|
182
|
+
[/^screenshot$/, "looking at the screen"],
|
|
183
|
+
[/^(click|type_text|press_key|scroll|computer_batch)$/, "using the computer"],
|
|
184
|
+
[/^open_url$/, "opening a page"],
|
|
185
|
+
[/^list_bots$/, "checking who's around"],
|
|
186
|
+
[/^ask_bot$/, "asking a teammate"],
|
|
187
|
+
];
|
|
188
|
+
for (const [pattern, phrase] of verbs) {
|
|
189
|
+
if (pattern.test(bare))
|
|
190
|
+
return phrase;
|
|
191
|
+
}
|
|
192
|
+
// an unrecognized tool still deserves a beat, but not its raw argv
|
|
193
|
+
const short = shortenPaths(name).slice(0, 40);
|
|
194
|
+
return /^[\w .:/-]+$/.test(short) ? `running ${short}` : null;
|
|
195
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Built-in macOS speech synthesis — the zero-key voice provider.
|
|
2
|
+
//
|
|
3
|
+
// Calls and spoken replies work with no ElevenLabs account by using the
|
|
4
|
+
// voices already installed on the Mac (`/usr/bin/say`, the same engine the
|
|
5
|
+
// Spoken Content pane uses). Everything about driving `say` lives in this
|
|
6
|
+
// file: listing voices, and turning one utterance into WAV bytes.
|
|
7
|
+
//
|
|
8
|
+
// It runs on the HARNESS for the same reason ElevenLabs does — one place to
|
|
9
|
+
// spawn processes, one place that owns the utterance split — and the
|
|
10
|
+
// renderer keeps playing opaque audio bytes, so it never learns or cares
|
|
11
|
+
// which provider produced them.
|
|
12
|
+
//
|
|
13
|
+
// Platform-gated: `say` is a Darwin binary. Elsewhere the provider simply
|
|
14
|
+
// never appears as an option, and the ElevenLabs path is unchanged.
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { promisify } from "node:util";
|
|
20
|
+
const SAY = "/usr/bin/say";
|
|
21
|
+
const execFileAsync = promisify(execFile);
|
|
22
|
+
export function systemVoicesAvailable(platform = process.platform) {
|
|
23
|
+
return platform === "darwin";
|
|
24
|
+
}
|
|
25
|
+
const defaultRun = (file, args, timeout) => execFileAsync(file, args, { timeout, maxBuffer: 4 * 1024 * 1024 });
|
|
26
|
+
/** Parse `say -v ?` output. Lines look like:
|
|
27
|
+
* `Albert en_US # Hello! My name is Albert.`
|
|
28
|
+
* The header above the table is localized, so anything without the
|
|
29
|
+
* `name lang # sample` shape is ignored rather than parsed. */
|
|
30
|
+
export function parseVoiceList(stdout) {
|
|
31
|
+
const voices = [];
|
|
32
|
+
for (const line of stdout.split("\n")) {
|
|
33
|
+
const match = /^(.+?)\s{2,}(\S+)\s+#\s*(.+)$/.exec(line.trimEnd());
|
|
34
|
+
if (!match)
|
|
35
|
+
continue;
|
|
36
|
+
const [, name, locale, sample] = match;
|
|
37
|
+
const id = name.trim();
|
|
38
|
+
if (!id)
|
|
39
|
+
continue;
|
|
40
|
+
voices.push({ id, label: id, description: `${locale} — ${sample.trim()}` });
|
|
41
|
+
}
|
|
42
|
+
return voices;
|
|
43
|
+
}
|
|
44
|
+
export async function listSystemVoices(run = defaultRun) {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout } = await run(SAY, ["-v", "?"], 5_000);
|
|
47
|
+
return parseVoiceList(stdout);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Synthesize one utterance to 22 kHz mono WAV — small, and every browser
|
|
54
|
+
* plays it. `say` writes the container itself; no conversion step needed.
|
|
55
|
+
* The voice id is a system voice name; empty falls back to the system's
|
|
56
|
+
* own default, which is what a fresh Mac already sounds like. */
|
|
57
|
+
export async function synthesizeSystem(text, voiceId, run = defaultRun) {
|
|
58
|
+
const trimmed = text.trim();
|
|
59
|
+
if (!trimmed)
|
|
60
|
+
return { bytes: new Uint8Array(), mime: "audio/wav" };
|
|
61
|
+
const dir = await mkdtemp(join(tmpdir(), "openmausbot-say-"));
|
|
62
|
+
const out = join(dir, "utterance.wav");
|
|
63
|
+
try {
|
|
64
|
+
const args = ["-o", out, "--data-format=LEI16@22050"];
|
|
65
|
+
if (voiceId?.trim())
|
|
66
|
+
args.push("-v", voiceId.trim());
|
|
67
|
+
args.push(trimmed);
|
|
68
|
+
await run(SAY, args, 30_000);
|
|
69
|
+
return { bytes: new Uint8Array(await readFile(out)), mime: "audio/wav" };
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await rm(dir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Rung three of the hosting ladder: `openmausbot serve --tunnel` gives a
|
|
2
|
+
// server a public HTTPS address (https://c-<id>.openmausbot.com) with no
|
|
3
|
+
// domain, no proxy and no open port, through the same control plane and
|
|
4
|
+
// Cloudflare tunnel the desktop app already uses. Headless, so:
|
|
5
|
+
//
|
|
6
|
+
// - the account and connector credentials live in a 0600 JSON file under
|
|
7
|
+
// the data dir instead of the OS keychain (a server has none; a key kept
|
|
8
|
+
// next to the ciphertext would be theatre), and its mode is re-tightened
|
|
9
|
+
// on every open;
|
|
10
|
+
// - cloudflared is fetched once into the data dir, by the same pinned
|
|
11
|
+
// version + digest script the release build uses, when nothing usable is
|
|
12
|
+
// on the machine;
|
|
13
|
+
// - the tunnel's loopback gateway forwards to an IPC listener the harness
|
|
14
|
+
// opens for it (OMB_TUNNEL_SOCKET), so to request-auth every public
|
|
15
|
+
// request is "through a proxy" by construction and needs a paired
|
|
16
|
+
// session — the loopback bind and its owner trust are untouched.
|
|
17
|
+
//
|
|
18
|
+
// Everything network- and process-shaped is the desktop's own code
|
|
19
|
+
// (electron/control-plane-client.mjs, companion-account-service.mjs,
|
|
20
|
+
// managed-companion-tunnel.mjs and the connector guardian): one
|
|
21
|
+
// implementation, so a fix lands in both.
|
|
22
|
+
import { spawn } from "node:child_process";
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
25
|
+
import { hostname } from "node:os";
|
|
26
|
+
import { dirname, join, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { COMPANION_ACCOUNT_EMAIL_FIELD, COMPANION_INSTALLATION_ID_FIELD, createCompanionAccountService, resolveCompanionControlPlaneURL, } from "../electron/companion-account-service.mjs";
|
|
29
|
+
import { cleanupCompanionOriginEndpoint, createCompanionOriginEndpoint, MANAGED_COMPANION_ORIGIN_PORT, } from "../electron/companion-origin-gateway.mjs";
|
|
30
|
+
import { createControlPlaneClient } from "../electron/control-plane-client.mjs";
|
|
31
|
+
import { createManagedCompanionTunnel, managedCompanionTunnelAccess, resolveCloudflaredBinary, } from "../electron/managed-companion-tunnel.mjs";
|
|
32
|
+
import { createSecureCredentialState } from "../electron/secure-credential-state.mjs";
|
|
33
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
34
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
export const TUNNEL_CREDENTIALS_FILE = "tunnel-account.json";
|
|
36
|
+
export const TUNNEL_RUNTIME_DIR = "tunnel-runtime";
|
|
37
|
+
function plainDocument(value) {
|
|
38
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
39
|
+
return null;
|
|
40
|
+
const document = {};
|
|
41
|
+
for (const [key, entry] of Object.entries(value))
|
|
42
|
+
document[key] = entry;
|
|
43
|
+
return document;
|
|
44
|
+
}
|
|
45
|
+
export function openTunnelCredentials(dataDir) {
|
|
46
|
+
const file = join(dataDir, TUNNEL_CREDENTIALS_FILE);
|
|
47
|
+
let initial = {};
|
|
48
|
+
let status = "empty";
|
|
49
|
+
if (existsSync(file)) {
|
|
50
|
+
try {
|
|
51
|
+
// A file someone loosened (or an old umask) is tightened, not trusted as is.
|
|
52
|
+
if (process.platform !== "win32" && (statSync(file).mode & 0o077) !== 0)
|
|
53
|
+
chmodSync(file, 0o600);
|
|
54
|
+
const parsed = plainDocument(JSON.parse(readFileSync(file, "utf8")));
|
|
55
|
+
if (!parsed)
|
|
56
|
+
throw new Error("not a JSON object");
|
|
57
|
+
initial = parsed;
|
|
58
|
+
status = "ok";
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
status = "unavailable";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const persist = (next) => {
|
|
65
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
66
|
+
writeFileAtomic(file, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
67
|
+
};
|
|
68
|
+
const state = createSecureCredentialState(initial, persist, { writable: status !== "unavailable" });
|
|
69
|
+
return {
|
|
70
|
+
file,
|
|
71
|
+
status,
|
|
72
|
+
read: () => state.read(),
|
|
73
|
+
update: (derive, afterPersist) => state.update(derive, afterPersist),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function stringField(document, field) {
|
|
77
|
+
const value = document[field];
|
|
78
|
+
return typeof value === "string" && value ? value : null;
|
|
79
|
+
}
|
|
80
|
+
export function describeTunnelAccount(document) {
|
|
81
|
+
return {
|
|
82
|
+
email: stringField(document, COMPANION_ACCOUNT_EMAIL_FIELD),
|
|
83
|
+
address: managedCompanionTunnelAccess(document)?.endpoint ?? null,
|
|
84
|
+
installationId: stringField(document, COMPANION_INSTALLATION_ID_FIELD),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function tunnelAccess(document) {
|
|
88
|
+
return managedCompanionTunnelAccess(document);
|
|
89
|
+
}
|
|
90
|
+
/** The control plane's platform vocabulary. */
|
|
91
|
+
export function platformName(platform = process.platform) {
|
|
92
|
+
return platform === "darwin" ? "darwin" : platform === "win32" ? "windows" : "linux";
|
|
93
|
+
}
|
|
94
|
+
/** The desktop honours OMB_CONTROL_PLANE_URL only in development builds; a
|
|
95
|
+
* server's operator owns its environment, so it is honoured here always and
|
|
96
|
+
* the caller says so in its output. */
|
|
97
|
+
export function createTunnelAccount(options) {
|
|
98
|
+
const env = options.env ?? process.env;
|
|
99
|
+
const credentials = openTunnelCredentials(options.dataDir);
|
|
100
|
+
const controlPlane = resolveCompanionControlPlaneURL({ isPackaged: true, environment: env });
|
|
101
|
+
const client = controlPlane ? createControlPlaneClient({ baseURL: controlPlane, fetchImpl: options.fetchImpl }) : null;
|
|
102
|
+
const service = createCompanionAccountService({
|
|
103
|
+
client,
|
|
104
|
+
readCredentials: () => credentials.read(),
|
|
105
|
+
updateCredentials: (derive, afterPersist) => credentials.update(derive, afterPersist),
|
|
106
|
+
identity: { name: options.machineName ?? hostname(), platform: platformName(), appVersion: options.version },
|
|
107
|
+
newClientInstanceId: () => randomUUID(),
|
|
108
|
+
});
|
|
109
|
+
return { service, credentials, controlPlane };
|
|
110
|
+
}
|
|
111
|
+
// ── cloudflared and the guardian: where they are, or how to get them ──────
|
|
112
|
+
/** OMB_CLOUDFLARED_PATH, then the copy this command downloaded into the data
|
|
113
|
+
* dir, then PATH. */
|
|
114
|
+
export function cloudflaredPath(dataDir, env = process.env) {
|
|
115
|
+
return resolveCloudflaredBinary({ isPackaged: false, appPath: dataDir, environment: env });
|
|
116
|
+
}
|
|
117
|
+
/** The pinned-download script: bundled beside the server in a package or
|
|
118
|
+
* image, the source in a checkout. */
|
|
119
|
+
export function prepareEntry(here = HERE) {
|
|
120
|
+
const bundled = join(here, "prepare-cloudflared.js");
|
|
121
|
+
return existsSync(bundled) ? bundled : resolve(here, "..", "scripts", "prepare-cloudflared.mjs");
|
|
122
|
+
}
|
|
123
|
+
/** The connector guardian (the process that owns the gateway and cloudflared
|
|
124
|
+
* and tears both down if this process dies), same rule. */
|
|
125
|
+
export function guardianEntry(here = HERE) {
|
|
126
|
+
const bundled = join(here, "tunnel-guardian.js");
|
|
127
|
+
if (existsSync(bundled))
|
|
128
|
+
return bundled;
|
|
129
|
+
const source = resolve(here, "..", "electron", "managed-companion-guardian.mjs");
|
|
130
|
+
return existsSync(source) ? source : null;
|
|
131
|
+
}
|
|
132
|
+
export async function ensureCloudflared(options) {
|
|
133
|
+
const env = options.env ?? process.env;
|
|
134
|
+
const found = cloudflaredPath(options.dataDir, env);
|
|
135
|
+
if (found)
|
|
136
|
+
return found;
|
|
137
|
+
options.log(`downloading cloudflared once (pinned version and digest, about 40 MB) into ${options.dataDir}`);
|
|
138
|
+
await new Promise((done, fail) => {
|
|
139
|
+
const child = spawn(process.execPath, [prepareEntry(options.here), "--current", "--root", options.dataDir], {
|
|
140
|
+
env,
|
|
141
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
142
|
+
});
|
|
143
|
+
child.on("error", fail);
|
|
144
|
+
child.on("exit", (code) => (code === 0 ? done() : fail(new Error(`the cloudflared download failed (exit ${code ?? "signal"})`))));
|
|
145
|
+
});
|
|
146
|
+
const staged = cloudflaredPath(options.dataDir, env);
|
|
147
|
+
if (!staged)
|
|
148
|
+
throw new Error("cloudflared was downloaded but is not where it should be; remove the data dir's dist-native folder and try again");
|
|
149
|
+
return staged;
|
|
150
|
+
}
|
|
151
|
+
// ── the origin: an IPC path the harness listens on for the gateway ────────
|
|
152
|
+
export function createTunnelOrigin() {
|
|
153
|
+
return createCompanionOriginEndpoint({ processId: process.pid });
|
|
154
|
+
}
|
|
155
|
+
export function cleanupTunnelOrigin(origin) {
|
|
156
|
+
cleanupCompanionOriginEndpoint(origin);
|
|
157
|
+
}
|
|
158
|
+
/** The gateway listens on 127.0.0.1:<originPort> (the control plane points
|
|
159
|
+
* the tunnel at 8812; OMB_TUNNEL_ORIGIN_PORT exists for tests) and forwards
|
|
160
|
+
* to the harness's IPC socket. Verification polls the public address until
|
|
161
|
+
* it answers as this app, then retries with backoff forever if it never does. */
|
|
162
|
+
export function startTunnel(options) {
|
|
163
|
+
const env = options.env ?? process.env;
|
|
164
|
+
const originPort = Number(env.OMB_TUNNEL_ORIGIN_PORT || MANAGED_COMPANION_ORIGIN_PORT);
|
|
165
|
+
let current = { status: "stopped", ready: false };
|
|
166
|
+
const tunnel = createManagedCompanionTunnel({
|
|
167
|
+
binaryPath: options.binaryPath,
|
|
168
|
+
guardianEntry: options.guardian,
|
|
169
|
+
runtimeRoot: join(options.dataDir, TUNNEL_RUNTIME_DIR),
|
|
170
|
+
originPort,
|
|
171
|
+
environment: env,
|
|
172
|
+
fetchImpl: options.fetchImpl,
|
|
173
|
+
onChange: (state) => {
|
|
174
|
+
current = state;
|
|
175
|
+
options.onState?.(state);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const started = tunnel.start({ endpoint: options.access.endpoint, token: options.access.token, originTarget: options.originTarget });
|
|
179
|
+
return {
|
|
180
|
+
address: options.access.endpoint,
|
|
181
|
+
state: () => current,
|
|
182
|
+
started,
|
|
183
|
+
stop: async () => {
|
|
184
|
+
await tunnel.stop();
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** One line per state change, for a terminal or a journal. */
|
|
189
|
+
export function describeTunnelState(state, address) {
|
|
190
|
+
switch (state.status) {
|
|
191
|
+
case "starting":
|
|
192
|
+
return "tunnel: connecting…";
|
|
193
|
+
case "ready":
|
|
194
|
+
return `tunnel: live at ${address}`;
|
|
195
|
+
case "retrying":
|
|
196
|
+
return `tunnel: ${state.error ?? "not verified yet"} retrying in ${Math.max(1, Math.round((state.retryInMs ?? 0) / 1000))}s`;
|
|
197
|
+
case "stopped":
|
|
198
|
+
return "tunnel: stopped";
|
|
199
|
+
default:
|
|
200
|
+
return `tunnel: ${state.error ?? state.status}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Does this engine need the thread replayed to it? True when a DIFFERENT
|
|
2
|
+
* instance ran the last turn here — a cursor of our own is not enough,
|
|
3
|
+
* because it only proves we once had a session covering some prefix of the
|
|
4
|
+
* thread; every turn another engine took since is missing from it. Tasks
|
|
5
|
+
* from before `lastInstanceId` existed fall back to the cursor map: a lone
|
|
6
|
+
* cursor that is ours means a single-engine thread we can keep resuming;
|
|
7
|
+
* anything else is ambiguous, and replaying is the safe side of ambiguous.
|
|
8
|
+
* Gated on a prior USER turn: a new bot's thread is seeded with its own
|
|
9
|
+
* greeting, and that alone is nothing to join. */
|
|
10
|
+
export function engineIsFresh(input) {
|
|
11
|
+
const { instanceId, lastInstanceId, resumeCursors, transcript } = input;
|
|
12
|
+
if (!transcript.some((m) => m.role === "user"))
|
|
13
|
+
return false;
|
|
14
|
+
if (lastInstanceId !== undefined)
|
|
15
|
+
return lastInstanceId !== instanceId || resumeCursors[instanceId] === undefined;
|
|
16
|
+
const cursorIds = Object.keys(resumeCursors);
|
|
17
|
+
return !(cursorIds.length === 1 && cursorIds[0] === instanceId);
|
|
18
|
+
}
|
|
19
|
+
const REWOUND_PREAMBLE = "[The user rewound this conversation (edited a message or switched to another version). Everything before this point was replaced by the following history:]";
|
|
20
|
+
const FRESH_PREAMBLE = "[You are joining this conversation mid-thread (the user switched this bot over to you). The conversation so far:]";
|
|
21
|
+
const EXTERNAL_UPDATE_PREAMBLE = "[This conversation received an update outside your provider session. The complete current history follows so you can use that update in your next response:]";
|
|
22
|
+
export function buildTurnContext(input) {
|
|
23
|
+
const { text, transcript, rewound, fresh, externallyUpdated, replaysNatively } = input;
|
|
24
|
+
const resume = !rewound && !fresh && !externallyUpdated;
|
|
25
|
+
const replay = !resume && !replaysNatively && transcript.length > 0;
|
|
26
|
+
if (!replay)
|
|
27
|
+
return { turnText: text, resume };
|
|
28
|
+
return {
|
|
29
|
+
turnText: [
|
|
30
|
+
rewound ? REWOUND_PREAMBLE : externallyUpdated ? EXTERNAL_UPDATE_PREAMBLE : FRESH_PREAMBLE,
|
|
31
|
+
"",
|
|
32
|
+
...transcript.map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.text}`),
|
|
33
|
+
"",
|
|
34
|
+
"[Now reply to the user's latest message:]",
|
|
35
|
+
"",
|
|
36
|
+
text,
|
|
37
|
+
].join("\n"),
|
|
38
|
+
resume,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/** Close the Stop-vs-provider-handshake race shared by direct and room turns.
|
|
2
|
+
* An adapter may not publish its active process until sendTurn resolves, so
|
|
3
|
+
* an interrupt during that await can be an honest no-op. Re-check once setup
|
|
4
|
+
* completes and issue a second interrupt only when cancellation won. */
|
|
5
|
+
export async function guardTurnDispatch(started, cancelled, stopAfterSetup) {
|
|
6
|
+
const value = await started;
|
|
7
|
+
if (!cancelled())
|
|
8
|
+
return { value, cancelled: false };
|
|
9
|
+
await stopAfterSetup();
|
|
10
|
+
return { value, cancelled: true };
|
|
11
|
+
}
|
|
12
|
+
/** Bounded tombstones for provider turns cancelled during asynchronous
|
|
13
|
+
* startup. Their late completion/session events must not settle a newer turn
|
|
14
|
+
* that reused the same conversation thread. */
|
|
15
|
+
export class RetiredTurnRegistry {
|
|
16
|
+
#turnIds = new Set();
|
|
17
|
+
#limit;
|
|
18
|
+
constructor(limit = 4_096) {
|
|
19
|
+
this.#limit = limit;
|
|
20
|
+
}
|
|
21
|
+
retire(turnId) {
|
|
22
|
+
this.#turnIds.add(turnId);
|
|
23
|
+
while (this.#turnIds.size > this.#limit) {
|
|
24
|
+
const oldest = this.#turnIds.values().next().value;
|
|
25
|
+
if (oldest === undefined)
|
|
26
|
+
break;
|
|
27
|
+
this.#turnIds.delete(oldest);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
has(turnId) {
|
|
31
|
+
return turnId !== undefined && this.#turnIds.has(turnId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Correlate an internal capability generation with its provider turn without
|
|
35
|
+
* leaving a bearer alive when a very fast provider completes before
|
|
36
|
+
* sendTurn() returns its id. Completed ids are kept in a bounded tombstone
|
|
37
|
+
* registry, so a late bind fails closed instead of publishing stale ownership. */
|
|
38
|
+
export class ProviderTurnGenerationRegistry {
|
|
39
|
+
#owners = new Map();
|
|
40
|
+
#completed;
|
|
41
|
+
constructor(limit = 4_096) {
|
|
42
|
+
this.#completed = new RetiredTurnRegistry(limit);
|
|
43
|
+
}
|
|
44
|
+
bind(threadId, generation, turnId) {
|
|
45
|
+
if (this.#completed.has(turnId))
|
|
46
|
+
return false;
|
|
47
|
+
this.#owners.set(turnId, { threadId, generation });
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
complete(threadId, turnId) {
|
|
51
|
+
this.#completed.retire(turnId);
|
|
52
|
+
const owner = this.#owners.get(turnId);
|
|
53
|
+
if (!owner || owner.threadId !== threadId)
|
|
54
|
+
return null;
|
|
55
|
+
this.#owners.delete(turnId);
|
|
56
|
+
return owner;
|
|
57
|
+
}
|
|
58
|
+
deleteGeneration(threadId, generation) {
|
|
59
|
+
for (const [turnId, owner] of this.#owners) {
|
|
60
|
+
if (owner.threadId === threadId && owner.generation === generation) {
|
|
61
|
+
this.#owners.delete(turnId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
clear() {
|
|
66
|
+
this.#owners.clear();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Thread gate for the narrow interval after Stop wins but before an async
|
|
70
|
+
* adapter returns the provider turn id that can be retired. Multiple queued
|
|
71
|
+
* room operations may share a thread, so ownership is reference-counted.
|
|
72
|
+
*
|
|
73
|
+
* A broken adapter is allowed to leave its sendTurn promise pending forever.
|
|
74
|
+
* The gate therefore expires each owner independently: by then any turn id
|
|
75
|
+
* observed during the vulnerable handshake has been moved to the longer-lived
|
|
76
|
+
* RetiredTurnRegistry, while a replacement turn's new id can flow normally. */
|
|
77
|
+
export class PendingTurnCancellations {
|
|
78
|
+
#ownersByThread = new Map();
|
|
79
|
+
#clearWaitersByThread = new Map();
|
|
80
|
+
#ttlMs;
|
|
81
|
+
constructor(ttlMs = 30_000) {
|
|
82
|
+
this.#ttlMs = Math.max(1, Math.floor(ttlMs));
|
|
83
|
+
}
|
|
84
|
+
mark(threadId, ownerId) {
|
|
85
|
+
const owners = this.#ownersByThread.get(threadId) ?? new Map();
|
|
86
|
+
const previous = owners.get(ownerId);
|
|
87
|
+
if (previous)
|
|
88
|
+
clearTimeout(previous);
|
|
89
|
+
const expiry = setTimeout(() => {
|
|
90
|
+
// A clear + re-mark may have installed a newer timer for the same
|
|
91
|
+
// owner. The stale callback must not clear that renewed quarantine.
|
|
92
|
+
if (this.#ownersByThread.get(threadId)?.get(ownerId) !== expiry)
|
|
93
|
+
return;
|
|
94
|
+
this.clear(threadId, ownerId);
|
|
95
|
+
}, this.#ttlMs);
|
|
96
|
+
expiry.unref?.();
|
|
97
|
+
owners.set(ownerId, expiry);
|
|
98
|
+
this.#ownersByThread.set(threadId, owners);
|
|
99
|
+
}
|
|
100
|
+
clear(threadId, ownerId) {
|
|
101
|
+
const owners = this.#ownersByThread.get(threadId);
|
|
102
|
+
const expiry = owners?.get(ownerId);
|
|
103
|
+
if (expiry)
|
|
104
|
+
clearTimeout(expiry);
|
|
105
|
+
owners?.delete(ownerId);
|
|
106
|
+
if (owners?.size !== 0)
|
|
107
|
+
return;
|
|
108
|
+
this.#ownersByThread.delete(threadId);
|
|
109
|
+
const waiters = this.#clearWaitersByThread.get(threadId);
|
|
110
|
+
this.#clearWaitersByThread.delete(threadId);
|
|
111
|
+
for (const resolve of waiters ?? [])
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
/** Do not overlap a replacement dispatch with the ambiguous pre-id window.
|
|
115
|
+
* Re-check after every wake: a cancellation can be renewed in the same
|
|
116
|
+
* microtask turn that clears an older owner. Each owner has its own bounded
|
|
117
|
+
* expiry, so a broken adapter cannot strand this await forever. */
|
|
118
|
+
async waitForClear(threadId) {
|
|
119
|
+
while (this.has(threadId)) {
|
|
120
|
+
await new Promise((resolve) => {
|
|
121
|
+
const waiters = this.#clearWaitersByThread.get(threadId) ?? new Set();
|
|
122
|
+
waiters.add(resolve);
|
|
123
|
+
this.#clearWaitersByThread.set(threadId, waiters);
|
|
124
|
+
// clear() cannot interleave with this synchronous block, but keeping
|
|
125
|
+
// the second check makes the registration safe if the implementation
|
|
126
|
+
// later gains an externally supplied scheduler.
|
|
127
|
+
if (!this.has(threadId)) {
|
|
128
|
+
waiters.delete(resolve);
|
|
129
|
+
if (waiters.size === 0)
|
|
130
|
+
this.#clearWaitersByThread.delete(threadId);
|
|
131
|
+
resolve();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
has(threadId) {
|
|
137
|
+
return this.#ownersByThread.has(threadId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Apply the two-stage cancellation quarantine. While the unresolved
|
|
141
|
+
* handshake owns the thread, every event is ignored and any stable provider
|
|
142
|
+
* turn id it reveals is retired. After the bounded gate expires, that old id
|
|
143
|
+
* remains ignored but an unrelated replacement id is admitted. */
|
|
144
|
+
export function isTurnEventQuarantined(pending, retired, event) {
|
|
145
|
+
if (retired.has(event.turnId))
|
|
146
|
+
return true;
|
|
147
|
+
if (!pending.has(event.threadId))
|
|
148
|
+
return false;
|
|
149
|
+
if (event.turnId)
|
|
150
|
+
retired.retire(event.turnId);
|
|
151
|
+
return true;
|
|
152
|
+
}
|