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.
Files changed (499) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +14 -0
  3. package/cli.js +2 -0
  4. package/dist/app-icon.svg +29 -0
  5. package/dist/assets/abap-BdImnpbu.js +1 -0
  6. package/dist/assets/actionscript-3-B3316cI-.js +1 -0
  7. package/dist/assets/ada-bCR0ucgS.js +1 -0
  8. package/dist/assets/ahk-CsyLZFj1.js +1 -0
  9. package/dist/assets/ahk2-8Zs4aa1G.js +1 -0
  10. package/dist/assets/andromeeda-C4gqWexZ.js +1 -0
  11. package/dist/assets/angular-html-DA-rfuFy.js +1 -0
  12. package/dist/assets/angular-ts-BrjP3tb8.js +1 -0
  13. package/dist/assets/apache-Pmp26Uib.js +1 -0
  14. package/dist/assets/apex-DhZFqWV2.js +1 -0
  15. package/dist/assets/apl-CORt7UWP.js +1 -0
  16. package/dist/assets/applescript-Co6uUVPk.js +1 -0
  17. package/dist/assets/ara-BRHolxvo.js +1 -0
  18. package/dist/assets/asciidoc-CSVQ5wI8.js +1 -0
  19. package/dist/assets/asm-D_Q5rh1f.js +1 -0
  20. package/dist/assets/astro-HNnZUWAn.js +1 -0
  21. package/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  22. package/dist/assets/awk-DMzUqQB5.js +1 -0
  23. package/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
  24. package/dist/assets/ayu-light-BA47KaF1.js +1 -0
  25. package/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
  26. package/dist/assets/ballerina-BFfxhgS-.js +1 -0
  27. package/dist/assets/bat-CickPsom.js +1 -0
  28. package/dist/assets/beancount-k_qm7-4y.js +1 -0
  29. package/dist/assets/berry-uYugtg8r.js +1 -0
  30. package/dist/assets/bibtex-CHM0blh-.js +1 -0
  31. package/dist/assets/bicep-Bmn6On1c.js +1 -0
  32. package/dist/assets/bird2-Bx8U0n9b.js +1 -0
  33. package/dist/assets/blade-2xfisSek.js +1 -0
  34. package/dist/assets/bsl-DlhNcFeZ.js +1 -0
  35. package/dist/assets/c-BIGW1oBm.js +1 -0
  36. package/dist/assets/c3-Dp5svz6Z.js +1 -0
  37. package/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  38. package/dist/assets/cairo-KRGpt6FW.js +1 -0
  39. package/dist/assets/catppuccin-frappe-CZL1YF0i.js +1 -0
  40. package/dist/assets/catppuccin-latte-DH-8KZSZ.js +1 -0
  41. package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +1 -0
  42. package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +1 -0
  43. package/dist/assets/chapel-DTp_pixX.js +1 -0
  44. package/dist/assets/clarity-Dn5IMItf.js +1 -0
  45. package/dist/assets/clojure-P80f7IUj.js +1 -0
  46. package/dist/assets/cmake-D1j8_8rp.js +1 -0
  47. package/dist/assets/cobol-nBiQ_Alo.js +1 -0
  48. package/dist/assets/codeowners-Bp6g37R7.js +1 -0
  49. package/dist/assets/codeql-DsOJ9woJ.js +1 -0
  50. package/dist/assets/coffee-Ch7k5sss.js +1 -0
  51. package/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  52. package/dist/assets/coq-C7JzOVbR.js +1 -0
  53. package/dist/assets/cpp-BMRokrvK.js +1 -0
  54. package/dist/assets/crystal-DGywbUpC.js +1 -0
  55. package/dist/assets/csharp-DSvCPggb.js +1 -0
  56. package/dist/assets/css-CLj8gQPS.js +1 -0
  57. package/dist/assets/csv-fuZLfV_i.js +1 -0
  58. package/dist/assets/cue-D82EKSYY.js +1 -0
  59. package/dist/assets/cypher-COkxafJQ.js +1 -0
  60. package/dist/assets/d-85-TOEBH.js +1 -0
  61. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  62. package/dist/assets/dart-bE4Kk8sk.js +1 -0
  63. package/dist/assets/dax-CEL-wOlO.js +1 -0
  64. package/dist/assets/desktop-BmXAJ9_W.js +1 -0
  65. package/dist/assets/diff-D97Zzqfu.js +1 -0
  66. package/dist/assets/docker-BcOcwvcX.js +1 -0
  67. package/dist/assets/dotenv-Da5cRb03.js +1 -0
  68. package/dist/assets/dracula-BzJJZx-M.js +1 -0
  69. package/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  70. package/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  71. package/dist/assets/edge-FbVlp4U3.js +1 -0
  72. package/dist/assets/elixir-CkH2-t6x.js +1 -0
  73. package/dist/assets/elm-DbKCFpqz.js +1 -0
  74. package/dist/assets/emacs-lisp-C_m_b--Z.js +1 -0
  75. package/dist/assets/erb-DXfck5VN.js +1 -0
  76. package/dist/assets/erlang-DsQrWhSR.js +1 -0
  77. package/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  78. package/dist/assets/everforest-light-C8M2exoo.js +1 -0
  79. package/dist/assets/fennel-BYunw83y.js +1 -0
  80. package/dist/assets/fish-BvzEVeQv.js +1 -0
  81. package/dist/assets/fluent-C4IJs8-o.js +1 -0
  82. package/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  83. package/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
  84. package/dist/assets/fsharp-CXgrBDvD.js +1 -0
  85. package/dist/assets/gdresource-TyuKm33G.js +1 -0
  86. package/dist/assets/gdscript-DqcFQ5yU.js +1 -0
  87. package/dist/assets/gdshader-DkwncUOv.js +1 -0
  88. package/dist/assets/genie-D0YGMca9.js +1 -0
  89. package/dist/assets/gherkin-DyxjwDmM.js +1 -0
  90. package/dist/assets/git-commit-F4YmCXRG.js +1 -0
  91. package/dist/assets/git-rebase-r7XF79zn.js +1 -0
  92. package/dist/assets/github-dark-DHJKELXO.js +1 -0
  93. package/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  94. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  95. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  96. package/dist/assets/github-light-DAi9KRSo.js +1 -0
  97. package/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  98. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  99. package/dist/assets/gleam-BspZqrRM.js +1 -0
  100. package/dist/assets/glimmer-js-ByusRIyA.js +1 -0
  101. package/dist/assets/glimmer-ts-BfAWNZQY.js +1 -0
  102. package/dist/assets/glsl-DplSGwfg.js +1 -0
  103. package/dist/assets/gn-n2N0HUVH.js +1 -0
  104. package/dist/assets/gnuplot-DdkO51Og.js +1 -0
  105. package/dist/assets/go-C27-OAKa.js +1 -0
  106. package/dist/assets/graphql-ChdNCCLP.js +1 -0
  107. package/dist/assets/groovy-gcz8RCvz.js +1 -0
  108. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  109. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  110. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  111. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  112. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  113. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  114. package/dist/assets/hack-BWmVpMyf.js +1 -0
  115. package/dist/assets/haml-D5jkg6IW.js +1 -0
  116. package/dist/assets/handlebars-BpdQsYii.js +1 -0
  117. package/dist/assets/haskell-Df6bDoY_.js +1 -0
  118. package/dist/assets/haxe-CfZj7gIn.js +1 -0
  119. package/dist/assets/hcl-BWvSN4gD.js +1 -0
  120. package/dist/assets/hjson-D5-asLiD.js +1 -0
  121. package/dist/assets/hlsl-D3lLCCz7.js +1 -0
  122. package/dist/assets/horizon-BUw7H-hv.js +1 -0
  123. package/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
  124. package/dist/assets/houston-DnULxvSX.js +1 -0
  125. package/dist/assets/html-derivative-DlHx6ybY.js +1 -0
  126. package/dist/assets/html-pp8916En.js +1 -0
  127. package/dist/assets/http-jrhK8wxY.js +1 -0
  128. package/dist/assets/hurl-irOxFIW8.js +1 -0
  129. package/dist/assets/hxml-2-FPmUDs.js +1 -0
  130. package/dist/assets/hy-DFXneXwc.js +1 -0
  131. package/dist/assets/imba-DGztddWO.js +1 -0
  132. package/dist/assets/index-B9rLIY81.js +263 -0
  133. package/dist/assets/index-CJBrtDbC.css +1 -0
  134. package/dist/assets/index-D-u0LYsV.js +153 -0
  135. package/dist/assets/ini-BEwlwnbL.js +1 -0
  136. package/dist/assets/java-CylS5w8V.js +1 -0
  137. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  138. package/dist/assets/jinja-f2NsQr07.js +1 -0
  139. package/dist/assets/jison-wvAkD_A8.js +1 -0
  140. package/dist/assets/json-Cp-IABpG.js +1 -0
  141. package/dist/assets/json5-C9tS-k6U.js +1 -0
  142. package/dist/assets/jsonc-Des-eS-w.js +1 -0
  143. package/dist/assets/jsonl-DcaNXYhu.js +1 -0
  144. package/dist/assets/jsonnet-DFQXde-d.js +1 -0
  145. package/dist/assets/jssm-C2t-YnRu.js +1 -0
  146. package/dist/assets/jsx-g9-lgVsj.js +1 -0
  147. package/dist/assets/julia-5Bft2YPA.js +1 -0
  148. package/dist/assets/just-Cwhn7H3k.js +1 -0
  149. package/dist/assets/kanagawa-dragon-BuwD2xS4.js +1 -0
  150. package/dist/assets/kanagawa-lotus-C3DzagqV.js +1 -0
  151. package/dist/assets/kanagawa-wave-DFGoQZhC.js +1 -0
  152. package/dist/assets/kdl-DV7GczEv.js +1 -0
  153. package/dist/assets/kotlin-BdnUsdx6.js +1 -0
  154. package/dist/assets/kusto-wEQ09or8.js +1 -0
  155. package/dist/assets/laserwave-DUszq2jm.js +1 -0
  156. package/dist/assets/latex-D5pSuvFb.js +1 -0
  157. package/dist/assets/lean-BZvkOJ9d.js +1 -0
  158. package/dist/assets/less-B1dDrJ26.js +1 -0
  159. package/dist/assets/light-plus-B7mTdjB0.js +1 -0
  160. package/dist/assets/liquid-C0sCDyMI.js +1 -0
  161. package/dist/assets/llvm-BZoOZj88.js +1 -0
  162. package/dist/assets/log-2UxHyX5q.js +1 -0
  163. package/dist/assets/logo-BtOb2qkB.js +1 -0
  164. package/dist/assets/lua-BaeVxFsk.js +1 -0
  165. package/dist/assets/luau-BnpPk5vE.js +1 -0
  166. package/dist/assets/make-CHLpvVh8.js +1 -0
  167. package/dist/assets/markdown-Cvjx9yec.js +1 -0
  168. package/dist/assets/marko-DjSrsDqO.js +1 -0
  169. package/dist/assets/material-theme-D5KoaKCx.js +1 -0
  170. package/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  171. package/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  172. package/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  173. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  174. package/dist/assets/matlab-D7o27uSR.js +1 -0
  175. package/dist/assets/mdc-D1_yUvq7.js +1 -0
  176. package/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  177. package/dist/assets/mermaid-CQcHuHx7.js +1 -0
  178. package/dist/assets/min-dark-CafNBF8u.js +1 -0
  179. package/dist/assets/min-light-CTRr51gU.js +1 -0
  180. package/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  181. package/dist/assets/mojo-DJz3ZmWd.js +1 -0
  182. package/dist/assets/monokai-BRVnQi9A.js +1 -0
  183. package/dist/assets/moonbit-CHtswR0a.js +1 -0
  184. package/dist/assets/move-el3G9tDJ.js +1 -0
  185. package/dist/assets/narrat-DRg8JJMk.js +1 -0
  186. package/dist/assets/nextflow-C-mBbutL.js +1 -0
  187. package/dist/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  188. package/dist/assets/nginx-BpAMiNFr.js +1 -0
  189. package/dist/assets/night-owl-C39BiMTA.js +1 -0
  190. package/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
  191. package/dist/assets/nim-BIad80T-.js +1 -0
  192. package/dist/assets/nix-CwoSXNpI.js +1 -0
  193. package/dist/assets/nord-Ddv68eIx.js +1 -0
  194. package/dist/assets/nsis-BlV79W_Q.js +1 -0
  195. package/dist/assets/nushell-D3jzshHO.js +1 -0
  196. package/dist/assets/objective-c-DXmwc3jG.js +1 -0
  197. package/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  198. package/dist/assets/ocaml-C0hk2d4L.js +1 -0
  199. package/dist/assets/odin-BBf5iR-q.js +1 -0
  200. package/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  201. package/dist/assets/one-light-C3Wv6jpd.js +1 -0
  202. package/dist/assets/openscad-C4EeE6gA.js +1 -0
  203. package/dist/assets/org-DM6o9KBp.js +1 -0
  204. package/dist/assets/pascal-D93ZcfNL.js +1 -0
  205. package/dist/assets/perl-B9cMNwum.js +1 -0
  206. package/dist/assets/php-Csjmro_R.js +1 -0
  207. package/dist/assets/pkl-u5AG7uiY.js +1 -0
  208. package/dist/assets/plastic-3e1v2bzS.js +1 -0
  209. package/dist/assets/plsql-ChMvpjG-.js +1 -0
  210. package/dist/assets/po-BTJTHyun.js +1 -0
  211. package/dist/assets/poimandres-CS3Unz2-.js +1 -0
  212. package/dist/assets/polar-C0HS_06l.js +1 -0
  213. package/dist/assets/postcss-CXtECtnM.js +1 -0
  214. package/dist/assets/powerquery-CEu0bR-o.js +1 -0
  215. package/dist/assets/powershell-BmBUJMz7.js +1 -0
  216. package/dist/assets/prisma-Vru482bI.js +1 -0
  217. package/dist/assets/prolog-CbFg5uaA.js +1 -0
  218. package/dist/assets/proto-C7zT0LnQ.js +1 -0
  219. package/dist/assets/pug-DKIMFp6K.js +1 -0
  220. package/dist/assets/puppet-BMWR74SV.js +1 -0
  221. package/dist/assets/purescript-CklMAg4u.js +1 -0
  222. package/dist/assets/python-B6aJPvgy.js +1 -0
  223. package/dist/assets/qml-3beO22l8.js +1 -0
  224. package/dist/assets/qmldir-C8lEn-DE.js +1 -0
  225. package/dist/assets/qss-IeuSbFQv.js +1 -0
  226. package/dist/assets/r-Cf5RLm7j.js +1 -0
  227. package/dist/assets/racket-BqYA7rlc.js +1 -0
  228. package/dist/assets/raku-DXvB9xmW.js +1 -0
  229. package/dist/assets/razor-BjBPvh-w.js +1 -0
  230. package/dist/assets/rbs-CpoqiR4B.js +1 -0
  231. package/dist/assets/red-hvxz__6c.js +1 -0
  232. package/dist/assets/reg-C-SQnVFl.js +1 -0
  233. package/dist/assets/regexp-CDVJQ6XC.js +1 -0
  234. package/dist/assets/rel-C3B-1QV4.js +1 -0
  235. package/dist/assets/riscv-BM1_JUlF.js +1 -0
  236. package/dist/assets/ron-D8l8udqQ.js +1 -0
  237. package/dist/assets/rose-pine-5qJOZa0Y.js +1 -0
  238. package/dist/assets/rose-pine-dawn-zx0QlTCp.js +1 -0
  239. package/dist/assets/rose-pine-moon-C9pEdX9L.js +1 -0
  240. package/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  241. package/dist/assets/rst-bs7f0vWN.js +1 -0
  242. package/dist/assets/ruby-C0TQ7zu5.js +1 -0
  243. package/dist/assets/rust-B1yitclQ.js +1 -0
  244. package/dist/assets/sas-DEy46yEz.js +1 -0
  245. package/dist/assets/sass-Cj5Yp3dK.js +1 -0
  246. package/dist/assets/scala-CqE71os6.js +1 -0
  247. package/dist/assets/scheme-C98Dy4si.js +1 -0
  248. package/dist/assets/scss-D5BDwBP9.js +1 -0
  249. package/dist/assets/sdbl-DVxCFoDh.js +1 -0
  250. package/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  251. package/dist/assets/shellscript-Yzrsuije.js +1 -0
  252. package/dist/assets/shellsession-BADoaaVG.js +1 -0
  253. package/dist/assets/slack-dark-BthQWCQV.js +1 -0
  254. package/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  255. package/dist/assets/smalltalk-BOQMe2GC.js +1 -0
  256. package/dist/assets/smithy-cds9vsN8.js +1 -0
  257. package/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  258. package/dist/assets/solarized-dark-CpvCGNkr.js +1 -0
  259. package/dist/assets/solarized-light-Dlz6yCKv.js +1 -0
  260. package/dist/assets/solidity-DijEV5ha.js +1 -0
  261. package/dist/assets/soy-8wufbnw4.js +1 -0
  262. package/dist/assets/sparql-rVzFXLq3.js +1 -0
  263. package/dist/assets/splunk-BtCnVYZw.js +1 -0
  264. package/dist/assets/sql-CRqJ_cUM.js +1 -0
  265. package/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  266. package/dist/assets/stata-DI20mbqo.js +1 -0
  267. package/dist/assets/stylus-BEDo0Tqx.js +1 -0
  268. package/dist/assets/surrealql-Cjom0U5J.js +1 -0
  269. package/dist/assets/svelte-Cy7k_4gC.js +1 -0
  270. package/dist/assets/swift-C2oV4EkX.js +1 -0
  271. package/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  272. package/dist/assets/system-verilog-0hqHdDBg.js +1 -0
  273. package/dist/assets/systemd-4A_iFExJ.js +1 -0
  274. package/dist/assets/talonscript-CkByrt1z.js +1 -0
  275. package/dist/assets/tasl-QIJgUcNo.js +1 -0
  276. package/dist/assets/tcl-dwOrl1Do.js +1 -0
  277. package/dist/assets/templ-DhtptRzy.js +1 -0
  278. package/dist/assets/terraform-BETggiCN.js +1 -0
  279. package/dist/assets/tex-D96PA37w.js +1 -0
  280. package/dist/assets/tokyo-night-hegEt444.js +1 -0
  281. package/dist/assets/toml-vGWfd6FD.js +1 -0
  282. package/dist/assets/ts-tags-D351s5mN.js +1 -0
  283. package/dist/assets/tsv-B_m7g4N7.js +1 -0
  284. package/dist/assets/tsx-COt5Ahok.js +1 -0
  285. package/dist/assets/turtle-BsS91CYL.js +1 -0
  286. package/dist/assets/twig-27uCiNez.js +1 -0
  287. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  288. package/dist/assets/typespec-CAFt9gP4.js +1 -0
  289. package/dist/assets/typst-BUadGCkm.js +1 -0
  290. package/dist/assets/v-BGw2Nkan.js +1 -0
  291. package/dist/assets/vala-CsfeWuGM.js +1 -0
  292. package/dist/assets/vb-Cu-pLBUe.js +1 -0
  293. package/dist/assets/verilog-nZwndyjY.js +1 -0
  294. package/dist/assets/vesper-DRje8inN.js +1 -0
  295. package/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  296. package/dist/assets/viml-CJc9bBzg.js +1 -0
  297. package/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  298. package/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  299. package/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  300. package/dist/assets/vue-BqiEGhQt.js +1 -0
  301. package/dist/assets/vue-html-AaS7Mt5G.js +1 -0
  302. package/dist/assets/vue-vine-BoDAl6tE.js +1 -0
  303. package/dist/assets/vyper-CDx5xZoG.js +1 -0
  304. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  305. package/dist/assets/wasm-MzD3tlZU.js +1 -0
  306. package/dist/assets/wenyan-BV7otONQ.js +1 -0
  307. package/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  308. package/dist/assets/wikitext-BhOHFoWU.js +1 -0
  309. package/dist/assets/wit-5i3qLPDT.js +1 -0
  310. package/dist/assets/wolfram-lXgVvXCa.js +1 -0
  311. package/dist/assets/xml-sdJ4AIDG.js +1 -0
  312. package/dist/assets/xsl-CtQFsRM5.js +1 -0
  313. package/dist/assets/yaml-Buea-lGh.js +1 -0
  314. package/dist/assets/zenscript-DVFEvuxE.js +1 -0
  315. package/dist/assets/zig-VOosw3JB.js +1 -0
  316. package/dist/index.html +14 -0
  317. package/dist-server/companion/src/listener.js +187 -0
  318. package/dist-server/computer-proxy.js +1185 -0
  319. package/dist-server/connector-proxy.js +186 -0
  320. package/dist-server/container-mcp.js +15186 -0
  321. package/dist-server/drivers/agents-proxy.js +887 -0
  322. package/dist-server/drivers/browser-proxy.js +15037 -0
  323. package/dist-server/drivers/dweb-proxy.js +175 -0
  324. package/dist-server/drivers/phone-proxy.js +339 -0
  325. package/dist-server/drivers/pi-mcp-extension.ts +638 -0
  326. package/dist-server/index.js +64869 -0
  327. package/dist-server/local-computer.js +198 -0
  328. package/dist-server/mcp-server.js +1233 -0
  329. package/dist-server/openmausbot.js +3671 -0
  330. package/dist-server/pair-cli.js +3671 -0
  331. package/dist-server/permission-proxy.js +126 -0
  332. package/dist-server/prepare-cloudflared.js +253 -0
  333. package/dist-server/proxy-paths.js +29 -0
  334. package/dist-server/server/atomic.js +40 -0
  335. package/dist-server/server/attachments.js +471 -0
  336. package/dist-server/server/auto-approve.js +162 -0
  337. package/dist-server/server/auto-review.js +79 -0
  338. package/dist-server/server/avatar-image.js +144 -0
  339. package/dist-server/server/bot-cwd.js +29 -0
  340. package/dist-server/server/bot-directory.js +127 -0
  341. package/dist-server/server/bot-package.js +268 -0
  342. package/dist-server/server/bot-profile.js +76 -0
  343. package/dist-server/server/box-create-idempotency.js +463 -0
  344. package/dist-server/server/box.js +944 -0
  345. package/dist-server/server/brand.js +65 -0
  346. package/dist-server/server/browser-connection.js +187 -0
  347. package/dist-server/server/browser-lifecycle-cleanup.js +359 -0
  348. package/dist-server/server/calendar-calls.js +314 -0
  349. package/dist-server/server/channel-queue.js +70 -0
  350. package/dist-server/server/checkpoints.js +405 -0
  351. package/dist-server/server/chief-of-staff.js +49 -0
  352. package/dist-server/server/claude-update.js +70 -0
  353. package/dist-server/server/cli.js +524 -0
  354. package/dist-server/server/cloud-backend.js +30 -0
  355. package/dist-server/server/comms-visibility.js +109 -0
  356. package/dist-server/server/composio.js +945 -0
  357. package/dist-server/server/computer-control.js +149 -0
  358. package/dist-server/server/computer-observation.js +133 -0
  359. package/dist-server/server/computer-proxy.js +1061 -0
  360. package/dist-server/server/config.js +760 -0
  361. package/dist-server/server/connector-proxy.js +228 -0
  362. package/dist-server/server/container-computer.js +840 -0
  363. package/dist-server/server/container-mcp.js +27 -0
  364. package/dist-server/server/contracts.js +24 -0
  365. package/dist-server/server/control-client.js +100 -0
  366. package/dist-server/server/data-dir-lease.js +3 -0
  367. package/dist-server/server/decision-log.js +106 -0
  368. package/dist-server/server/delegations.js +628 -0
  369. package/dist-server/server/drivers/acp/core.js +904 -0
  370. package/dist-server/server/drivers/acp/cursor.js +365 -0
  371. package/dist-server/server/drivers/acp/custom.js +51 -0
  372. package/dist-server/server/drivers/acp/droid.js +225 -0
  373. package/dist-server/server/drivers/acp/gemini.js +95 -0
  374. package/dist-server/server/drivers/acp/grok.js +249 -0
  375. package/dist-server/server/drivers/acp/hermes.js +429 -0
  376. package/dist-server/server/drivers/acp/kimi.js +559 -0
  377. package/dist-server/server/drivers/acp/opencode-go.js +370 -0
  378. package/dist-server/server/drivers/acp/qwen.js +98 -0
  379. package/dist-server/server/drivers/agents-proxy.js +941 -0
  380. package/dist-server/server/drivers/antigravity-acp.js +507 -0
  381. package/dist-server/server/drivers/antigravity-release.js +65 -0
  382. package/dist-server/server/drivers/antigravity-runtime.js +395 -0
  383. package/dist-server/server/drivers/antigravity.js +144 -0
  384. package/dist-server/server/drivers/boxagent.js +245 -0
  385. package/dist-server/server/drivers/browser-proxy.js +520 -0
  386. package/dist-server/server/drivers/builtIn.js +36 -0
  387. package/dist-server/server/drivers/claude.js +1337 -0
  388. package/dist-server/server/drivers/codex-catalog.js +409 -0
  389. package/dist-server/server/drivers/codex.js +1116 -0
  390. package/dist-server/server/drivers/dweb-proxy.js +193 -0
  391. package/dist-server/server/drivers/grok.js +48 -0
  392. package/dist-server/server/drivers/grokagent.js +4 -0
  393. package/dist-server/server/drivers/local-inject.js +388 -0
  394. package/dist-server/server/drivers/minimax.js +108 -0
  395. package/dist-server/server/drivers/native.js +21 -0
  396. package/dist-server/server/drivers/openai-chat.js +240 -0
  397. package/dist-server/server/drivers/openai-compat.js +143 -0
  398. package/dist-server/server/drivers/phone-proxy.js +372 -0
  399. package/dist-server/server/drivers/pi.js +855 -0
  400. package/dist-server/server/drivers/retry.js +96 -0
  401. package/dist-server/server/enterprise.js +93 -0
  402. package/dist-server/server/env-path.js +329 -0
  403. package/dist-server/server/environment.js +105 -0
  404. package/dist-server/server/generated-image.js +39 -0
  405. package/dist-server/server/graceful-shutdown.js +24 -0
  406. package/dist-server/server/group-goal-run.js +124 -0
  407. package/dist-server/server/harness/bus.js +86 -0
  408. package/dist-server/server/harness/registry.js +217 -0
  409. package/dist-server/server/index.js +11634 -0
  410. package/dist-server/server/installed-playbooks.js +34 -0
  411. package/dist-server/server/local-computer.js +278 -0
  412. package/dist-server/server/local-routing.js +9 -0
  413. package/dist-server/server/local-vm-idle.js +44 -0
  414. package/dist-server/server/local-vm-inventory.js +34 -0
  415. package/dist-server/server/local-vm-lease.js +58 -0
  416. package/dist-server/server/mcp-bridge.js +251 -0
  417. package/dist-server/server/mcp-probe.js +153 -0
  418. package/dist-server/server/mcp-registry.js +130 -0
  419. package/dist-server/server/member-turn.js +6 -0
  420. package/dist-server/server/message-db.js +358 -0
  421. package/dist-server/server/message-file.js +439 -0
  422. package/dist-server/server/names.js +26 -0
  423. package/dist-server/server/notify.js +69 -0
  424. package/dist-server/server/openmaus-status-capsule.js +329 -0
  425. package/dist-server/server/openmausbot.js +6 -0
  426. package/dist-server/server/package-export.js +157 -0
  427. package/dist-server/server/pair-cli.js +7 -0
  428. package/dist-server/server/peer-approval-key.js +5 -0
  429. package/dist-server/server/peer-approval.js +182 -0
  430. package/dist-server/server/peer-provenance.js +44 -0
  431. package/dist-server/server/peer-roster.js +139 -0
  432. package/dist-server/server/permission-proxy.js +157 -0
  433. package/dist-server/server/phone-secret.js +358 -0
  434. package/dist-server/server/private-screen-capture.js +17 -0
  435. package/dist-server/server/procs.js +123 -0
  436. package/dist-server/server/project-scout.js +304 -0
  437. package/dist-server/server/proxy-paths.js +47 -0
  438. package/dist-server/server/redact.js +102 -0
  439. package/dist-server/server/remote-computer.js +170 -0
  440. package/dist-server/server/repeat-detector.js +48 -0
  441. package/dist-server/server/replies.js +38 -0
  442. package/dist-server/server/request-auth.js +327 -0
  443. package/dist-server/server/room-cwd.js +15 -0
  444. package/dist-server/server/room-post-budget.js +112 -0
  445. package/dist-server/server/room-turn-timeout.js +115 -0
  446. package/dist-server/server/routine-requests.js +871 -0
  447. package/dist-server/server/routines.js +1111 -0
  448. package/dist-server/server/schema.js +11 -0
  449. package/dist-server/server/screen-frame-gate.js +80 -0
  450. package/dist-server/server/section-context.js +85 -0
  451. package/dist-server/server/send-idempotency.js +53 -0
  452. package/dist-server/server/sessions.js +319 -0
  453. package/dist-server/server/skill-fetch.js +148 -0
  454. package/dist-server/server/skill-learn.js +61 -0
  455. package/dist-server/server/skill-library.js +116 -0
  456. package/dist-server/server/skills.js +1223 -0
  457. package/dist-server/server/steer-queue.js +114 -0
  458. package/dist-server/server/store.js +1217 -0
  459. package/dist-server/server/surface.js +107 -0
  460. package/dist-server/server/tailscale.js +103 -0
  461. package/dist-server/server/team-backup.js +183 -0
  462. package/dist-server/server/team-library.js +205 -0
  463. package/dist-server/server/team-manifest.js +224 -0
  464. package/dist-server/server/thread-events.js +226 -0
  465. package/dist-server/server/tts/elevenlabs.js +92 -0
  466. package/dist-server/server/tts/index.js +79 -0
  467. package/dist-server/server/tts/speech-text.js +195 -0
  468. package/dist-server/server/tts/system-voices.js +74 -0
  469. package/dist-server/server/tunnel.js +202 -0
  470. package/dist-server/server/turn-context.js +40 -0
  471. package/dist-server/server/turn-dispatch-guard.js +152 -0
  472. package/dist-server/server/turn-images.js +138 -0
  473. package/dist-server/server/turn-watchdog.js +65 -0
  474. package/dist-server/server/vps-computer.js +1099 -0
  475. package/dist-server/server/vps-container-mcp.js +31 -0
  476. package/dist-server/server/webhook-ingress.js +173 -0
  477. package/dist-server/server/webhooks.js +491 -0
  478. package/dist-server/server/workspace.js +178 -0
  479. package/dist-server/shared/approval-mode.js +39 -0
  480. package/dist-server/shared/bot-avatar.js +33 -0
  481. package/dist-server/shared/bot-profile.js +7 -0
  482. package/dist-server/shared/connector-availability.js +12 -0
  483. package/dist-server/shared/credential-request.js +82 -0
  484. package/dist-server/shared/group-goal-run.js +1 -0
  485. package/dist-server/shared/import-name.js +11 -0
  486. package/dist-server/shared/mascot-bodies.js +107 -0
  487. package/dist-server/shared/routine-request.js +10 -0
  488. package/dist-server/shared/routine-run.js +1 -0
  489. package/dist-server/shared/skill-request.js +16 -0
  490. package/dist-server/shared/team-backup.js +148 -0
  491. package/dist-server/src/lib/composer-attachments.js +676 -0
  492. package/dist-server/tunnel-guardian.js +390 -0
  493. package/dist-server/vps-container-mcp.js +15209 -0
  494. package/package.json +32 -0
  495. package/skills/create-verification-skill/SKILL.md +65 -0
  496. package/skills/create-verification-skill/manifest.json +13 -0
  497. package/skills/phone-harness/SKILL.md +26 -0
  498. package/skills/phone-harness/agents/openai.yaml +13 -0
  499. package/skills/phone-harness/manifest.json +17 -0
