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,1111 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { z } from "zod";
5
+ import { DATA_DIR } from "./config.js";
6
+ import { writeFileAtomic } from "./atomic.js";
7
+ import { redactSecretsInText } from "./redact.js";
8
+ const persistedSourceThreadId = z.string().trim().min(1).optional().catch(undefined);
9
+ function routineRequestOwnerKey(owner) {
10
+ return JSON.stringify([owner.requestId, owner.messageId, owner.botId, owner.threadId]);
11
+ }
12
+ const ALL_DAYS = [0, 1, 2, 3, 4, 5, 6];
13
+ const CATCH_UP_MS = 12 * 60 * 60_000;
14
+ const MAX_DATE_MS = 8_640_000_000_000_000;
15
+ const MAX_RUNS = 2_000;
16
+ const MAX_ATTACHMENTS = 50;
17
+ const attachmentSchema = z.object({
18
+ id: z.string().trim().min(1).max(200),
19
+ kind: z.enum(["file", "image"]),
20
+ name: z.string().trim().min(1).max(255),
21
+ path: z.string().trim().min(1).max(4_096),
22
+ size: z.number().finite().nonnegative(),
23
+ });
24
+ const ROUTINE_REQUEST_ACTIONS = new Set([
25
+ "create",
26
+ "update",
27
+ "pause",
28
+ "resume",
29
+ "run_now",
30
+ "delete",
31
+ ]);
32
+ function isRoutineRequestAction(value) {
33
+ return typeof value === "string" && ROUTINE_REQUEST_ACTIONS.has(value);
34
+ }
35
+ function cleanDays(days) {
36
+ if (!Array.isArray(days))
37
+ return ALL_DAYS;
38
+ const out = [...new Set(days.filter((d) => Number.isInteger(d) && d >= 0 && d <= 6))].sort();
39
+ return out.length ? out : ALL_DAYS;
40
+ }
41
+ function cleanAttachments(value) {
42
+ if (value == null)
43
+ return [];
44
+ if (!Array.isArray(value) || value.length > MAX_ATTACHMENTS) {
45
+ throw new Error(`Add no more than ${MAX_ATTACHMENTS} attachments`);
46
+ }
47
+ const ids = new Set();
48
+ return value.map((candidate) => {
49
+ const parsed = attachmentSchema.safeParse(candidate);
50
+ if (!parsed.success || parsed.data.name.includes("\0") || parsed.data.path.includes("\0")) {
51
+ throw new Error("Choose a valid attachment");
52
+ }
53
+ if (ids.has(parsed.data.id))
54
+ throw new Error("Each attachment must be unique");
55
+ ids.add(parsed.data.id);
56
+ return { ...parsed.data };
57
+ });
58
+ }
59
+ function cleanTimeoutMinutes(value) {
60
+ if (value == null)
61
+ return undefined;
62
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 5 || value > 240) {
63
+ throw new Error("Run limit must be a whole number from 5 to 240 minutes");
64
+ }
65
+ return value;
66
+ }
67
+ function loadTimeoutMinutes(value) {
68
+ try {
69
+ return cleanTimeoutMinutes(value);
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ /** A malformed legacy metadata field must not make the scheduler forget the
76
+ * otherwise valid routine or run that owns it. New writes still fail closed. */
77
+ function loadAttachments(value) {
78
+ try {
79
+ return cleanAttachments(value);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ }
85
+ function cloneSchedule(schedule) {
86
+ if (schedule.type === "once")
87
+ return { type: "once", at: schedule.at };
88
+ if (schedule.type === "interval") {
89
+ return { type: "interval", everyMinutes: schedule.everyMinutes, anchorAt: schedule.anchorAt };
90
+ }
91
+ return { type: "daily", time: schedule.time, weekdays: [...schedule.weekdays] };
92
+ }
93
+ function cloneAttachments(attachments) {
94
+ return attachments?.map((attachment) => ({ ...attachment })) ?? [];
95
+ }
96
+ function loadTarget(value) {
97
+ return value === "room-goal" ? "room-goal" : "bot";
98
+ }
99
+ const ROUTINE_GOAL_STATUSES = new Set([
100
+ "completed",
101
+ "needs-input",
102
+ "blocked",
103
+ "limit-reached",
104
+ "paused",
105
+ "stopped",
106
+ "failed",
107
+ ]);
108
+ function loadGoalStatus(value, target) {
109
+ return target === "room-goal" && typeof value === "string" && ROUTINE_GOAL_STATUSES.has(value)
110
+ ? value
111
+ : undefined;
112
+ }
113
+ function loadGroupId(value, target) {
114
+ if (target !== "room-goal" || typeof value !== "string")
115
+ return undefined;
116
+ return value.trim() || undefined;
117
+ }
118
+ function cloneRoutine(routine) {
119
+ return {
120
+ ...routine,
121
+ schedule: cloneSchedule(routine.schedule),
122
+ attachments: cloneAttachments(routine.attachments),
123
+ };
124
+ }
125
+ function cloneRun(run) {
126
+ return {
127
+ ...run,
128
+ attachments: cloneAttachments(run.attachments),
129
+ denials: run.denials ? [...run.denials] : undefined,
130
+ };
131
+ }
132
+ /** Keep untrusted local paths inside the same quoted tag shape used by chat. */
133
+ function escapeAttachmentPath(value) {
134
+ return value
135
+ .replaceAll("&", "&amp;")
136
+ .replaceAll('"', "&quot;")
137
+ .replaceAll("<", "&lt;")
138
+ .replaceAll(">", "&gt;")
139
+ .replaceAll("\t", "&#9;")
140
+ .replaceAll("\r", "&#13;")
141
+ .replaceAll("\n", "&#10;");
142
+ }
143
+ function composeExecutionPrompt(prompt, attachments) {
144
+ const parts = [prompt];
145
+ for (const attachment of attachments ?? []) {
146
+ const tag = attachment.kind === "image" ? "attached-image" : "attached-file";
147
+ parts.push(`<${tag} path="${escapeAttachmentPath(attachment.path)}" name="${escapeAttachmentPath(attachment.name)}" />`);
148
+ }
149
+ return parts.filter(Boolean).join("\n\n");
150
+ }
151
+ function cleanSchedule(schedule) {
152
+ if (schedule?.type === "once") {
153
+ const at = Number(schedule.at);
154
+ if (!Number.isFinite(at))
155
+ throw new Error("Choose a valid date and time");
156
+ return { type: "once", at };
157
+ }
158
+ if (schedule?.type === "daily") {
159
+ const time = String(schedule.time ?? "");
160
+ if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(time))
161
+ throw new Error("Time must use HH:MM");
162
+ return { type: "daily", time, weekdays: cleanDays(schedule.weekdays) };
163
+ }
164
+ if (schedule?.type === "interval") {
165
+ const { everyMinutes, anchorAt } = schedule;
166
+ if (typeof everyMinutes !== "number" || !Number.isInteger(everyMinutes) || everyMinutes < 5 || everyMinutes > 1_440) {
167
+ throw new Error("Interval must be a whole number from 5 to 1440 minutes");
168
+ }
169
+ if (typeof anchorAt !== "number" ||
170
+ !Number.isSafeInteger(anchorAt) ||
171
+ anchorAt < 0 ||
172
+ anchorAt > MAX_DATE_MS) {
173
+ throw new Error("Choose a valid interval start time");
174
+ }
175
+ return { type: "interval", everyMinutes, anchorAt };
176
+ }
177
+ throw new Error("Choose a supported schedule");
178
+ }
179
+ function loadSchedule(value) {
180
+ try {
181
+ return cleanSchedule(value);
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ /** Next wall-clock occurrence in this computer's timezone, strictly after `after`. */
188
+ export function nextOccurrence(schedule, after) {
189
+ if (schedule.type === "once")
190
+ return schedule.at > after ? schedule.at : null;
191
+ if (schedule.type === "interval") {
192
+ if (schedule.anchorAt > after)
193
+ return schedule.anchorAt;
194
+ const intervalMs = schedule.everyMinutes * 60_000;
195
+ const intervalsElapsed = Math.floor((after - schedule.anchorAt) / intervalMs);
196
+ const candidate = schedule.anchorAt + (intervalsElapsed + 1) * intervalMs;
197
+ return Number.isSafeInteger(candidate) && candidate <= MAX_DATE_MS ? candidate : null;
198
+ }
199
+ const [hour, minute] = schedule.time.split(":").map(Number);
200
+ const weekdays = new Set(cleanDays(schedule.weekdays));
201
+ for (let offset = 0; offset <= 8; offset++) {
202
+ const d = new Date(after);
203
+ d.setDate(d.getDate() + offset);
204
+ d.setHours(hour, minute, 0, 0);
205
+ if (d.getTime() > after && weekdays.has(d.getDay()))
206
+ return d.getTime();
207
+ }
208
+ return null;
209
+ }
210
+ function latestIntervalOccurrence(schedule, at) {
211
+ if (schedule.anchorAt > at)
212
+ return null;
213
+ const intervalMs = schedule.everyMinutes * 60_000;
214
+ return schedule.anchorAt + Math.floor((at - schedule.anchorAt) / intervalMs) * intervalMs;
215
+ }
216
+ function sanitizeInput(input) {
217
+ const name = String(input.name ?? "").trim().slice(0, 80);
218
+ const prompt = String(input.prompt ?? "").trim().slice(0, 20_000);
219
+ const botId = String(input.botId ?? "").trim();
220
+ if (!name)
221
+ throw new Error("Give the routine a name");
222
+ if (!prompt)
223
+ throw new Error("Tell the bot what to do");
224
+ if (!botId)
225
+ throw new Error("Choose a bot");
226
+ const target = input.target ?? "bot";
227
+ if (target !== "bot" && target !== "room-goal")
228
+ throw new Error("Choose a valid routine target");
229
+ const groupId = typeof input.groupId === "string" ? input.groupId.trim() : "";
230
+ if (target === "room-goal" && !groupId)
231
+ throw new Error("Choose a room for this goal");
232
+ const runOn = input.runOn ?? "maus";
233
+ if (runOn !== "maus" && runOn !== "cloud")
234
+ throw new Error("Choose where this routine runs");
235
+ const attachments = cleanAttachments(input.attachments);
236
+ const timeoutMinutes = cleanTimeoutMinutes(input.timeoutMinutes);
237
+ if (target === "room-goal" && runOn === "cloud") {
238
+ throw new Error("Room goals can only run on this computer");
239
+ }
240
+ if (target === "room-goal" && attachments.length > 0) {
241
+ throw new Error("Room goals do not support attachments yet");
242
+ }
243
+ if (runOn === "cloud" && attachments.length > 0) {
244
+ throw new Error("Attachments can only run on this computer until cloud file staging is available");
245
+ }
246
+ return {
247
+ name,
248
+ prompt,
249
+ target,
250
+ botId,
251
+ groupId: target === "room-goal" ? groupId : undefined,
252
+ runOn,
253
+ enabled: input.enabled !== false,
254
+ schedule: cleanSchedule(input.schedule),
255
+ durationMinutes: Math.min(240, Math.max(5, Math.round(Number(input.durationMinutes) || 30))),
256
+ ...(timeoutMinutes === undefined ? {} : { timeoutMinutes }),
257
+ attachments,
258
+ };
259
+ }
260
+ export class RoutineManager {
261
+ file;
262
+ now;
263
+ options;
264
+ routines = [];
265
+ runs = [];
266
+ routineRequestReceipts = [];
267
+ timer = null;
268
+ ticking = false;
269
+ constructor(options) {
270
+ this.options = options;
271
+ this.file = options.file ?? join(DATA_DIR, "routines.json");
272
+ this.now = options.now ?? Date.now;
273
+ try {
274
+ const disk = JSON.parse(readFileSync(this.file, "utf8"));
275
+ this.routines = Array.isArray(disk.routines)
276
+ ? disk.routines.flatMap((routine) => {
277
+ const schedule = loadSchedule(routine.schedule);
278
+ if (!schedule)
279
+ return [];
280
+ const target = loadTarget(routine.target);
281
+ const loaded = {
282
+ ...routine,
283
+ schedule,
284
+ target,
285
+ groupId: loadGroupId(routine.groupId, target),
286
+ runOn: routine.runOn ?? "maus",
287
+ timeoutMinutes: loadTimeoutMinutes(routine.timeoutMinutes),
288
+ attachments: loadAttachments(routine.attachments),
289
+ sourceThreadId: persistedSourceThreadId.parse(routine.sourceThreadId),
290
+ };
291
+ if (loaded.timeoutMinutes === undefined)
292
+ delete loaded.timeoutMinutes;
293
+ return [loaded];
294
+ })
295
+ : [];
296
+ this.runs = Array.isArray(disk.runs)
297
+ ? disk.runs.map((run) => {
298
+ const target = loadTarget(run.target);
299
+ const loaded = {
300
+ ...run,
301
+ target,
302
+ goalStatus: loadGoalStatus(run.goalStatus, target),
303
+ groupId: loadGroupId(run.groupId, target),
304
+ runOn: run.runOn ?? "maus",
305
+ timeoutMinutes: loadTimeoutMinutes(run.timeoutMinutes),
306
+ attachments: loadAttachments(run.attachments),
307
+ sourceThreadId: persistedSourceThreadId.parse(run.sourceThreadId),
308
+ };
309
+ if (loaded.timeoutMinutes === undefined)
310
+ delete loaded.timeoutMinutes;
311
+ return loaded;
312
+ })
313
+ : [];
314
+ this.routineRequestReceipts = Array.isArray(disk.routineRequestReceipts)
315
+ ? disk.routineRequestReceipts.filter((receipt) => typeof receipt?.requestId === "string" &&
316
+ typeof receipt?.messageId === "string" &&
317
+ typeof receipt?.botId === "string" &&
318
+ typeof receipt?.threadId === "string" &&
319
+ isRoutineRequestAction(receipt?.action) &&
320
+ receipt?.fingerprintVersion === 1 &&
321
+ typeof receipt?.fingerprint === "string" && /^[a-f0-9]{64}$/.test(receipt.fingerprint) &&
322
+ typeof receipt?.resultId === "string" &&
323
+ Number.isFinite(receipt?.appliedAt))
324
+ : [];
325
+ }
326
+ catch {
327
+ this.routines = [];
328
+ this.runs = [];
329
+ this.routineRequestReceipts = [];
330
+ }
331
+ // A local process cannot still own these turns after a full restart.
332
+ const recovered = [];
333
+ for (const run of this.runs) {
334
+ if (run.status === "running" || run.status === "waiting") {
335
+ run.status = "failed";
336
+ if (run.target === "room-goal")
337
+ run.goalStatus = "failed";
338
+ run.error = "OpenMausBot restarted while this routine was running";
339
+ run.attention = undefined;
340
+ run.finishedAt = this.now();
341
+ recovered.push(cloneRun(run));
342
+ }
343
+ }
344
+ if (recovered.length > 0) {
345
+ this.save();
346
+ for (const run of recovered) {
347
+ this.notifyRunChanged(run);
348
+ this.options.onRunFailed?.(run);
349
+ }
350
+ }
351
+ }
352
+ listRoutines() {
353
+ return this.routines.map(cloneRoutine);
354
+ }
355
+ listRuns(from, to) {
356
+ return this.runs
357
+ .filter((r) => (from == null || r.scheduledFor >= from) && (to == null || r.scheduledFor <= to))
358
+ .sort((a, b) => b.scheduledFor - a.scheduledFor)
359
+ .map(cloneRun);
360
+ }
361
+ activeRunForBot(botId) {
362
+ const run = this.runs.find((candidate) => candidate.botId === botId && ["running", "waiting"].includes(candidate.status));
363
+ return run ? cloneRun(run) : null;
364
+ }
365
+ /** Active work that owns the bot's direct conversation. Room goals may use
366
+ * the same bot as their coordinator, but execute in a separate room task. */
367
+ activeBotRunForBot(botId) {
368
+ const run = this.runs.find((candidate) => candidate.target === "bot" &&
369
+ candidate.botId === botId &&
370
+ ["running", "waiting"].includes(candidate.status));
371
+ return run ? cloneRun(run) : null;
372
+ }
373
+ routineRequestReceipt(requestId) {
374
+ const receipt = this.routineRequestReceipts.find((candidate) => candidate.requestId === requestId);
375
+ return receipt ? { ...receipt } : null;
376
+ }
377
+ /** Small startup index used to locate only transcripts that may need
378
+ * cross-file commit recovery. Most launches have no receipts and therefore
379
+ * do not read or cache any transcript for this feature. */
380
+ routineRequestReceiptOwners() {
381
+ return this.routineRequestReceipts.map(({ requestId, messageId, botId, threadId }) => ({
382
+ requestId,
383
+ messageId,
384
+ botId,
385
+ threadId,
386
+ }));
387
+ }
388
+ /** Once the transcript card is durably settled, its scheduler receipt is
389
+ * redundant. Unsettled receipts are intentionally never count-evicted: an
390
+ * actionable card may survive indefinitely and must retain its exact-once
391
+ * recovery record for the same lifetime. */
392
+ forgetRoutineRequestReceipt(request) {
393
+ const receipt = this.matchingRoutineRequestReceipt(request);
394
+ if (!receipt)
395
+ return false;
396
+ const index = this.routineRequestReceipts.indexOf(receipt);
397
+ this.commitMutation(() => {
398
+ this.routineRequestReceipts.splice(index, 1);
399
+ });
400
+ return true;
401
+ }
402
+ forgetRoutineRequestReceiptsForThread(threadId) {
403
+ const kept = this.routineRequestReceipts.filter((receipt) => receipt.threadId !== threadId);
404
+ const removed = this.routineRequestReceipts.length - kept.length;
405
+ if (removed === 0)
406
+ return 0;
407
+ this.commitMutation(() => {
408
+ this.routineRequestReceipts = kept;
409
+ });
410
+ return removed;
411
+ }
412
+ /** Drop only receipts whose confirmation transcript no longer exists.
413
+ * Reachable open cards retain exact-once recovery for their full lifetime. */
414
+ reconcileRoutineRequestReceipts(reachable) {
415
+ const keys = new Set(reachable.map(routineRequestOwnerKey));
416
+ const kept = this.routineRequestReceipts.filter((receipt) => keys.has(routineRequestOwnerKey(receipt)));
417
+ const removed = this.routineRequestReceipts.length - kept.length;
418
+ if (removed === 0)
419
+ return 0;
420
+ this.commitMutation(() => {
421
+ this.routineRequestReceipts = kept;
422
+ });
423
+ return removed;
424
+ }
425
+ isActiveThread(threadId) {
426
+ return this.runs.some((run) => run.threadId === threadId && ["running", "waiting"].includes(run.status));
427
+ }
428
+ create(input, request) {
429
+ if (request) {
430
+ const receipt = this.matchingRoutineRequestReceipt(request);
431
+ if (receipt) {
432
+ const committed = this.routines.find((routine) => routine.id === receipt.resultId);
433
+ if (committed)
434
+ return cloneRoutine(committed);
435
+ throw new Error("This routine request was already applied");
436
+ }
437
+ }
438
+ const clean = sanitizeInput(input);
439
+ if (this.targetState(clean) === "missing")
440
+ throw new Error(this.missingTargetMessage(clean.target));
441
+ const at = this.now();
442
+ const routine = {
443
+ id: randomUUID(),
444
+ ...clean,
445
+ // Only a confirmed chat card supplies `request`; the public calendar
446
+ // API cannot choose an arbitrary transcript as a reporting target.
447
+ sourceThreadId: request?.threadId,
448
+ nextRunAt: clean.enabled ? this.initialOccurrence(clean.schedule, at) : null,
449
+ createdAt: at,
450
+ updatedAt: at,
451
+ };
452
+ this.commitMutation(() => {
453
+ this.routines.unshift(routine);
454
+ if (request)
455
+ this.rememberRoutineRequest(request, routine.id, at);
456
+ });
457
+ this.emitRoutine(routine);
458
+ return cloneRoutine(routine);
459
+ }
460
+ update(id, patch, request) {
461
+ if (request) {
462
+ const receipt = this.matchingRoutineRequestReceipt(request);
463
+ if (receipt) {
464
+ const committed = this.routines.find((routine) => routine.id === receipt.resultId);
465
+ return committed ? cloneRoutine(committed) : null;
466
+ }
467
+ }
468
+ const routine = this.routines.find((r) => r.id === id);
469
+ if (!routine)
470
+ return null;
471
+ const now = this.now();
472
+ const clean = sanitizeInput({
473
+ name: patch.name ?? routine.name,
474
+ prompt: patch.prompt ?? routine.prompt,
475
+ target: patch.target ?? routine.target,
476
+ botId: patch.botId ?? routine.botId,
477
+ groupId: Object.hasOwn(patch, "groupId") ? patch.groupId : routine.groupId,
478
+ runOn: patch.runOn ?? routine.runOn,
479
+ enabled: patch.enabled ?? routine.enabled,
480
+ schedule: patch.schedule ?? routine.schedule,
481
+ durationMinutes: patch.durationMinutes ?? routine.durationMinutes,
482
+ timeoutMinutes: Object.hasOwn(patch, "timeoutMinutes") ? patch.timeoutMinutes : routine.timeoutMinutes,
483
+ attachments: patch.attachments ?? routine.attachments,
484
+ });
485
+ if (this.targetState(clean) === "missing")
486
+ throw new Error(this.missingTargetMessage(clean.target));
487
+ const cancelledRuns = [];
488
+ this.commitMutation(() => {
489
+ Object.assign(routine, clean, {
490
+ nextRunAt: clean.enabled ? this.initialOccurrence(clean.schedule, now) : null,
491
+ // `updatedAt` doubles as the optimistic revision on durable routine
492
+ // confirmation cards. Keep it monotonic even for two writes in one ms.
493
+ updatedAt: Math.max(now, routine.updatedAt + 1),
494
+ });
495
+ if (Object.hasOwn(patch, "timeoutMinutes") && patch.timeoutMinutes == null) {
496
+ delete routine.timeoutMinutes;
497
+ }
498
+ if (patch.enabled === false) {
499
+ for (const run of this.runs) {
500
+ if (run.routineId !== routine.id || run.status !== "queued")
501
+ continue;
502
+ run.status = "cancelled";
503
+ run.attention = undefined;
504
+ run.finishedAt = this.now();
505
+ run.error = "The routine was paused before this run started";
506
+ cancelledRuns.push(run);
507
+ }
508
+ }
509
+ if (request)
510
+ this.rememberRoutineRequest(request, routine.id, now);
511
+ });
512
+ for (const run of cancelledRuns)
513
+ this.emitRun(run);
514
+ this.emitRoutine(routine);
515
+ return cloneRoutine(routine);
516
+ }
517
+ remove(id, request) {
518
+ if (request) {
519
+ const receipt = this.matchingRoutineRequestReceipt(request);
520
+ if (receipt) {
521
+ return true;
522
+ }
523
+ }
524
+ const at = this.routines.findIndex((r) => r.id === id);
525
+ if (at === -1)
526
+ return false;
527
+ const cancelledRuns = [];
528
+ this.commitMutation(() => {
529
+ this.routines.splice(at, 1);
530
+ for (const run of this.runs) {
531
+ if (run.routineId !== id || run.status !== "queued")
532
+ continue;
533
+ run.status = "cancelled";
534
+ run.attention = undefined;
535
+ run.finishedAt = this.now();
536
+ cancelledRuns.push(run);
537
+ }
538
+ if (request)
539
+ this.rememberRoutineRequest(request, id, this.now());
540
+ });
541
+ for (const run of cancelledRuns)
542
+ this.emitRun(run);
543
+ this.options.emit?.({ kind: "routine.deleted", routineId: id });
544
+ return true;
545
+ }
546
+ disableForBot(botId) {
547
+ let changed = false;
548
+ for (const routine of this.routines) {
549
+ if (routine.botId !== botId || !routine.enabled)
550
+ continue;
551
+ routine.enabled = false;
552
+ routine.nextRunAt = null;
553
+ routine.updatedAt = Math.max(this.now(), routine.updatedAt + 1);
554
+ this.emitRoutine(routine);
555
+ changed = true;
556
+ }
557
+ for (const run of this.runs) {
558
+ if (run.botId !== botId || !["queued", "running", "waiting"].includes(run.status))
559
+ continue;
560
+ run.status = "cancelled";
561
+ if (run.target === "room-goal")
562
+ run.goalStatus = "stopped";
563
+ run.attention = undefined;
564
+ run.finishedAt = this.now();
565
+ run.error = "The assigned bot was deleted";
566
+ this.emitRun(run);
567
+ if (run.threadId) {
568
+ if (run.target === "room-goal" && run.groupId) {
569
+ void this.options.interruptGoal?.(run.groupId, run.threadId).catch(() => { });
570
+ }
571
+ else {
572
+ void this.options.interruptTurn?.(run.botId, run.threadId, run.runOn ?? "maus").catch(() => { });
573
+ }
574
+ }
575
+ changed = true;
576
+ }
577
+ if (changed)
578
+ this.save();
579
+ }
580
+ disableForGroup(groupId) {
581
+ let changed = false;
582
+ for (const routine of this.routines) {
583
+ if (routine.target !== "room-goal" || routine.groupId !== groupId || !routine.enabled)
584
+ continue;
585
+ routine.enabled = false;
586
+ routine.nextRunAt = null;
587
+ routine.updatedAt = Math.max(this.now(), routine.updatedAt + 1);
588
+ this.emitRoutine(routine);
589
+ changed = true;
590
+ }
591
+ for (const run of this.runs) {
592
+ if (run.target !== "room-goal" ||
593
+ run.groupId !== groupId ||
594
+ !["queued", "running", "waiting"].includes(run.status))
595
+ continue;
596
+ run.status = "cancelled";
597
+ run.goalStatus = "stopped";
598
+ run.attention = undefined;
599
+ run.finishedAt = this.now();
600
+ run.error = "The assigned room was deleted";
601
+ this.emitRun(run);
602
+ if (run.threadId) {
603
+ void this.options.interruptGoal?.(groupId, run.threadId).catch(() => { });
604
+ }
605
+ changed = true;
606
+ }
607
+ if (changed)
608
+ this.save();
609
+ }
610
+ runNow(id, request) {
611
+ if (request) {
612
+ const receipt = this.matchingRoutineRequestReceipt(request);
613
+ if (receipt) {
614
+ const committed = this.runs.find((run) => run.id === receipt.resultId);
615
+ return committed ? cloneRun(committed) : null;
616
+ }
617
+ }
618
+ const routine = this.routines.find((r) => r.id === id);
619
+ if (!routine)
620
+ return null;
621
+ let run;
622
+ this.commitMutation(() => {
623
+ run = this.newRun(routine, this.now(), true);
624
+ // A chat-confirmed "run now" reports back to the conversation that
625
+ // invoked this one run. It must not silently rebind future schedules.
626
+ if (request)
627
+ run.sourceThreadId = request.threadId;
628
+ if (request)
629
+ this.rememberRoutineRequest(request, run.id, this.now());
630
+ });
631
+ this.emitRun(run);
632
+ queueMicrotask(() => void this.tick());
633
+ return cloneRun(run);
634
+ }
635
+ /** Queue an event-driven job without inventing a calendar schedule. Webhook
636
+ * definitions live in their own store; the execution receipt deliberately
637
+ * reuses this manager so busy-bot ordering, task creation and VM routing stay
638
+ * identical for every unattended job. */
639
+ enqueueWebhook(input) {
640
+ if (this.options.botState(input.botId) === "missing") {
641
+ throw Object.assign(new Error("The assigned MAUS no longer exists"), { status: 410 });
642
+ }
643
+ const run = {
644
+ id: randomUUID(),
645
+ routineId: input.webhookId,
646
+ routineName: input.webhookName,
647
+ prompt: input.prompt,
648
+ target: "bot",
649
+ botId: input.botId,
650
+ runOn: input.runOn,
651
+ scheduledFor: input.receivedAt,
652
+ status: "queued",
653
+ manual: false,
654
+ triggerSource: "webhook",
655
+ webhookId: input.webhookId,
656
+ deliveryId: input.deliveryId,
657
+ attachments: [],
658
+ createdAt: this.now(),
659
+ };
660
+ this.runs.push(run);
661
+ this.save();
662
+ this.emitRun(run);
663
+ queueMicrotask(() => void this.tick());
664
+ return cloneRun(run);
665
+ }
666
+ activeWebhookRunCount(webhookId) {
667
+ return this.runs.filter((run) => run.webhookId === webhookId && ["queued", "running", "waiting"].includes(run.status)).length;
668
+ }
669
+ cancelQueuedWebhook(webhookId, message) {
670
+ let changed = false;
671
+ for (const run of this.runs) {
672
+ if (run.webhookId !== webhookId || run.status !== "queued")
673
+ continue;
674
+ run.status = "cancelled";
675
+ run.attention = undefined;
676
+ run.finishedAt = this.now();
677
+ run.error = message.slice(0, 500);
678
+ this.emitRun(run);
679
+ changed = true;
680
+ }
681
+ if (changed)
682
+ this.save();
683
+ }
684
+ async cancelRun(id) {
685
+ const run = this.runs.find((r) => r.id === id);
686
+ if (!run || !["queued", "running", "waiting"].includes(run.status))
687
+ return null;
688
+ run.status = "cancelled";
689
+ if (run.target === "room-goal")
690
+ run.goalStatus = "stopped";
691
+ run.attention = undefined;
692
+ run.finishedAt = this.now();
693
+ this.save();
694
+ this.emitRun(run);
695
+ if (run.threadId) {
696
+ if (run.target === "room-goal" && run.groupId) {
697
+ await this.options.interruptGoal?.(run.groupId, run.threadId).catch(() => { });
698
+ }
699
+ else {
700
+ await this.options.interruptTurn?.(run.botId, run.threadId, run.runOn ?? "maus").catch(() => { });
701
+ }
702
+ }
703
+ queueMicrotask(() => void this.tick());
704
+ return cloneRun(run);
705
+ }
706
+ markSeen(id) {
707
+ const run = this.runs.find((r) => r.id === id);
708
+ if (!run)
709
+ return null;
710
+ if (!run.seenAt) {
711
+ run.seenAt = this.now();
712
+ this.save();
713
+ this.emitRun(run);
714
+ }
715
+ return cloneRun(run);
716
+ }
717
+ start() {
718
+ if (this.timer)
719
+ return;
720
+ void this.tick();
721
+ this.timer = setInterval(() => void this.tick(), 10_000);
722
+ this.timer.unref?.();
723
+ }
724
+ stop() {
725
+ if (this.timer)
726
+ clearInterval(this.timer);
727
+ this.timer = null;
728
+ }
729
+ async tick() {
730
+ if (this.ticking)
731
+ return;
732
+ this.ticking = true;
733
+ try {
734
+ const now = this.now();
735
+ for (const run of this.runs) {
736
+ if (!["running", "waiting"].includes(run.status) ||
737
+ run.startedAt == null ||
738
+ run.timeoutMinutes == null ||
739
+ now - run.startedAt < run.timeoutMinutes * 60_000)
740
+ continue;
741
+ const threadId = run.threadId;
742
+ const detail = `Stopped after reaching the ${run.timeoutMinutes}-minute run limit`;
743
+ if (run.target === "room-goal")
744
+ run.goalStatus = "limit-reached";
745
+ this.failRun(run, detail);
746
+ if (!threadId)
747
+ continue;
748
+ if (run.target === "room-goal" && run.groupId) {
749
+ await this.options.interruptGoal?.(run.groupId, threadId, {
750
+ status: "limit-reached",
751
+ detail,
752
+ }).catch(() => { });
753
+ }
754
+ else {
755
+ await this.options.interruptTurn?.(run.botId, threadId, run.runOn ?? "maus").catch(() => { });
756
+ }
757
+ }
758
+ let changed = false;
759
+ const missedRuns = [];
760
+ for (const routine of this.routines) {
761
+ if (!routine.enabled || routine.nextRunAt == null || routine.nextRunAt > now)
762
+ continue;
763
+ const pendingAt = routine.nextRunAt;
764
+ const late = now - pendingAt;
765
+ const scheduledFor = routine.schedule.type === "interval" && late <= CATCH_UP_MS
766
+ ? latestIntervalOccurrence(routine.schedule, now) ?? pendingAt
767
+ : pendingAt;
768
+ // One slow interval run must not build an unbounded queue of stale
769
+ // copies behind it. The series still advances on its original phase.
770
+ const overlapping = routine.schedule.type === "interval" && this.runs.some((run) => run.routineId === routine.id && ["queued", "running", "waiting"].includes(run.status));
771
+ if (!overlapping) {
772
+ if (late > CATCH_UP_MS) {
773
+ const missed = this.newRun(routine, scheduledFor, false);
774
+ missed.status = "missed";
775
+ missed.finishedAt = now;
776
+ missed.error = "This computer was offline for more than 12 hours after the scheduled time";
777
+ this.emitRun(missed);
778
+ missedRuns.push(cloneRun(missed));
779
+ }
780
+ else {
781
+ const run = this.newRun(routine, scheduledFor, false);
782
+ this.emitRun(run);
783
+ }
784
+ }
785
+ routine.nextRunAt =
786
+ routine.schedule.type === "once" ? null : nextOccurrence(routine.schedule, Math.max(now, scheduledFor));
787
+ // `updatedAt` is the optimistic definition revision carried by
788
+ // routine confirmation cards. Moving the scheduler cursor is runtime
789
+ // progress, not a definition edit, so recurring ticks must not make a
790
+ // still-accurate pending confirmation stale. A one-time routine does
791
+ // mutate its definition by auto-disabling after its occurrence.
792
+ if (routine.schedule.type === "once") {
793
+ routine.enabled = false;
794
+ routine.updatedAt = Math.max(now, routine.updatedAt + 1);
795
+ }
796
+ this.emitRoutine(routine);
797
+ changed = true;
798
+ }
799
+ if (changed)
800
+ this.save();
801
+ for (const missed of missedRuns)
802
+ this.options.onRunFailed?.(missed);
803
+ for (const run of [...this.runs].reverse()) {
804
+ if (run.status !== "queued")
805
+ continue;
806
+ // A queued interval represents the latest useful check, not a backlog
807
+ // item. If the bot stayed busy across later occurrences, align this
808
+ // scheduled receipt to the newest due point immediately before it can
809
+ // dispatch. Manual runs and webhook deliveries retain their exact
810
+ // requested/received timestamps.
811
+ const triggerSource = run.triggerSource ?? (run.manual ? "manual" : "schedule");
812
+ const definition = triggerSource === "schedule"
813
+ ? this.routines.find((routine) => routine.id === run.routineId)
814
+ : undefined;
815
+ if (definition?.schedule.type === "interval") {
816
+ const latest = latestIntervalOccurrence(definition.schedule, now);
817
+ if (latest !== null && latest > run.scheduledFor) {
818
+ run.scheduledFor = latest;
819
+ this.save();
820
+ this.emitRun(run);
821
+ }
822
+ }
823
+ const state = this.targetState(run);
824
+ if (state === "busy")
825
+ continue;
826
+ if (state === "missing") {
827
+ this.failRun(run, this.missingTargetMessage(run.target));
828
+ continue;
829
+ }
830
+ // A webhook is an incoming message, so make its task the bot's live
831
+ // chat immediately. Scheduled work remains detached and unobtrusive.
832
+ const task = run.target === "room-goal"
833
+ ? run.groupId
834
+ ? this.options.createGoalTask?.(run.groupId, run.routineName) ?? null
835
+ : null
836
+ : this.options.createTask(run.botId, run.routineName, run.triggerSource === "webhook");
837
+ if (!task) {
838
+ this.failRun(run, run.target === "room-goal"
839
+ ? "Could not create a room task for this goal"
840
+ : "Could not create a task for this run");
841
+ continue;
842
+ }
843
+ run.threadId = task.threadId;
844
+ run.startedAt = this.now();
845
+ run.status = "running";
846
+ this.save();
847
+ this.emitRun(run);
848
+ try {
849
+ const prompt = run.prompt ?? this.routines.find((r) => r.id === run.routineId)?.prompt;
850
+ if (!prompt) {
851
+ this.failThread(task.threadId, "The routine was deleted before it could start");
852
+ continue;
853
+ }
854
+ const triggerSource = run.triggerSource ?? (run.manual ? "manual" : "schedule");
855
+ if (run.target === "room-goal") {
856
+ if (!run.groupId || !this.options.startGoal) {
857
+ this.failThread(task.threadId, "Room goal routines are unavailable");
858
+ continue;
859
+ }
860
+ await this.options.startGoal(run.groupId, task.threadId, prompt, run.botId, run.id, (message) => this.failThread(task.threadId, message));
861
+ }
862
+ else {
863
+ await this.options.startTurn(run.botId, task.threadId, composeExecutionPrompt(prompt, run.attachments), run.runOn ?? "maus", triggerSource, (message) => this.failThread(task.threadId, message));
864
+ }
865
+ }
866
+ catch (error) {
867
+ this.failThread(task.threadId, error instanceof Error ? error.message : String(error));
868
+ }
869
+ }
870
+ }
871
+ finally {
872
+ this.ticking = false;
873
+ }
874
+ }
875
+ handleRuntimeEvent(event) {
876
+ const run = this.runs.find((r) => r.threadId === event.threadId && ["running", "waiting"].includes(r.status));
877
+ if (!run)
878
+ return null;
879
+ // A room goal contains several provider turns. Its orchestrator owns the
880
+ // terminal decision and reports it through finishGoalRun; one member's
881
+ // completion and private coordinator envelope are only intermediate
882
+ // protocol, never the routine receipt's result.
883
+ if (run.target === "room-goal" &&
884
+ (event.type === "turn.completed" || (event.type === "item.completed" && event.itemType === "assistant_text")))
885
+ return null;
886
+ if (event.type === "request.opened") {
887
+ run.status = "waiting";
888
+ run.attention = redactSecretsInText(event.summary).trim().slice(0, 500) || undefined;
889
+ }
890
+ else if (event.type === "request.resolved") {
891
+ run.status = "running";
892
+ run.attention = undefined;
893
+ }
894
+ else if (event.type === "item.completed" && event.itemType === "assistant_text") {
895
+ run.output = redactSecretsInText(event.text).trim().slice(0, 2_000);
896
+ }
897
+ else if (event.type === "runtime.error") {
898
+ run.error = redactSecretsInText(event.message).slice(0, 500);
899
+ }
900
+ else if (event.type === "turn.retrying") {
901
+ // the driver will relaunch this same run; a transient blip is not a
902
+ // receipt-worthy failure, so keep the run running and stay quiet
903
+ return null;
904
+ }
905
+ else if (event.type === "turn.completed") {
906
+ run.cost = event.cost;
907
+ run.denials = event.denials;
908
+ if (!event.ok) {
909
+ this.failRun(run, event.stopReason ?? run.error ?? "The bot did not complete this run");
910
+ queueMicrotask(() => void this.tick());
911
+ return cloneRun(run);
912
+ }
913
+ run.status = "completed";
914
+ run.attention = undefined;
915
+ run.finishedAt = this.now();
916
+ run.error = undefined;
917
+ }
918
+ else {
919
+ return null;
920
+ }
921
+ this.save();
922
+ this.emitRun(run);
923
+ if (event.type === "turn.completed")
924
+ queueMicrotask(() => void this.tick());
925
+ return cloneRun(run);
926
+ }
927
+ failThread(threadId, message) {
928
+ const run = this.runs.find((r) => r.threadId === threadId && ["running", "waiting"].includes(r.status));
929
+ if (!run)
930
+ return;
931
+ this.failRun(run, message);
932
+ queueMicrotask(() => void this.tick());
933
+ }
934
+ finishGoalRun(runId, status, detail) {
935
+ const run = this.runs.find((candidate) => candidate.id === runId &&
936
+ candidate.target === "room-goal" &&
937
+ ["running", "waiting"].includes(candidate.status));
938
+ if (!run || status === "working")
939
+ return null;
940
+ const safeDetail = redactSecretsInText(detail).trim();
941
+ run.goalStatus = status;
942
+ // Only a completed goal is a completed run. A team asking the human a
943
+ // question is still waiting on them, and a blocked or turn-capped goal
944
+ // did not finish — reporting either as "completed" would silence the
945
+ // one outcome that most needs a person's attention.
946
+ if (status === "failed" || status === "blocked" || status === "limit-reached") {
947
+ this.failRun(run, safeDetail ||
948
+ (status === "limit-reached" ? "The room goal reached its turn limit" : "The room goal is blocked"));
949
+ }
950
+ else if (status === "needs-input" || status === "paused") {
951
+ run.status = "waiting";
952
+ run.attention = safeDetail.slice(0, 500) || (status === "paused" ? "The room goal is paused" : "The team needs your input");
953
+ run.error = undefined;
954
+ this.save();
955
+ this.emitRun(run);
956
+ }
957
+ else {
958
+ run.status = status === "stopped" ? "cancelled" : "completed";
959
+ run.attention = undefined;
960
+ run.finishedAt = this.now();
961
+ run.error = undefined;
962
+ if (status !== "stopped")
963
+ run.output = safeDetail.slice(0, 2_000) || undefined;
964
+ this.save();
965
+ this.emitRun(run);
966
+ }
967
+ queueMicrotask(() => void this.tick());
968
+ return cloneRun(run);
969
+ }
970
+ failRun(run, message) {
971
+ run.status = "failed";
972
+ run.attention = undefined;
973
+ run.error = redactSecretsInText(message).slice(0, 500);
974
+ run.finishedAt = this.now();
975
+ this.save();
976
+ this.emitRun(run);
977
+ this.options.onRunFailed?.(cloneRun(run));
978
+ }
979
+ targetState(target) {
980
+ if (target.target === "room-goal") {
981
+ if (!target.groupId || !this.options.goalState)
982
+ return "missing";
983
+ return this.options.goalState(target.groupId, target.botId);
984
+ }
985
+ return this.options.botState(target.botId);
986
+ }
987
+ missingTargetMessage(target) {
988
+ return target === "room-goal"
989
+ ? "The assigned room or coordinator no longer exists"
990
+ : "The assigned bot no longer exists";
991
+ }
992
+ initialOccurrence(schedule, now) {
993
+ // Return the original time, not max(at, now): tick() already decides
994
+ // whether a stale "once" run fires or is recorded as "missed" based on
995
+ // how far past the scheduled time it is. Clamping to now here hides the
996
+ // original schedule from the run receipt (scheduledFor would read "now"
997
+ // instead of the time the user chose) and prevents the 12-hour missed
998
+ // threshold from ever triggering for a "once" routine created late.
999
+ if (schedule.type === "once")
1000
+ return schedule.at;
1001
+ return nextOccurrence(schedule, now);
1002
+ }
1003
+ newRun(routine, scheduledFor, manual) {
1004
+ const run = {
1005
+ id: randomUUID(),
1006
+ routineId: routine.id,
1007
+ routineName: routine.name,
1008
+ prompt: routine.prompt,
1009
+ durationMinutes: routine.durationMinutes,
1010
+ ...(routine.timeoutMinutes === undefined ? {} : { timeoutMinutes: routine.timeoutMinutes }),
1011
+ attachments: cloneAttachments(routine.attachments),
1012
+ target: routine.target,
1013
+ groupId: routine.groupId,
1014
+ botId: routine.botId,
1015
+ runOn: routine.runOn ?? "maus",
1016
+ scheduledFor,
1017
+ status: "queued",
1018
+ manual,
1019
+ triggerSource: manual ? "manual" : "schedule",
1020
+ sourceThreadId: routine.sourceThreadId,
1021
+ createdAt: this.now(),
1022
+ };
1023
+ this.runs.push(run);
1024
+ return run;
1025
+ }
1026
+ emitRoutine(routine) {
1027
+ this.options.emit?.({ kind: "routine", routine: cloneRoutine(routine) });
1028
+ }
1029
+ emitRun(run) {
1030
+ this.options.emit?.({ kind: "routine.run", run: cloneRun(run) });
1031
+ this.notifyRunChanged(run);
1032
+ }
1033
+ notifyRunChanged(run) {
1034
+ try {
1035
+ this.options.onRunChanged?.(cloneRun(run));
1036
+ }
1037
+ catch (error) {
1038
+ // Reporting is secondary to scheduler truth. A transcript write must
1039
+ // never strand the run in memory or prevent the next tick.
1040
+ console.error("routine: source-thread lifecycle update failed", error);
1041
+ }
1042
+ }
1043
+ matchingRoutineRequestReceipt(request) {
1044
+ const receipt = this.routineRequestReceipts.find((candidate) => candidate.requestId === request.requestId);
1045
+ if (!receipt)
1046
+ return null;
1047
+ if (receipt.action !== request.action ||
1048
+ receipt.messageId !== request.messageId ||
1049
+ receipt.botId !== request.botId ||
1050
+ receipt.threadId !== request.threadId ||
1051
+ receipt.fingerprintVersion !== request.fingerprintVersion ||
1052
+ receipt.fingerprint !== request.fingerprint) {
1053
+ throw new Error("Routine request receipt does not match this confirmation card");
1054
+ }
1055
+ return receipt;
1056
+ }
1057
+ rememberRoutineRequest(request, resultId, appliedAt) {
1058
+ const existing = this.matchingRoutineRequestReceipt(request);
1059
+ if (existing) {
1060
+ if (existing.resultId !== resultId)
1061
+ throw new Error("Routine request receipt has another result");
1062
+ return;
1063
+ }
1064
+ this.routineRequestReceipts.unshift({ ...request, resultId, appliedAt });
1065
+ }
1066
+ /**
1067
+ * A confirmation receipt is only true once the scheduler mutation and its
1068
+ * receipt reached the same atomic file. Restore the complete in-memory
1069
+ * state if writing or renaming that file fails so a retry cannot mistake an
1070
+ * uncommitted action for a durable one.
1071
+ */
1072
+ commitMutation(mutate) {
1073
+ const before = {
1074
+ routines: this.routines.map(cloneRoutine),
1075
+ runs: this.runs.map(cloneRun),
1076
+ receipts: this.routineRequestReceipts.map((receipt) => ({ ...receipt })),
1077
+ };
1078
+ try {
1079
+ mutate();
1080
+ this.save();
1081
+ }
1082
+ catch (error) {
1083
+ this.routines = before.routines;
1084
+ this.runs = before.runs;
1085
+ this.routineRequestReceipts = before.receipts;
1086
+ throw error;
1087
+ }
1088
+ }
1089
+ save() {
1090
+ // Active receipts own cancellation, timeout, and provider-event routing;
1091
+ // evicting one would strand live work. Treat MAX_RUNS as a soft history
1092
+ // cap and reclaim only the oldest terminal receipts. An unusually large
1093
+ // active queue may exceed it until work settles.
1094
+ let excess = this.runs.length - MAX_RUNS;
1095
+ for (let index = 0; index < this.runs.length && excess > 0;) {
1096
+ if (["queued", "running", "waiting"].includes(this.runs[index].status)) {
1097
+ index += 1;
1098
+ continue;
1099
+ }
1100
+ this.runs.splice(index, 1);
1101
+ excess -= 1;
1102
+ }
1103
+ mkdirSync(dirname(this.file), { recursive: true });
1104
+ writeFileAtomic(this.file, JSON.stringify({
1105
+ version: 1,
1106
+ routines: this.routines,
1107
+ runs: this.runs,
1108
+ routineRequestReceipts: this.routineRequestReceipts,
1109
+ }, null, 2), { mode: 0o600 });
1110
+ }
1111
+ }