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,760 @@
1
+ // Config + data dirs. One file, ~/.openmausbot/config.json, env fallbacks:
2
+ // { "xai": {"key":"xai-…"}, "composio": {"apiKey":"ak_…"}, "box": {"token":"…"},
3
+ // "instances": { "<instanceId>": {"driver":"grok", …} } }
4
+ import { readFileSync, mkdirSync, existsSync, renameSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { z } from "zod";
8
+ import { writeFileAtomic } from "./atomic.js";
9
+ import { parseStoredMcpServer } from "./mcp-registry.js";
10
+ import { parseJson, schemaIssue } from "./schema.js";
11
+ const optionalText = z.string().optional();
12
+ const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
13
+ const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
14
+ const BROWSER_PROFILE_ID = /^[a-z0-9_-]{1,40}$/;
15
+ export const DEFAULT_ROOM_TURN_TIMEOUT_MINUTES = 5;
16
+ export const MIN_ROOM_TURN_TIMEOUT_MINUTES = 1;
17
+ export const MAX_ROOM_TURN_TIMEOUT_MINUTES = 1_440;
18
+ export const DEFAULT_LOCAL_VM_MODE = "shared";
19
+ export const DEFAULT_LOCAL_VM_MAX_INSTANCES = 2;
20
+ export const MIN_LOCAL_VM_MAX_INSTANCES = 1;
21
+ export const MAX_LOCAL_VM_MAX_INSTANCES = 4;
22
+ export function isValidSshAlias(value) {
23
+ return typeof value === "string" && SSH_ALIAS.test(value);
24
+ }
25
+ /** Keep the persisted VPS shape deliberately smaller than an SSH connection. */
26
+ export function normalizeVpsConfig(raw) {
27
+ if (raw === undefined || raw === null)
28
+ return {};
29
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
30
+ throw new Error("vps must be an object containing an SSH config alias");
31
+ }
32
+ const alias = raw.sshAlias;
33
+ if (alias === undefined || alias === "")
34
+ return {};
35
+ if (!isValidSshAlias(alias)) {
36
+ throw new Error("vps.sshAlias must be a simple SSH config alias (letters, numbers, dot, dash, or underscore)");
37
+ }
38
+ return { sshAlias: alias };
39
+ }
40
+ const vpsConfigSchema = z.object({
41
+ sshAlias: z.string().refine((value) => value === "" || isValidSshAlias(value), {
42
+ message: "must be a simple SSH config alias",
43
+ }).optional(),
44
+ });
45
+ const roomConfigSchema = z.object({
46
+ turnTimeoutMinutes: z
47
+ .number()
48
+ .int()
49
+ .min(MIN_ROOM_TURN_TIMEOUT_MINUTES)
50
+ .max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
51
+ });
52
+ const localVmConfigSchema = z.object({
53
+ mode: z.enum(["shared", "per-bot"]).optional(),
54
+ maxInstances: z
55
+ .number()
56
+ .int()
57
+ .min(MIN_LOCAL_VM_MAX_INSTANCES)
58
+ .max(MAX_LOCAL_VM_MAX_INSTANCES)
59
+ .optional(),
60
+ });
61
+ /** A named, shareable browser session ("Work", "Client A"). The id names a
62
+ * durable Electron partition; user-controlled characters never reach it. */
63
+ const browserProfileSchema = z.object({
64
+ // "guest" is the throwaway session's reserved id, never a saved profile
65
+ // Lowercase is part of the storage contract: durable Chromium partition
66
+ // directories would otherwise collide on case-insensitive filesystems.
67
+ id: z.string().regex(BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved"),
68
+ name: z.string().trim().min(1).max(40),
69
+ }).strict();
70
+ // #567 accepted mixed-case and duplicate ids. This schema exists only at the
71
+ // persisted-data boundary so an existing config can be read and migrated;
72
+ // API patches and save inputs continue to use browserProfileSchema above.
73
+ const legacyBrowserProfileSchema = z.object({
74
+ id: z.string().regex(LEGACY_BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved"),
75
+ name: z.string().trim().min(1).max(40),
76
+ /** Exact #567 Electron partition identity. This is persisted only by the
77
+ * migration boundary; config PATCH callers cannot choose or redirect it. */
78
+ partitionId: z.string().regex(LEGACY_BROWSER_PROFILE_ID).refine((id) => id !== "guest", "guest is reserved").optional(),
79
+ }).strict();
80
+ function suffixedBrowserProfileId(base, unavailable) {
81
+ for (let suffix = 2;; suffix += 1) {
82
+ const ending = `-${suffix}`;
83
+ const candidate = `${base.slice(0, 40 - ending.length)}${ending}`;
84
+ if (candidate !== "guest" && !unavailable.has(candidate))
85
+ return candidate;
86
+ }
87
+ }
88
+ function migrateStoredBrowserProfiles(profiles) {
89
+ const requestedPartitions = profiles.map((profile) => profile.partitionId ?? profile.id);
90
+ const rawBases = profiles.map((profile) => profile.id.toLowerCase());
91
+ // Canonical logical ids must be stable even if bots.json is migrated before
92
+ // config.json is rewritten. Give an exact lowercase spelling first claim on
93
+ // its id, then the first case variant. Generated ids avoid every legacy base
94
+ // and partition spelling, so applying the same legacy alias map again cannot
95
+ // reinterpret a previously migrated bot reference.
96
+ const canonicalIds = Array(profiles.length).fill(undefined);
97
+ const used = new Set();
98
+ const baseOwner = new Map();
99
+ rawBases.forEach((base, index) => {
100
+ if (base === "guest")
101
+ return;
102
+ const current = baseOwner.get(base);
103
+ if (current === undefined || (profiles[index].id === base && profiles[current].id !== base)) {
104
+ baseOwner.set(base, index);
105
+ }
106
+ });
107
+ for (const [base, index] of baseOwner) {
108
+ canonicalIds[index] = base;
109
+ used.add(base);
110
+ }
111
+ const reserved = new Set([
112
+ "guest",
113
+ ...rawBases,
114
+ ...requestedPartitions.map((partitionId) => partitionId.toLowerCase()),
115
+ ]);
116
+ rawBases.forEach((base, index) => {
117
+ if (canonicalIds[index] !== undefined)
118
+ return;
119
+ const id = suffixedBrowserProfileId(base, new Set([...reserved, ...used]));
120
+ canonicalIds[index] = id;
121
+ used.add(id);
122
+ });
123
+ // Chromium partition directories collide by case on Windows and default
124
+ // macOS volumes. Pick one safe owner for every case-folded identity. Prefer
125
+ // the profile whose canonical id matches that partition; every loser gets a
126
+ // new partition named after its collision-safe logical id.
127
+ const partitionWinner = new Map();
128
+ requestedPartitions.forEach((partitionId, index) => {
129
+ const folded = partitionId.toLowerCase();
130
+ const current = partitionWinner.get(folded);
131
+ if (current === undefined) {
132
+ partitionWinner.set(folded, index);
133
+ return;
134
+ }
135
+ const score = (candidate) => canonicalIds[candidate] === folded ? 1 : 0;
136
+ if (score(index) > score(current))
137
+ partitionWinner.set(folded, index);
138
+ });
139
+ let effectivePartitions = requestedPartitions.map((partitionId, index) => partitionWinner.get(partitionId.toLowerCase()) === index ? partitionId : canonicalIds[index]);
140
+ // An earlier implementation could produce a cycle such as
141
+ // `foo-2 -> partition foo-2-2` and `foo-2-2 -> partition FOO-2`. The
142
+ // partitions are distinct today, but deleting and re-adding either id would
143
+ // join the other account. Move the *logical id owner* to a fresh id while
144
+ // retaining both exact durable partitions. Fresh ids avoid every raw id, so
145
+ // the old->new bot aliases below remain fixed points across repeated starts.
146
+ const conflictingIdOwners = new Set();
147
+ canonicalIds.forEach((id, owner) => {
148
+ effectivePartitions.forEach((partitionId, partitionOwner) => {
149
+ if (partitionOwner !== owner && partitionId.toLowerCase() === id)
150
+ conflictingIdOwners.add(owner);
151
+ });
152
+ });
153
+ const unavailable = new Set([...reserved, ...used]);
154
+ for (const owner of conflictingIdOwners) {
155
+ const id = suffixedBrowserProfileId(rawBases[owner], unavailable);
156
+ canonicalIds[owner] = id;
157
+ unavailable.add(id);
158
+ }
159
+ if (conflictingIdOwners.size > 0) {
160
+ effectivePartitions = requestedPartitions.map((partitionId, index) => partitionWinner.get(partitionId.toLowerCase()) === index ? partitionId : canonicalIds[index]);
161
+ }
162
+ const aliases = new Map();
163
+ const canonical = profiles.map((profile, index) => {
164
+ const id = canonicalIds[index];
165
+ const partitionId = effectivePartitions[index];
166
+ const migrated = { id, name: profile.name };
167
+ if (partitionId !== id)
168
+ migrated.partitionId = partitionId;
169
+ // Exact duplicates are inherently ambiguous. Preserve the first mapping;
170
+ // later duplicate records get isolated ids but existing bot references
171
+ // cannot be distinguished from the first record.
172
+ if (!aliases.has(profile.id))
173
+ aliases.set(profile.id, id);
174
+ return migrated;
175
+ });
176
+ return { profiles: canonical, aliases };
177
+ }
178
+ const legacyBrowserProfilesSchema = z.array(legacyBrowserProfileSchema).max(20);
179
+ const storedBrowserProfilesSchema = legacyBrowserProfilesSchema.transform((profiles) => migrateStoredBrowserProfiles(profiles).profiles);
180
+ const browserProfilesSchema = z.array(browserProfileSchema).max(20).superRefine((profiles, ctx) => {
181
+ const seen = new Set();
182
+ profiles.forEach((profile, index) => {
183
+ if (!seen.has(profile.id)) {
184
+ seen.add(profile.id);
185
+ return;
186
+ }
187
+ ctx.addIssue({
188
+ code: "custom",
189
+ path: [index, "id"],
190
+ message: `browser profile id ${profile.id} is duplicated`,
191
+ });
192
+ });
193
+ });
194
+ const featureConfigSchema = z.object({
195
+ /** Experimental desktop workflow recorder. Hidden unless explicitly enabled. */
196
+ skillRecorder: z.boolean().optional(),
197
+ /** Show each tool run in the transcript. Off unless explicitly enabled. */
198
+ showToolCalls: z.boolean().optional(),
199
+ /** Experimental built-in browser. Off until explicitly enabled; each bot
200
+ * also has its own switch. */
201
+ browser: z.boolean().optional(),
202
+ });
203
+ const instanceConfigSchema = z.object({
204
+ driver: z.string().min(1),
205
+ displayName: optionalText,
206
+ accentColor: optionalText,
207
+ environment: z.record(z.string(), z.string()).optional(),
208
+ enabled: z.boolean().optional(),
209
+ config: z.json().optional(),
210
+ });
211
+ const instanceConfigMapSchema = z.record(z.string(), instanceConfigSchema);
212
+ const appConfigSchema = z.object({
213
+ xai: z.object({ key: optionalText, url: optionalText }).optional(),
214
+ /** `model` seeds the default selection; `provider` pins an OpenRouter
215
+ * upstream (e.g. "fireworks"). Both are non-secret and optional. */
216
+ openaiCompat: z
217
+ .object({ key: optionalText, url: optionalText, model: optionalText, provider: optionalText })
218
+ .optional(),
219
+ /** Project key used for Sessions, catalog and agent tools. userId/sessionId
220
+ * are non-secret local identifiers used to reuse one Composio Session. */
221
+ composio: z.object({ apiKey: optionalText, userId: optionalText, sessionId: optionalText }).optional(),
222
+ box: z.object({ token: optionalText }).optional(),
223
+ vps: vpsConfigSchema.optional(),
224
+ /** Optional OpenCode key; persisted write-only and passed only to its child. */
225
+ opencodeGo: z.object({ apiKey: optionalText }).optional(),
226
+ /** Voice credentials and the selected voice id. `provider` picks the
227
+ * engine: "elevenlabs" (default; needs a key) or "system" (the Mac's
228
+ * built-in voices, no key). */
229
+ tts: z.object({ key: optionalText, voice: optionalText, provider: z.enum(["elevenlabs", "system"]).optional() }).optional(),
230
+ /** OpenAI key used only by the in-process avatar image generator. */
231
+ imageGen: z.object({ key: optionalText }).optional(),
232
+ /** Non-secret profile details shown in the sidebar. */
233
+ profile: z.object({ name: optionalText, email: optionalText }).optional(),
234
+ /** UI language override (BCP-47, lowercase). Empty/absent = follow the
235
+ * system language. Unknown tags degrade to English in the renderer. */
236
+ language: optionalText,
237
+ rooms: roomConfigSchema.optional(),
238
+ localVm: localVmConfigSchema.optional(),
239
+ features: featureConfigSchema.optional(),
240
+ browserProfiles: browserProfilesSchema.optional(),
241
+ instances: instanceConfigMapSchema.optional(),
242
+ /** User-configured MCP servers, mounted into every capable engine. Kept
243
+ * loosely typed HERE on purpose: parseStoredConfig throws away the whole
244
+ * file on a schema error, and one bad server entry must degrade to a
245
+ * skipped entry (customMcpServers), never to a vanished config. */
246
+ mcpServers: z.record(z.string(), z.unknown()).optional(),
247
+ });
248
+ const storedAppConfigSchema = appConfigSchema.extend({
249
+ browserProfiles: storedBrowserProfilesSchema.optional(),
250
+ });
251
+ const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true });
252
+ const jsonObjectSchema = z.record(z.string(), z.json());
253
+ /** Resolve a canonical profile record to its exact durable Electron
254
+ * partition identity. Callers must never substitute the display/API id. */
255
+ export function browserProfilePartitionId(profile) {
256
+ return profile.partitionId ?? profile.id;
257
+ }
258
+ /** Every durable partition must have one owner, and no other profile may use
259
+ * that partition's folded name as its logical id. Otherwise deleting and
260
+ * re-adding the logical id can silently attach a bot to the retained account. */
261
+ export function browserProfileRoutingConflict(profiles) {
262
+ const logicalOwner = new Map(profiles.map((profile, index) => [profile.id.toLowerCase(), index]));
263
+ const partitionOwner = new Map();
264
+ for (const [index, profile] of profiles.entries()) {
265
+ const partitionId = browserProfilePartitionId(profile);
266
+ const foldedPartition = partitionId.toLowerCase();
267
+ const existingPartitionOwner = partitionOwner.get(foldedPartition);
268
+ if (existingPartitionOwner !== undefined && existingPartitionOwner !== index) {
269
+ return `browser profiles cannot share the durable session “${partitionId}”`;
270
+ }
271
+ partitionOwner.set(foldedPartition, index);
272
+ const otherLogicalOwner = logicalOwner.get(foldedPartition);
273
+ if (otherLogicalOwner !== undefined && otherLogicalOwner !== index) {
274
+ return `browser profile id “${profiles[otherLogicalOwner].id}” is already used by another durable session`;
275
+ }
276
+ }
277
+ return null;
278
+ }
279
+ /** A list replacement cannot recycle a removed partition in the same write.
280
+ * Electron erases that partition only after commit, so allowing a new profile
281
+ * to claim its case-folded name would race new activity against the wipe. */
282
+ export function browserProfileReplacementConflict(currentProfiles, nextProfiles) {
283
+ const routingConflict = browserProfileRoutingConflict(nextProfiles);
284
+ if (routingConflict)
285
+ return routingConflict;
286
+ const currentIds = new Set(currentProfiles.map((profile) => profile.id));
287
+ const nextIds = new Set(nextProfiles.map((profile) => profile.id));
288
+ const removedPartitions = new Set(currentProfiles
289
+ .filter((profile) => !nextIds.has(profile.id))
290
+ .map((profile) => browserProfilePartitionId(profile).toLowerCase()));
291
+ const reused = nextProfiles.find((profile) => !currentIds.has(profile.id)
292
+ && removedPartitions.has(browserProfilePartitionId(profile).toLowerCase()));
293
+ return reused
294
+ ? `browser profile “${reused.name}” cannot reuse a session that is being erased; delete it first, then add the new profile`
295
+ : null;
296
+ }
297
+ export function browserProfilePartitionTarget(config, profileId) {
298
+ const profile = config.browserProfiles?.find((candidate) => candidate.id === profileId);
299
+ return profile ? { profileId: profile.id, partitionId: browserProfilePartitionId(profile) } : null;
300
+ }
301
+ export function parseStoredConfig(value) {
302
+ const parsed = storedAppConfigSchema.safeParse(value);
303
+ if (!parsed.success)
304
+ throw new Error(schemaIssue(parsed.error, "Invalid stored configuration"));
305
+ return parsed.data;
306
+ }
307
+ /** Exact old→canonical profile ids from #567's persisted config. Store
308
+ * hydration uses this to migrate bot references in the same write that
309
+ * resets other transient bot state. Invalid/non-legacy config is inert. */
310
+ export function loadBrowserProfileIdAliases() {
311
+ try {
312
+ const document = z.object({ browserProfiles: legacyBrowserProfilesSchema.optional() }).safeParse(parseJson(readFileSync(join(DATA_DIR, "config.json"), "utf8")));
313
+ if (!document.success || !document.data.browserProfiles)
314
+ return new Map();
315
+ return migrateStoredBrowserProfiles(document.data.browserProfiles).aliases;
316
+ }
317
+ catch {
318
+ return new Map();
319
+ }
320
+ }
321
+ export function parseConfigPatch(value) {
322
+ const parsed = appConfigPatchSchema.safeParse(value);
323
+ if (!parsed.success) {
324
+ throw Object.assign(new Error(schemaIssue(parsed.error, "Invalid configuration")), { status: 400 });
325
+ }
326
+ return parsed.data;
327
+ }
328
+ export function vpsSshAlias(cfg) {
329
+ return isValidSshAlias(cfg.vps?.sshAlias) ? cfg.vps.sshAlias : null;
330
+ }
331
+ export function roomTurnTimeoutMinutes(cfg) {
332
+ return cfg.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
333
+ }
334
+ export function localVmMode(cfg) {
335
+ return cfg.localVm?.mode ?? DEFAULT_LOCAL_VM_MODE;
336
+ }
337
+ export function localVmMaxInstances(cfg) {
338
+ return cfg.localVm?.maxInstances ?? DEFAULT_LOCAL_VM_MAX_INSTANCES;
339
+ }
340
+ export function skillRecorderEnabled(cfg) {
341
+ return cfg.features?.skillRecorder === true;
342
+ }
343
+ export function showToolCallsEnabled(cfg) {
344
+ return cfg.features?.showToolCalls === true;
345
+ }
346
+ /** Workspace-level gate for the experimental built-in browser. A bot's own
347
+ * switch sits under it, so either can withhold the browser. */
348
+ export function builtInBrowserEnabled(cfg) {
349
+ return cfg.features?.browser === true;
350
+ }
351
+ // OMB_DATA_DIR isolates test/soak rigs from the user's real fleet.
352
+ export const DATA_DIR = process.env.OMB_DATA_DIR ?? join(homedir(), ".openmausbot");
353
+ const LEGACY_DATA_DIR = join(homedir(), ".opengrokbot");
354
+ export const EVENTS_DIR = join(DATA_DIR, "events");
355
+ export const NATIVE_DIR = join(DATA_DIR, "native");
356
+ export function ensureDirs() {
357
+ // one-time migration from the pre-rename data dir — bots, transcripts,
358
+ // config and keys all carry over
359
+ if (!existsSync(DATA_DIR) && existsSync(LEGACY_DATA_DIR)) {
360
+ try {
361
+ renameSync(LEGACY_DATA_DIR, DATA_DIR);
362
+ }
363
+ catch {
364
+ /* cross-device or busy — fall through to a fresh dir */
365
+ }
366
+ }
367
+ for (const dir of [DATA_DIR, EVENTS_DIR, NATIVE_DIR])
368
+ mkdirSync(dir, { recursive: true });
369
+ }
370
+ export function loadConfig() {
371
+ let cfg = {};
372
+ try {
373
+ cfg = parseStoredConfig(parseJson(readFileSync(join(DATA_DIR, "config.json"), "utf8")));
374
+ }
375
+ catch {
376
+ /* first run — env fallbacks below */
377
+ }
378
+ // Env wins over the file for every credential. The desktop shell keeps
379
+ // these secrets OS-encrypted and hands them to this process as env at
380
+ // spawn, leaving config.json without the plaintext field — so the file
381
+ // value is the dev-mode (no desktop shell) fallback, not the primary.
382
+ // Anything that saves a credential mid-session must keep process.env in
383
+ // step (syncCredentialEnv below), or the value injected at boot would
384
+ // shadow the save until the next launch.
385
+ cfg.xai = { ...cfg.xai };
386
+ if (process.env.XAI_API_KEY !== undefined)
387
+ cfg.xai.key = process.env.XAI_API_KEY;
388
+ cfg.openaiCompat = { ...cfg.openaiCompat };
389
+ if (process.env.OPENAI_COMPAT_API_KEY !== undefined)
390
+ cfg.openaiCompat.key = process.env.OPENAI_COMPAT_API_KEY;
391
+ if (process.env.OPENAI_COMPAT_URL !== undefined)
392
+ cfg.openaiCompat.url = process.env.OPENAI_COMPAT_URL;
393
+ if (process.env.OPENAI_COMPAT_MODEL !== undefined)
394
+ cfg.openaiCompat.model = process.env.OPENAI_COMPAT_MODEL;
395
+ if (process.env.OPENAI_COMPAT_PROVIDER !== undefined)
396
+ cfg.openaiCompat.provider = process.env.OPENAI_COMPAT_PROVIDER;
397
+ cfg.composio = { ...cfg.composio };
398
+ if (process.env.COMPOSIO_API_KEY !== undefined)
399
+ cfg.composio.apiKey = process.env.COMPOSIO_API_KEY;
400
+ cfg.box = { ...cfg.box };
401
+ if (process.env.BOX_TOKEN !== undefined)
402
+ cfg.box.token = process.env.BOX_TOKEN;
403
+ cfg.opencodeGo = { ...cfg.opencodeGo };
404
+ if (process.env.OPENCODE_API_KEY !== undefined)
405
+ cfg.opencodeGo.apiKey = process.env.OPENCODE_API_KEY;
406
+ cfg.tts = { ...cfg.tts };
407
+ if (process.env.OMB_TTS_KEY !== undefined)
408
+ cfg.tts.key = process.env.OMB_TTS_KEY;
409
+ cfg.imageGen = { ...cfg.imageGen };
410
+ if (process.env.OMB_OPENAI_IMAGE_KEY !== undefined)
411
+ cfg.imageGen.key = process.env.OMB_OPENAI_IMAGE_KEY;
412
+ return cfg;
413
+ }
414
+ /** After saveConfig() writes a credential, the running process's env must
415
+ * follow the newest value — loadConfig() prefers env, so the secret injected
416
+ * at boot would otherwise shadow the save until relaunch: the UI would show
417
+ * "saved" while every turn still used the old key. An empty string means the
418
+ * user cleared the credential, so the var is dropped and the (now empty)
419
+ * file value is authoritative again. Fields absent from the patch are
420
+ * untouched. */
421
+ export function syncCredentialEnv(patch) {
422
+ const secrets = [
423
+ [patch.xai?.key, "XAI_API_KEY"],
424
+ [patch.openaiCompat?.key, "OPENAI_COMPAT_API_KEY"],
425
+ [patch.composio?.apiKey, "COMPOSIO_API_KEY"],
426
+ [patch.box?.token, "BOX_TOKEN"],
427
+ [patch.opencodeGo?.apiKey, "OPENCODE_API_KEY"],
428
+ [patch.tts?.key, "OMB_TTS_KEY"],
429
+ [patch.imageGen?.key, "OMB_OPENAI_IMAGE_KEY"],
430
+ ];
431
+ for (const [value, name] of secrets) {
432
+ if (value === undefined)
433
+ continue;
434
+ if (value)
435
+ process.env[name] = value;
436
+ else
437
+ delete process.env[name];
438
+ }
439
+ // loadConfig() also prefers env for url/model/provider, so a saved value
440
+ // must follow the same set-when-truthy / delete-when-cleared rule as keys.
441
+ const settings = [
442
+ [patch.openaiCompat?.url, "OPENAI_COMPAT_URL"],
443
+ [patch.openaiCompat?.model, "OPENAI_COMPAT_MODEL"],
444
+ [patch.openaiCompat?.provider, "OPENAI_COMPAT_PROVIDER"],
445
+ ];
446
+ for (const [value, name] of settings) {
447
+ if (value === undefined)
448
+ continue;
449
+ if (value)
450
+ process.env[name] = value;
451
+ else
452
+ delete process.env[name];
453
+ }
454
+ }
455
+ /** Env names of every workspace credential this process may be holding —
456
+ * injected at boot by the desktop shell or exported by a developer. Spawned
457
+ * engine CLIs must never inherit them: the one driver that consumes a given
458
+ * secret receives it through instanceConfigs() narrowing, and to every other
459
+ * child these are someone else's keys riding along in `...process.env`. */
460
+ export const WORKSPACE_CREDENTIAL_ENV = [
461
+ "XAI_API_KEY",
462
+ "OPENAI_COMPAT_API_KEY",
463
+ "OPENAI_COMPAT_URL",
464
+ "BOX_TOKEN",
465
+ "OPENCODE_API_KEY",
466
+ "OMB_TTS_KEY",
467
+ "OMB_OPENAI_IMAGE_KEY",
468
+ "COMPOSIO_API_KEY",
469
+ "OMB_COMPOSIO_BROKER_TOKEN",
470
+ // Harness-private filesystem hints are not credentials themselves, but
471
+ // exposing them to a shell-capable agent points straight at app-owned
472
+ // state. The built-in browser master is delivered privately in memory.
473
+ "OMB_BROWSER_CONNECTION",
474
+ "OMB_USER_DATA",
475
+ ];
476
+ /** Drop every workspace credential from a child-process env (in place). */
477
+ export function stripWorkspaceCredentialEnv(env) {
478
+ for (const key of WORKSPACE_CREDENTIAL_ENV)
479
+ delete env[key];
480
+ }
481
+ /** Env names a provider CLI might read as its own billing identity. A spawned
482
+ * engine keeps only what its driver explicitly allows: a foreign key riding
483
+ * along in `...process.env` must not flip a subscription CLI onto
484
+ * pay-as-you-go billing the user never granted. */
485
+ export const PROVIDER_CREDENTIAL_ENV = [
486
+ "ANTHROPIC_API_KEY",
487
+ "FACTORY_API_KEY",
488
+ "GEMINI_API_KEY",
489
+ "GOOGLE_API_KEY",
490
+ "KIMI_API_KEY",
491
+ "MOONSHOT_API_KEY",
492
+ "MINIMAX_API_KEY",
493
+ "OPENAI_API_KEY",
494
+ "OPENCODE_API_KEY",
495
+ "XAI_API_KEY",
496
+ "CURSOR_API_KEY",
497
+ "CURSOR_AUTH_TOKEN",
498
+ ];
499
+ /** Merge a partial config into ~/.openmausbot/config.json (secrets never
500
+ * echoed back — callers report configured-or-not booleans only). */
501
+ export function saveConfig(patch) {
502
+ const p = join(DATA_DIR, "config.json");
503
+ let disk = {};
504
+ try {
505
+ const parsed = jsonObjectSchema.safeParse(parseJson(readFileSync(p, "utf8")));
506
+ if (parsed.success)
507
+ disk = parsed.data;
508
+ }
509
+ catch {
510
+ /* first write */
511
+ }
512
+ const checkedPatch = appConfigSchema.partial().parse(patch);
513
+ // A write is the durable migration point. Preserve every other raw key in
514
+ // config.json, but never write #567's mixed-case or duplicate profile ids
515
+ // back after we have successfully recognized the legacy list.
516
+ const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
517
+ if (storedProfiles.success)
518
+ disk.browserProfiles = storedProfiles.data;
519
+ for (const key of ["xai", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "localVm", "features"]) {
520
+ const section = checkedPatch[key];
521
+ if (!section)
522
+ continue;
523
+ const current = jsonObjectSchema.safeParse(disk[key]);
524
+ const merged = current.success ? { ...current.data } : {};
525
+ Object.assign(merged, section);
526
+ disk[key] = merged;
527
+ }
528
+ if (checkedPatch.vps !== undefined)
529
+ disk.vps = normalizeVpsConfig(checkedPatch.vps);
530
+ // scalar, not a section: the merge loop above only walks objects
531
+ if (checkedPatch.language !== undefined)
532
+ disk.language = checkedPatch.language;
533
+ // Custom MCP mutations go through their own dedicated local API, but
534
+ // saveConfig remains the single atomic persistence boundary.
535
+ if (checkedPatch.mcpServers !== undefined) {
536
+ disk.mcpServers = jsonObjectSchema.parse(checkedPatch.mcpServers);
537
+ }
538
+ // the whole list is the unit of change: an add or a delete arrives as the
539
+ // new list, never as a per-item merge
540
+ if (checkedPatch.browserProfiles !== undefined) {
541
+ // `partitionId` is read-only migration metadata. A rename/list replace
542
+ // from the renderer omits it, so carry it forward only for an unchanged
543
+ // canonical id. A genuinely new id always gets its own fresh partition.
544
+ const existingProfiles = new Map((storedProfiles.success ? storedProfiles.data : []).map((profile) => [profile.id, profile]));
545
+ const nextProfiles = checkedPatch.browserProfiles.map((profile) => {
546
+ const partitionId = existingProfiles.get(profile.id)?.partitionId;
547
+ return partitionId ? { ...profile, partitionId } : profile;
548
+ });
549
+ const routingConflict = browserProfileReplacementConflict(storedProfiles.success ? storedProfiles.data : [], nextProfiles);
550
+ if (routingConflict)
551
+ throw Object.assign(new Error(routingConflict), { status: 409 });
552
+ disk.browserProfiles = nextProfiles;
553
+ }
554
+ if (checkedPatch.instances) {
555
+ const currentInstances = jsonObjectSchema.safeParse(disk.instances);
556
+ const diskInstances = currentInstances.success ? currentInstances.data : {};
557
+ for (const [instanceId, entry] of Object.entries(checkedPatch.instances)) {
558
+ const current = jsonObjectSchema.safeParse(diskInstances[instanceId]);
559
+ const merged = current.success ? { ...current.data } : {};
560
+ Object.assign(merged, entry);
561
+ diskInstances[instanceId] = merged;
562
+ }
563
+ disk.instances = diskInstances;
564
+ }
565
+ mkdirSync(DATA_DIR, { recursive: true });
566
+ writeFileAtomic(p, JSON.stringify(disk, null, 2), { mode: 0o600 });
567
+ }
568
+ /** Set one instance's `config.cli` ("" clears the override back to the
569
+ * driver default). Creating the instance entry is fine — a config-less
570
+ * entry rides driver.defaultConfig(). Returns false for unknown instances
571
+ * when the fleet is explicitly configured. The returned map must stay
572
+ * PERSISTABLE: instanceConfigs() injects credential env into consuming
573
+ * drivers' entries for the live fleet, so those injected keys are stripped
574
+ * back out before the map is returned — otherwise saving an override would
575
+ * copy xai/box/opencodeGo secrets into the instances section of
576
+ * config.json. */
577
+ export function withInstanceCli(cfg, instanceId, cli) {
578
+ const next = structuredClone(cfg);
579
+ const map = instanceConfigs(next);
580
+ // hasOwn, not truthiness: map is a plain object literal, so
581
+ // map["__proto__"] resolves to Object.prototype — truthy — and the
582
+ // assignment below would poison EVERY object in the process (instanceId
583
+ // comes off the URL, where `__proto__` passes the route's [\w.-]+ regex)
584
+ if (!Object.hasOwn(map, instanceId))
585
+ return { ok: false, config: cfg };
586
+ const entry = map[instanceId];
587
+ const cliKey = cli.trim();
588
+ const currentConfig = jsonObjectSchema.safeParse(entry.config);
589
+ if (cliKey) {
590
+ const nextConfig = currentConfig.success ? { ...currentConfig.data } : {};
591
+ nextConfig.cli = cliKey;
592
+ entry.config = nextConfig;
593
+ }
594
+ else if (currentConfig.success && Object.hasOwn(currentConfig.data, "cli")) {
595
+ const rest = { ...currentConfig.data };
596
+ delete rest.cli;
597
+ entry.config = Object.keys(rest).length ? rest : undefined;
598
+ }
599
+ for (const e of Object.values(map)) {
600
+ if (!e.environment)
601
+ continue;
602
+ const injected = injectedEnvironment(next, e.driver);
603
+ for (const [k, v] of Object.entries(e.environment)) {
604
+ if (injected.get(k) === v)
605
+ delete e.environment[k];
606
+ }
607
+ if (!Object.keys(e.environment).length)
608
+ delete e.environment;
609
+ }
610
+ next.instances = map;
611
+ return { ok: true, config: next };
612
+ }
613
+ /** The credential env instanceConfigs() injects for one driver — shared with
614
+ * withInstanceCli() so the inject rule and the strip rule cannot drift apart.
615
+ * Each secret goes only to the driver that actually reads it: the API-key
616
+ * Grok driver reads XAI_API_KEY, the Computer driver reads BOX_TOKEN, and
617
+ * OpenCode reads OPENCODE_API_KEY. Every other engine brings its own
618
+ * login, so handing it a key it never uses would only put that key in the
619
+ * environment of an unrelated child process. */
620
+ function injectedEnvironment(cfg, driver) {
621
+ const environment = new Map();
622
+ if (driver === "grok" && cfg.xai?.key)
623
+ environment.set("XAI_API_KEY", cfg.xai.key);
624
+ if (driver === "openai-compat" && cfg.openaiCompat?.key)
625
+ environment.set("OPENAI_COMPAT_API_KEY", cfg.openaiCompat.key);
626
+ if (driver === "openai-compat" && cfg.openaiCompat?.url)
627
+ environment.set("OPENAI_COMPAT_URL", cfg.openaiCompat.url);
628
+ if (driver === "boxAgent" && cfg.box?.token)
629
+ environment.set("BOX_TOKEN", cfg.box.token);
630
+ if (driver === "opencodeGo" && cfg.opencodeGo?.apiKey)
631
+ environment.set("OPENCODE_API_KEY", cfg.opencodeGo.apiKey);
632
+ return environment;
633
+ }
634
+ // Default fleet: one instance per built-in driver (upstream
635
+ // defaultInstanceIdForDriver — instanceId defaults to the driver kind).
636
+ // Config-file keys are injected as per-instance environment so drivers
637
+ // see them without needing real process env vars — but only into the
638
+ // driver that consumes each key (injectedEnvironment above).
639
+ export function instanceConfigs(cfg) {
640
+ // The default `grok` instance rides the `grokAgent` driver, not the API-key
641
+ // one: like claude and codex it needs no credential from us, just the CLI
642
+ // installed and logged in (it shows up unavailable otherwise). The API-key
643
+ // `grok` driver stays registered but out of the default fleet — that key is
644
+ // a credential Milind doesn't want to manage; an `instances` entry brings
645
+ // it back anytime.
646
+ //
647
+ // Google rides `antigravityAgent` (the official Google ACP server), not
648
+ // `geminiAgent`:
649
+ // Google retired Gemini CLI for the free/Pro/Ultra tiers on 2026-06-18
650
+ // (developers.googleblog.com, "transitioning Gemini CLI to Antigravity
651
+ // CLI"), so a default `gemini` instance could only ever show unavailable.
652
+ // The driver stays registered for enterprise licences, which keep Gemini
653
+ // CLI — `{"instances": {"gemini": {"driver": "geminiAgent"}}}` restores it.
654
+ const DEFAULT_FLEET = {
655
+ grok: { driver: "grokAgent" },
656
+ kimi: { driver: "kimiAgent" },
657
+ droid: { driver: "droidAgent" },
658
+ cursor: { driver: "cursorAgent" },
659
+ claude: { driver: "claudeAgent" },
660
+ codex: { driver: "codex" },
661
+ antigravity: { driver: "antigravityAgent" },
662
+ opencodeGo: { driver: "opencodeGo" },
663
+ computer: { driver: "boxAgent" },
664
+ openaiCompat: { driver: "openai-compat" },
665
+ qwen: { driver: "qwenAgent" },
666
+ hermes: { driver: "hermesAgent" },
667
+ pi: { driver: "piAgent" },
668
+ };
669
+ const CUSTOM_ONLY = {
670
+ qwen: { driver: "qwenAgent" },
671
+ hermes: { driver: "hermesAgent" },
672
+ pi: { driver: "piAgent" },
673
+ };
674
+ // New default-fleet engines that existing product configs would otherwise
675
+ // never see. Custom-only engines stay in CUSTOM_ONLY so a one-off test map
676
+ // is not expanded, matching the claude/grok/codex product-fleet probe.
677
+ const PRODUCT_FLEET_ADDITIONS = {
678
+ cursor: { driver: "cursorAgent" },
679
+ openaiCompat: { driver: "openai-compat" },
680
+ ...CUSTOM_ONLY,
681
+ };
682
+ const configured = cfg.instances && Object.keys(cfg.instances).length ? cfg.instances : null;
683
+ const map = configured ? { ...configured } : { ...DEFAULT_FLEET };
684
+ // Product fleets pick up newly shipped engines. A one-off test/shadow map
685
+ // (no claude/grok/codex) is left exactly as written.
686
+ if (configured &&
687
+ (Object.hasOwn(configured, "claude") || Object.hasOwn(configured, "grok") || Object.hasOwn(configured, "codex"))) {
688
+ for (const [id, entry] of Object.entries(PRODUCT_FLEET_ADDITIONS)) {
689
+ if (!Object.hasOwn(map, id))
690
+ map[id] = { ...entry };
691
+ }
692
+ }
693
+ for (const [id, sourceEntry] of Object.entries(map)) {
694
+ // instanceConfigs() builds a transient runtime map. Never mutate the
695
+ // caller's persisted entries while injecting workspace defaults: doing so
696
+ // would turn the first workspace URL into a stale per-instance override.
697
+ const entry = { ...sourceEntry };
698
+ map[id] = entry;
699
+ const environment = { ...entry.environment };
700
+ for (const [key, value] of injectedEnvironment(cfg, entry.driver))
701
+ environment[key] = value;
702
+ entry.environment = environment;
703
+ // The driver URL is configuration, not a credential. Environment is
704
+ // intentionally not consulted by ProviderRegistry when it decodes a
705
+ // driver's config, so carry the workspace default into the transient
706
+ // instance map while preserving a per-instance override.
707
+ if (entry.driver === "openai-compat" && cfg.openaiCompat) {
708
+ const defaults = {};
709
+ if (cfg.openaiCompat.url)
710
+ defaults.url = cfg.openaiCompat.url;
711
+ if (cfg.openaiCompat.model)
712
+ defaults.model = cfg.openaiCompat.model;
713
+ if (cfg.openaiCompat.provider)
714
+ defaults.provider = cfg.openaiCompat.provider;
715
+ if (Object.keys(defaults).length) {
716
+ const raw = entry.config;
717
+ const current = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
718
+ const merged = { ...current };
719
+ // A per-instance value always wins over the workspace default.
720
+ for (const [k, v] of Object.entries(defaults)) {
721
+ if (typeof merged[k] !== "string" || !merged[k].trim())
722
+ merged[k] = v;
723
+ }
724
+ entry.config = merged;
725
+ }
726
+ }
727
+ }
728
+ return map;
729
+ }
730
+ const reportedMcpSkips = new Set();
731
+ function skipMcpEntry(name, why) {
732
+ const key = `${name}: ${why}`;
733
+ if (reportedMcpSkips.has(key))
734
+ return;
735
+ reportedMcpSkips.add(key);
736
+ console.error(`mcpServers.${JSON.stringify(name)} skipped — ${why}`);
737
+ }
738
+ /** The validated, normalized custom servers from config — or {}. */
739
+ export function customMcpServers(cfg) {
740
+ const out = {};
741
+ for (const [name, raw] of Object.entries(cfg.mcpServers ?? {})) {
742
+ if (raw && typeof raw === "object" && "url" in raw) {
743
+ skipMcpEntry(name, 'only stdio servers ("command") are supported so far — HTTP transports are a planned follow-up');
744
+ continue;
745
+ }
746
+ const parsed = parseStoredMcpServer(name, raw);
747
+ if (!parsed.ok) {
748
+ skipMcpEntry(name, `${parsed.error} Expected { "command": "npx", "args": [...], "env": { ... } }`);
749
+ continue;
750
+ }
751
+ if (!parsed.server.enabled)
752
+ continue;
753
+ out[name] = {
754
+ command: parsed.server.command,
755
+ args: parsed.server.args,
756
+ env: parsed.server.env,
757
+ };
758
+ }
759
+ return out;
760
+ }