@@ -0,0 +1,471 @@
1
+ // Image attachments: pasted/dropped images become files under
2
+ // ~/.openmausbot/attachments so every CLI engine can open them by path —
3
+ // the app never ships image bytes through the prompt itself.
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { createReadStream, linkSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
6
+ import { link, open, unlink } from "node:fs/promises";
7
+ import { join, extname } from "node:path";
8
+ import { DATA_DIR } from "./config.js";
9
+ export const ATTACHMENTS_DIR = join(DATA_DIR, "attachments");
10
+ /** The spec's ceiling: a screenshot bigger than this is rejected before it
11
+ * is ever buffered, matching the composer's existing size discipline. */
12
+ export const IMAGE_MAX_BYTES = 10 * 1024 * 1024;
13
+ /** Shared documents are deliberately smaller than Cloudflare's transport
14
+ * ceiling. This is a local inbox, not unbounded remote storage. */
15
+ export const FILE_MAX_BYTES = 25 * 1024 * 1024;
16
+ /** Attachments are durable because prompts refer to their paths. Never
17
+ * silently evict them: once this ceiling is reached, a new upload gets an
18
+ * explicit 507 and the person can decide what to remove. */
19
+ export const ATTACHMENTS_MAX_BYTES = 512 * 1024 * 1024;
20
+ /** Interrupted streamed uploads use private partial files. A crash can leave
21
+ * one behind, so future uploads remove only our stale partials — never a
22
+ * committed attachment that a transcript may still reference. */
23
+ export const ATTACHMENT_PARTIAL_MAX_AGE_MS = 60 * 60 * 1000;
24
+ const PARTIAL_NAME = /^\.openmaus-upload-[0-9a-f-]+-[0-9a-f-]+\.partial$/i;
25
+ const UPLOAD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
26
+ const activePartials = new Set();
27
+ const uploadLocks = new Map();
28
+ let reservedAttachmentBytes = 0;
29
+ const STREAM_RESERVATION_INCREMENT_BYTES = 1024 * 1024;
30
+ /** Mimes the endpoint accepts, mapped to the extension stored on disk.
31
+ * Sniffing is not attempted — a lie here only changes the filename. */
32
+ const IMAGE_MIMES = {
33
+ "image/png": ".png",
34
+ "image/jpeg": ".jpg",
35
+ "image/gif": ".gif",
36
+ "image/webp": ".webp",
37
+ };
38
+ /** Useful document formats accepted by the phone share sheet.
39
+ * Generic archives, binaries, HTML, SVG, and executable/script mimes stay
40
+ * out. Office/OpenDocument packages are allowed because they are documents,
41
+ * despite using ZIP internally. The claimed mime determines the extension;
42
+ * an attacker-controlled filename never does. */
43
+ const FILE_MIMES = {
44
+ "text/plain": ".txt",
45
+ "text/markdown": ".md",
46
+ "text/csv": ".csv",
47
+ "text/tab-separated-values": ".tsv",
48
+ "application/json": ".json",
49
+ "application/pdf": ".pdf",
50
+ "application/rtf": ".rtf",
51
+ "text/rtf": ".rtf",
52
+ "application/msword": ".doc",
53
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
54
+ "application/vnd.ms-excel": ".xls",
55
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
56
+ "application/vnd.ms-powerpoint": ".ppt",
57
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
58
+ "application/vnd.oasis.opendocument.text": ".odt",
59
+ "application/vnd.oasis.opendocument.spreadsheet": ".ods",
60
+ "application/vnd.oasis.opendocument.presentation": ".odp",
61
+ };
62
+ export function extensionForMime(mime) {
63
+ if (!mime)
64
+ return null;
65
+ return IMAGE_MIMES[mime.split(";")[0].trim().toLowerCase()] ?? null;
66
+ }
67
+ export function ensureAttachmentsDir() {
68
+ mkdirSync(ATTACHMENTS_DIR, { recursive: true, mode: 0o700 });
69
+ }
70
+ export function validateAttachmentUploadId(value) {
71
+ if (value === undefined)
72
+ return undefined;
73
+ const normalized = value.trim().toLowerCase();
74
+ if (!UPLOAD_ID.test(normalized)) {
75
+ throw statusError(400, "uploadId must be a UUID");
76
+ }
77
+ return normalized;
78
+ }
79
+ function attachmentUsageAndCleanup(now = Date.now()) {
80
+ ensureAttachmentsDir();
81
+ let bytes = 0;
82
+ let removed = 0;
83
+ for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
84
+ if (!entry.isFile())
85
+ continue;
86
+ const path = join(ATTACHMENTS_DIR, entry.name);
87
+ // A live partial is represented by its in-memory reservation. A fresh
88
+ // orphan from a crashed process still occupies disk and therefore counts
89
+ // until this same scan is allowed to remove it.
90
+ if (PARTIAL_NAME.test(entry.name) && activePartials.has(path))
91
+ continue;
92
+ try {
93
+ const stat = statSync(path);
94
+ if (PARTIAL_NAME.test(entry.name) && now - stat.mtimeMs >= ATTACHMENT_PARTIAL_MAX_AGE_MS) {
95
+ unlinkSync(path);
96
+ removed += 1;
97
+ }
98
+ else {
99
+ bytes += stat.size;
100
+ }
101
+ }
102
+ catch {
103
+ // A concurrent cleanup or manual removal made this entry consume zero.
104
+ }
105
+ }
106
+ return { bytes, removed };
107
+ }
108
+ /** Remove only abandoned temporary uploads. Active partials are protected
109
+ * even if a very slow request crosses the age threshold. */
110
+ export function cleanupStaleAttachmentPartials(now = Date.now()) {
111
+ return attachmentUsageAndCleanup(now).removed;
112
+ }
113
+ /** A retry owns the same UUID namespace as the interrupted attempt. Once it
114
+ * holds that UUID's in-process lock, any inactive partial with the prefix is
115
+ * from a crashed/failed process and can be replaced immediately instead of
116
+ * consuming quota for an hour. */
117
+ function cleanupAttachmentPartialsForUpload(uploadId) {
118
+ ensureAttachmentsDir();
119
+ const prefix = `.openmaus-upload-${uploadId}-`;
120
+ for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
121
+ if (!entry.isFile() || !entry.name.startsWith(prefix) || !PARTIAL_NAME.test(entry.name))
122
+ continue;
123
+ const path = join(ATTACHMENTS_DIR, entry.name);
124
+ if (activePartials.has(path))
125
+ continue;
126
+ try {
127
+ unlinkSync(path);
128
+ }
129
+ catch {
130
+ // A concurrent cleanup already removed the abandoned attempt.
131
+ }
132
+ }
133
+ }
134
+ class AttachmentReservation {
135
+ held = 0;
136
+ released = false;
137
+ reserve(bytes) {
138
+ if (this.released || bytes <= 0)
139
+ return;
140
+ this.reserveAtLeast(bytes, bytes);
141
+ }
142
+ reserveAtLeast(required, preferred) {
143
+ const used = attachmentUsageAndCleanup().bytes;
144
+ const available = ATTACHMENTS_MAX_BYTES - used - reservedAttachmentBytes;
145
+ if (available < required) {
146
+ throw statusError(507, `attachments storage is full (limit ${ATTACHMENTS_MAX_BYTES} bytes)`);
147
+ }
148
+ // Unknown-length HTTP bodies reserve in bounded chunks. Taking whatever
149
+ // remains below the preferred increment avoids falsely rejecting a small
150
+ // final upload near the quota while still bounding directory scans.
151
+ const granted = Math.min(preferred, available);
152
+ reservedAttachmentBytes += granted;
153
+ this.held += granted;
154
+ }
155
+ ensure(bytes) {
156
+ if (bytes <= this.held)
157
+ return;
158
+ const required = bytes - this.held;
159
+ const remainingPerFileCapacity = FILE_MAX_BYTES - this.held;
160
+ const preferred = Math.min(Math.max(required, STREAM_RESERVATION_INCREMENT_BYTES), remainingPerFileCapacity);
161
+ this.reserveAtLeast(required, preferred);
162
+ }
163
+ release() {
164
+ if (this.released)
165
+ return;
166
+ this.released = true;
167
+ reservedAttachmentBytes -= this.held;
168
+ this.held = 0;
169
+ }
170
+ }
171
+ async function withUploadLock(uploadId, operation) {
172
+ if (!uploadId)
173
+ return operation();
174
+ const previous = uploadLocks.get(uploadId) ?? Promise.resolve();
175
+ let release;
176
+ const current = new Promise((resolve) => {
177
+ release = resolve;
178
+ });
179
+ uploadLocks.set(uploadId, current);
180
+ await previous;
181
+ try {
182
+ return await operation();
183
+ }
184
+ finally {
185
+ release();
186
+ if (uploadLocks.get(uploadId) === current)
187
+ uploadLocks.delete(uploadId);
188
+ }
189
+ }
190
+ function committedPathForUpload(uploadId, extension) {
191
+ ensureAttachmentsDir();
192
+ const exact = `${uploadId}${extension}`;
193
+ for (const entry of readdirSync(ATTACHMENTS_DIR, { withFileTypes: true })) {
194
+ if (!entry.isFile() || PARTIAL_NAME.test(entry.name))
195
+ continue;
196
+ if (entry.name === exact)
197
+ return join(ATTACHMENTS_DIR, entry.name);
198
+ if (entry.name.startsWith(`${uploadId}.`)) {
199
+ throw statusError(409, "uploadId was already used for another content type");
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+ async function digestFile(path) {
205
+ const hash = createHash("sha256");
206
+ for await (const chunk of createReadStream(path))
207
+ hash.update(chunk);
208
+ return hash.digest("hex");
209
+ }
210
+ function statusError(status, message) {
211
+ return Object.assign(new Error(message), { status });
212
+ }
213
+ function normalizedMime(mime) {
214
+ const value = mime?.split(";")[0]?.trim().toLowerCase();
215
+ return value || null;
216
+ }
217
+ export function extensionForFileMime(mime) {
218
+ const value = normalizedMime(mime);
219
+ return value ? FILE_MIMES[value] ?? null : null;
220
+ }
221
+ /** Strip platform-reserved characters and replace any sender-provided
222
+ * extension with the canonical one for the accepted mime. Separators and
223
+ * encoded separators are rejected rather than massaged so traversal attempts
224
+ * remain visible as bad requests. */
225
+ export function sanitizeSharedFileName(name, mime) {
226
+ const extension = extensionForFileMime(mime);
227
+ if (!extension)
228
+ throw statusError(400, "content-type must be a supported document type");
229
+ const normalized = name.normalize("NFKC").trim();
230
+ if (!normalized)
231
+ throw statusError(400, "name is required");
232
+ if (normalized.length > 512)
233
+ throw statusError(400, "name is too long");
234
+ if (/[\\/]/.test(normalized) || /%(?:00|2f|5c)/i.test(normalized)) {
235
+ throw statusError(400, "name must be a filename, not a path");
236
+ }
237
+ const dot = normalized.lastIndexOf(".");
238
+ const withoutExtension = dot > 0 ? normalized.slice(0, dot) : normalized;
239
+ const safeCharacters = Array.from(withoutExtension, (character) => {
240
+ const code = character.charCodeAt(0);
241
+ return code <= 31 || code === 127 || "<>:\"|?*".includes(character) ? "_" : character;
242
+ }).join("");
243
+ const stem = safeCharacters
244
+ .replace(/^\.+/, "")
245
+ .replace(/[.\s]+$/, "")
246
+ .replace(/\s+/g, " ")
247
+ .trim();
248
+ const boundedStem = Array.from(stem).slice(0, 180).join("");
249
+ if (!boundedStem)
250
+ throw statusError(400, "name must contain visible characters");
251
+ return `${boundedStem}${extension}`;
252
+ }
253
+ /** Stream one shared document to a generated file without ever collecting
254
+ * the request in memory. A failed, empty, or oversized upload leaves no
255
+ * partial file behind. */
256
+ export async function saveFile(chunks, originalName, mime, options = {}) {
257
+ const normalized = normalizedMime(mime);
258
+ if (!normalized || !extensionForFileMime(normalized)) {
259
+ throw statusError(400, "content-type must be a supported document type");
260
+ }
261
+ const name = sanitizeSharedFileName(originalName, normalized);
262
+ const extension = extensionForFileMime(normalized);
263
+ const uploadId = validateAttachmentUploadId(options.uploadId);
264
+ const expectedBytes = options.expectedBytes;
265
+ if (expectedBytes !== undefined && (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0)) {
266
+ throw statusError(400, "content-length must be a non-negative integer");
267
+ }
268
+ if (expectedBytes !== undefined && expectedBytes > FILE_MAX_BYTES) {
269
+ throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
270
+ }
271
+ return withUploadLock(uploadId, async () => {
272
+ ensureAttachmentsDir();
273
+ if (uploadId)
274
+ cleanupAttachmentPartialsForUpload(uploadId);
275
+ const existing = uploadId ? committedPathForUpload(uploadId, extension) : null;
276
+ if (existing) {
277
+ const incomingHash = createHash("sha256");
278
+ let bytes = 0;
279
+ for await (const value of chunks) {
280
+ const chunk = Buffer.isBuffer(value)
281
+ ? value
282
+ : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
283
+ if (chunk.byteLength === 0)
284
+ continue;
285
+ if (bytes + chunk.byteLength > FILE_MAX_BYTES) {
286
+ throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
287
+ }
288
+ incomingHash.update(chunk);
289
+ bytes += chunk.byteLength;
290
+ }
291
+ if (bytes === 0)
292
+ throw statusError(400, "empty file");
293
+ if (statSync(existing).size !== bytes || await digestFile(existing) !== incomingHash.digest("hex")) {
294
+ throw statusError(409, "uploadId was already used for different file bytes");
295
+ }
296
+ return { path: existing, name, mime: normalized, bytes };
297
+ }
298
+ const id = uploadId ?? randomUUID();
299
+ const path = join(ATTACHMENTS_DIR, `${id}${extension}`);
300
+ const partialPath = join(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID()}.partial`);
301
+ const reservation = new AttachmentReservation();
302
+ if (expectedBytes)
303
+ reservation.reserve(expectedBytes);
304
+ activePartials.add(partialPath);
305
+ let file;
306
+ let bytes = 0;
307
+ let closed = false;
308
+ try {
309
+ file = await open(partialPath, "wx", 0o600);
310
+ for await (const value of chunks) {
311
+ const chunk = Buffer.isBuffer(value)
312
+ ? value
313
+ : Buffer.from(value.buffer, value.byteOffset, value.byteLength);
314
+ if (chunk.byteLength === 0)
315
+ continue;
316
+ if (bytes + chunk.byteLength > FILE_MAX_BYTES) {
317
+ throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
318
+ }
319
+ reservation.ensure(bytes + chunk.byteLength);
320
+ let offset = 0;
321
+ while (offset < chunk.byteLength) {
322
+ const result = await file.write(chunk, offset, chunk.byteLength - offset, null);
323
+ if (result.bytesWritten === 0)
324
+ throw new Error("file write made no progress");
325
+ offset += result.bytesWritten;
326
+ }
327
+ bytes += chunk.byteLength;
328
+ }
329
+ if (bytes === 0)
330
+ throw statusError(400, "empty file");
331
+ await file.close();
332
+ closed = true;
333
+ try {
334
+ await link(partialPath, path);
335
+ await unlink(partialPath);
336
+ }
337
+ catch (error) {
338
+ if (error.code !== "EEXIST")
339
+ throw error;
340
+ if (statSync(path).size !== bytes || await digestFile(path) !== await digestFile(partialPath)) {
341
+ throw statusError(409, "uploadId was already used for different file bytes");
342
+ }
343
+ await unlink(partialPath);
344
+ }
345
+ return { path, name, mime: normalized, bytes };
346
+ }
347
+ catch (error) {
348
+ if (file && !closed)
349
+ await file.close().catch(() => undefined);
350
+ await unlink(partialPath).catch(() => undefined);
351
+ throw error;
352
+ }
353
+ finally {
354
+ activePartials.delete(partialPath);
355
+ reservation.release();
356
+ }
357
+ });
358
+ }
359
+ /** Persist one image and return its path. The UUID filename means the name
360
+ * is never attacker-controlled and never collides; the extension preserves
361
+ * the format the sender claimed. */
362
+ export function saveImage(bytes, mime, requestedUploadId) {
363
+ const ext = extensionForMime(mime);
364
+ if (!ext)
365
+ throw Object.assign(new Error("unsupported image type"), { status: 400 });
366
+ if (bytes.byteLength === 0)
367
+ throw Object.assign(new Error("empty image"), { status: 400 });
368
+ if (bytes.byteLength > IMAGE_MAX_BYTES) {
369
+ throw Object.assign(new Error(`image exceeds ${IMAGE_MAX_BYTES} bytes`), { status: 413 });
370
+ }
371
+ const uploadId = validateAttachmentUploadId(requestedUploadId);
372
+ ensureAttachmentsDir();
373
+ const existing = uploadId ? committedPathForUpload(uploadId, ext) : null;
374
+ if (existing) {
375
+ const saved = readFileSync(existing);
376
+ if (!saved.equals(bytes)) {
377
+ throw statusError(409, "uploadId was already used for different image bytes");
378
+ }
379
+ return { path: existing, mime: mime.split(";")[0].trim().toLowerCase(), bytes: bytes.byteLength };
380
+ }
381
+ const reservation = new AttachmentReservation();
382
+ reservation.reserve(bytes.byteLength);
383
+ const id = uploadId ?? randomUUID();
384
+ const name = `${id}${ext}`;
385
+ const path = join(ATTACHMENTS_DIR, name);
386
+ const partialPath = join(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID()}.partial`);
387
+ activePartials.add(partialPath);
388
+ try {
389
+ writeFileSync(partialPath, bytes, { mode: 0o600, flag: "wx" });
390
+ try {
391
+ linkSync(partialPath, path);
392
+ }
393
+ catch (error) {
394
+ if (error.code !== "EEXIST")
395
+ throw error;
396
+ const saved = readFileSync(path);
397
+ if (!saved.equals(bytes)) {
398
+ throw statusError(409, "uploadId was already used for different image bytes");
399
+ }
400
+ }
401
+ unlinkSync(partialPath);
402
+ return { path, mime: mime.split(";")[0].trim().toLowerCase(), bytes: bytes.byteLength };
403
+ }
404
+ catch (error) {
405
+ try {
406
+ unlinkSync(partialPath);
407
+ }
408
+ catch {
409
+ // The successful path already removed it, or the write never created it.
410
+ }
411
+ throw error;
412
+ }
413
+ finally {
414
+ activePartials.delete(partialPath);
415
+ reservation.release();
416
+ }
417
+ }
418
+ /** HTTP uploads take the same per-ID lock as streamed files. saveImage stays
419
+ * synchronous for generated avatars, while this wrapper prevents an image
420
+ * and a document using the same caller-supplied UUID from committing with
421
+ * different extensions at the same time. */
422
+ export async function saveImageUpload(bytes, mime, requestedUploadId) {
423
+ const uploadId = validateAttachmentUploadId(requestedUploadId);
424
+ return withUploadLock(uploadId, async () => {
425
+ if (uploadId)
426
+ cleanupAttachmentPartialsForUpload(uploadId);
427
+ return saveImage(bytes, mime, uploadId);
428
+ });
429
+ }
430
+ /** Existence check with the same name discipline as readAttachment, without
431
+ * reading up to 10MB of pixels just to learn the file is there. */
432
+ export function attachmentExists(name) {
433
+ if (!/^[A-Za-z0-9-]+\.(png|jpg|gif|webp)$/.test(name))
434
+ return false;
435
+ try {
436
+ return statSync(join(ATTACHMENTS_DIR, name)).isFile();
437
+ }
438
+ catch {
439
+ return false;
440
+ }
441
+ }
442
+ /** Read an attachment back for serving. Only names that are exactly a bare
443
+ * filename (no separators, no dotfiles) inside ATTACHMENTS_DIR resolve —
444
+ * the route must never become a general file server for the data dir. */
445
+ export function readAttachment(name) {
446
+ if (!/^[A-Za-z0-9-]+\.(png|jpg|jpeg|gif|webp)$/.test(name))
447
+ return null;
448
+ const path = join(ATTACHMENTS_DIR, name);
449
+ if (extname(path) === ".jpeg")
450
+ return null; // saved as .jpg; .jpeg is not a name we write
451
+ try {
452
+ return { bytes: readFileSync(path), mime: mimeForExt(extname(path)) };
453
+ }
454
+ catch {
455
+ return null;
456
+ }
457
+ }
458
+ function mimeForExt(ext) {
459
+ switch (ext) {
460
+ case ".png":
461
+ return "image/png";
462
+ case ".jpg":
463
+ return "image/jpeg";
464
+ case ".gif":
465
+ return "image/gif";
466
+ case ".webp":
467
+ return "image/webp";
468
+ default:
469
+ return "application/octet-stream";
470
+ }
471
+ }
@@ -0,0 +1,162 @@
1
+ // Auto mode: when a bot may answer its own permission requests.
2
+ //
3
+ // Safe Auto and remembered grants stop at the destructive/sensitive and
4
+ // unattended boundaries below. Full access is a separate, explicitly
5
+ // acknowledged mode: it answers every permission request, including those
6
+ // guards, while questions remain outside this module and always reach a human.
7
+ //
8
+ // The guard is deliberately tiny and literal. It is NOT a security
9
+ // boundary (an agent set on damage has a thousand spellings for `rm`);
10
+ // it is a "you probably didn't mean to hand THIS one over unattended"
11
+ // backstop for the obvious catastrophes. Real containment is the
12
+ // sandbox and the bot's own computer, not a regex.
13
+ import { approvalModeFor } from "../shared/approval-mode.js";
14
+ const DESTRUCTIVE = [
15
+ /\brm\s+(-[a-z]*\s+)*-[a-z]*[rf]/i, // rm -rf, rm -fr, rm -r -f
16
+ /\bmkfs\b|\bdiskutil\s+erase|\bdd\s+[^|]*\bof=\/dev\//i,
17
+ /\bshutdown\b|\breboot\b|\bhalt\b/i,
18
+ /:\(\)\s*\{.*\}\s*;?\s*:/, // fork bomb
19
+ /\bgit\s+push\s+[^|]*--force(-with-lease)?\b|\bgit\s+reset\s+--hard\b/i,
20
+ /\bDROP\s+(TABLE|DATABASE)\b|\bTRUNCATE\s+TABLE\b/i,
21
+ /\bsudo\s+rm\b|\bchmod\s+-R\s+777\s+\//i,
22
+ ];
23
+ // Not destructive, but exactly what you don't hand over unattended: a
24
+ // bot reading your keys is quiet, permanent, and unrecoverable.
25
+ const SENSITIVE = [
26
+ /(^|[\s/"'])\.env(\.|$|["'\s])/i,
27
+ /\.ssh\/|id_rsa|id_ed25519|authorized_keys/i,
28
+ /\.aws\/credentials|\.netrc|\.npmrc|\.pypirc|\.docker\/config\.json/i,
29
+ /security\s+find-(generic|internet)-password|\bkeychain\b/i,
30
+ /\bcredentials?\.json\b|\bserviceaccount\b/i,
31
+ ];
32
+ /** First matching pattern's source, so a verdict can NAME the rule that
33
+ * made it — the decision log's whole value is "which rule", and deriving
34
+ * the match a second time at the call site is how the log and the verdict
35
+ * drift apart. */
36
+ function matchFirst(rules, text) {
37
+ for (const re of rules)
38
+ if (re.test(text))
39
+ return re.source;
40
+ return null;
41
+ }
42
+ export function looksSensitive(text) {
43
+ return matchFirst(SENSITIVE, text) !== null;
44
+ }
45
+ export function looksDestructive(text) {
46
+ return matchFirst(DESTRUCTIVE, text) !== null;
47
+ }
48
+ /** The key an "Always allow" remembers.
49
+ *
50
+ * A bare tool name is far too coarse for a command runner: remembering
51
+ * "Bash" would hand the bot a permanent unattended shell, which is the
52
+ * opposite of what someone pressing "always allow" on `git status`
53
+ * intends. Command tools are therefore keyed by their program —
54
+ * `Bash:git`, `Bash:npm` — so the grant is as narrow as the thing you
55
+ * actually looked at. Computed once, server-side, and echoed back by the
56
+ * client so the two sides can never disagree about what was granted. */
57
+ const COMMAND_TOOLS = new Set(["bash", "shell", "execute", "run_command", "computer_exec", "terminal"]);
58
+ export function approvalKey(tool, summary, scope) {
59
+ const bare = tool.replace(/^mcp__[^_]+__/, "").toLowerCase();
60
+ if (!COMMAND_TOOLS.has(bare))
61
+ return scope ? `${scope}:${tool}` : tool;
62
+ // first bare word of the command, skipping env assignments and sudo
63
+ const words = summary.trim().split(/\s+/);
64
+ let i = 0;
65
+ while (i < words.length && (/^[A-Z_][A-Z0-9_]*=/.test(words[i]) || words[i] === "sudo"))
66
+ i += 1;
67
+ const program = (words[i] ?? "").split("/").pop()?.replace(/[^\w.-]/g, "") ?? "";
68
+ const key = program ? `${tool}:${program}` : tool;
69
+ return scope ? `${scope}:${key}` : key;
70
+ }
71
+ /** A durable "Always allow" choice is offered only when that exact grant
72
+ * would be honored on the next identical request. Custom delegates approval
73
+ * semantics to config.toml, while guards and provider-sandbox changes are
74
+ * intentionally never bypassed by remembered app grants. */
75
+ export function rememberableApprovalKey(bot, tool, summary, context) {
76
+ if (!bot ||
77
+ approvalModeFor(bot) === "custom" ||
78
+ context.source !== "no-grant" ||
79
+ context.scope ||
80
+ context.requiresExplicitApproval) {
81
+ return undefined;
82
+ }
83
+ return approvalKey(tool, summary, context.scope);
84
+ }
85
+ /** The verdict AND its provenance. The decision itself is unchanged from
86
+ * autoDecision below — this exists so the decision log can record which
87
+ * rule decided without the call site re-deriving (and eventually
88
+ * mis-deriving) the match. */
89
+ export function autoVerdict(bot, tool, summary, context) {
90
+ const mode = approvalModeFor(bot);
91
+ if (context?.nativeApproval)
92
+ return { approve: null, source: "native-approval" };
93
+ // This branch intentionally precedes every guard. Entering Full access is
94
+ // separately consent-gated by the bot PATCH endpoint, and its promise is
95
+ // literal: even destructive, sensitive, unattended, and host-computer
96
+ // permission requests are approved. The request.opened caller invokes this
97
+ // function for permissions only, never for provider questions.
98
+ if (mode === "full") {
99
+ return {
100
+ approve: `approved ${tool} (full access)`,
101
+ source: "full-access",
102
+ };
103
+ }
104
+ if (context?.requiresExplicitApproval) {
105
+ return { approve: null, source: "explicit-approval-block" };
106
+ }
107
+ // the guards outrank the grants, so an "always allow" can never widen
108
+ // into them
109
+ const destructive = matchFirst(DESTRUCTIVE, summary) ?? matchFirst(DESTRUCTIVE, tool);
110
+ const sensitive = destructive ? null : matchFirst(SENSITIVE, summary);
111
+ // The grant is computed even when a hard block will refuse it: the row
112
+ // worth auditing is "this WOULD have auto-approved, and only the block
113
+ // stood in the way", which cannot be told apart from an ordinary
114
+ // "nobody granted this" card without knowing both halves.
115
+ const key = approvalKey(tool, summary, context?.scope);
116
+ const grant = destructive || sensitive
117
+ ? null
118
+ : mode !== "custom" && bot.alwaysAllow?.includes(key)
119
+ ? { approve: `auto-approved ${key} (always allowed)`, source: "always-allow", rule: key }
120
+ : mode === "auto"
121
+ ? { approve: `auto-approved ${tool}`, source: "auto-mode", rule: undefined }
122
+ : null;
123
+ if (context?.unattended) {
124
+ // Auto mode is something a person switched on for turns they are present
125
+ // for. A webhook turn begins with nobody watching, on a payload someone
126
+ // else wrote, so it does not inherit that decision — the guard above is a
127
+ // pattern list its own comment calls "not a security boundary", and it
128
+ // must not stand in for a human at 3am. A guard that would have carded
129
+ // anyway keeps its own name; the block is only the story when it is the
130
+ // thing that changed the outcome.
131
+ if (grant)
132
+ return { approve: null, source: "unattended-block", rule: grant.rule };
133
+ if (destructive)
134
+ return { approve: null, source: "destructive-guard", rule: destructive };
135
+ if (sensitive)
136
+ return { approve: null, source: "sensitive-guard", rule: sensitive };
137
+ return { approve: null, source: "no-grant" };
138
+ }
139
+ if (context?.scope === "local-computer" && mode !== "auto") {
140
+ // Host control is not covered by a remembered always-allow grant.
141
+ // After the Auto-on-this-computer warning, unclassified GUI actions
142
+ // (click/type) may auto-approve; destructive/sensitive still card.
143
+ if (grant)
144
+ return { approve: null, source: "local-computer-block", rule: grant.rule };
145
+ if (destructive)
146
+ return { approve: null, source: "destructive-guard", rule: destructive };
147
+ if (sensitive)
148
+ return { approve: null, source: "sensitive-guard", rule: sensitive };
149
+ return { approve: null, source: "no-grant" };
150
+ }
151
+ if (destructive)
152
+ return { approve: null, source: "destructive-guard", rule: destructive };
153
+ if (sensitive)
154
+ return { approve: null, source: "sensitive-guard", rule: sensitive };
155
+ if (grant)
156
+ return { approve: grant.approve, source: grant.source, rule: grant.rule };
157
+ return { approve: null, source: "no-grant" };
158
+ }
159
+ /** Why this request may be answered without the human, or null to ask. */
160
+ export function autoDecision(bot, tool, summary, context) {
161
+ return autoVerdict(bot, tool, summary, context).approve;
162
+ }