openmausbot 0.1.98 → 0.1.100

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 (397) hide show
  1. package/dist/assets/{ActivityPanel-H6D3SD39.js → ActivityPanel-CA9zfl3_.js} +1 -1
  2. package/dist/assets/BotSettingsDialog-3i_izROu.js +7 -0
  3. package/dist/assets/{CloudBackendPicker-iezpJFpF.js → CloudBackendPicker-BR42-xBg.js} +1 -1
  4. package/dist/assets/{ComputerPanel-DZPj-IWu.js → ComputerPanel-CvThnFmt.js} +1 -1
  5. package/dist/assets/{DesktopViewer-utONms1p.js → DesktopViewer-CqovWUic.js} +1 -1
  6. package/dist/assets/InspectorPanel-Cbjm86gt.js +5 -0
  7. package/dist/assets/{KeyboardShortcutsModal-gk9mvHj_.js → KeyboardShortcutsModal-X13pVrkh.js} +1 -1
  8. package/dist/assets/{LocalVmWorkspace-DLIvd2kC.js → LocalVmWorkspace-BGD3Tvd3.js} +1 -1
  9. package/dist/assets/{MacLocalControl-bJv8yiPB.js → MacLocalControl-ExdXHVUv.js} +1 -1
  10. package/dist/assets/NewBotDialog-ftDBMjWR.js +2 -0
  11. package/dist/assets/RemoteAgentSettingsPanel-BUHLV_vC.js +1 -0
  12. package/dist/assets/RoutinesPage-Buk5-r3o.js +1 -0
  13. package/dist/assets/{RoutinesSection-Dny9ra-c.js → RoutinesSection-NyReOXr8.js} +1 -1
  14. package/dist/assets/{SettingsModal-gC6XJJmt.js → SettingsModal-DkkhdxKM.js} +3 -3
  15. package/dist/assets/{TeamMapPage-B6fuu4r_.js → TeamMapPage-BIxeHoy3.js} +1 -1
  16. package/dist/assets/{TriggersPanel-j0qTP2CO.js → TriggersPanel-m5iqc43H.js} +1 -1
  17. package/dist/assets/{VisibilitySection-DT0FqPvu.js → VisibilitySection-y8Adk-c4.js} +1 -1
  18. package/dist/assets/WebhooksPanel-G_NWjHOP.js +3 -0
  19. package/dist/assets/{abnfDiagram-O67JEVCF-BsjcFX0-.js → abnfDiagram-O67JEVCF-D56ZgyPf.js} +1 -1
  20. package/dist/assets/{activity-BxOkayvD.js → activity-D_puBfZo.js} +1 -1
  21. package/dist/assets/{arc-CJNA94c6.js → arc-DzduGHbq.js} +1 -1
  22. package/dist/assets/{architectureDiagram-NJMV4G6O-dz2ud7lP.js → architectureDiagram-NJMV4G6O-C4NS6DQF.js} +1 -1
  23. package/dist/assets/{blockDiagram-BEXU5L5S-ChBEALoR.js → blockDiagram-BEXU5L5S-BNvsskiE.js} +1 -1
  24. package/dist/assets/{brain-CFZ7PEXc.js → brain-oq48IgWl.js} +1 -1
  25. package/dist/assets/{c4Diagram-YGBWAQC7-KZNRKDQe.js → c4Diagram-YGBWAQC7-ch3B7xZy.js} +1 -1
  26. package/dist/assets/channel-CmFDMHrN.js +1 -0
  27. package/dist/assets/{chunk-2Q5K7J3B-BLLwmCET.js → chunk-2Q5K7J3B-D9dL9ZS7.js} +1 -1
  28. package/dist/assets/{chunk-3FUC2YCW-CQxyreBZ.js → chunk-3FUC2YCW-DmLUUuNr.js} +2 -2
  29. package/dist/assets/{chunk-5VM5RSS4-D6sFMW0u.js → chunk-5VM5RSS4-B90ODeo2.js} +1 -1
  30. package/dist/assets/{chunk-GTNCS2PH-BaZ1YgLF.js → chunk-GTNCS2PH-BtlGIG3W.js} +1 -1
  31. package/dist/assets/{chunk-GWA4HPMP-CcalphmC.js → chunk-GWA4HPMP-CurBwFFG.js} +1 -1
  32. package/dist/assets/{chunk-JWPE2WC7-CSk8R3ve.js → chunk-JWPE2WC7-DTjNNXcd.js} +1 -1
  33. package/dist/assets/{chunk-NETBCI7D-CTmLjA57.js → chunk-NETBCI7D-xS7hOqBJ.js} +1 -1
  34. package/dist/assets/{chunk-WEXAMYUT-CM9Z77Ne.js → chunk-WEXAMYUT-DJZBSyA9.js} +1 -1
  35. package/dist/assets/{chunk-XXDRQBXY-B1fxpMS2.js → chunk-XXDRQBXY-CbiNRzjt.js} +1 -1
  36. package/dist/assets/{classDiagram-v2-NBCMYWYE-DCCyoLpj.js → classDiagram-v2-NBCMYWYE-DyON9tUb.js} +1 -1
  37. package/dist/assets/core-CRkAXD3N.js +12 -0
  38. package/dist/assets/{cose-bilkent-JH36ORCC-KyImBKvE.js → cose-bilkent-JH36ORCC-C7UwNnp8.js} +1 -1
  39. package/dist/assets/cpp-DddhVWhK.js +1 -0
  40. package/dist/assets/{cynefin-OW5HDTMX-fsZc6MGa.js → cynefin-OW5HDTMX-Dv1GTekc.js} +1 -1
  41. package/dist/assets/{cynefinDiagram-VND7K2PF-CGmHsShB.js → cynefinDiagram-VND7K2PF-CKgpyZxo.js} +1 -1
  42. package/dist/assets/{dagre-6A5THRUB-Dah1h-CH.js → dagre-6A5THRUB-BY3cYc8R.js} +1 -1
  43. package/dist/assets/{diagram-22UHCM2B-BIu71OyV.js → diagram-22UHCM2B-D71jBFCO.js} +1 -1
  44. package/dist/assets/{diagram-3UASUU5V-gxhUB_Mu.js → diagram-3UASUU5V-Bc3i6JGD.js} +1 -1
  45. package/dist/assets/{diagram-ATOU4E4O-CBhusUBn.js → diagram-ATOU4E4O-BPDSHISG.js} +1 -1
  46. package/dist/assets/{diagram-CDSNMT55-BdPrc86x.js → diagram-CDSNMT55-xuCT1v32.js} +1 -1
  47. package/dist/assets/{diagram-MLGK6HIB-Clp110R_.js → diagram-MLGK6HIB-LEDTXDPg.js} +1 -1
  48. package/dist/assets/{diagram-MPIPVDR6-DJKfbugw.js → diagram-MPIPVDR6-B0kfscRD.js} +1 -1
  49. package/dist/assets/{ebnfDiagram-ZINNZB2B-DnsltoBu.js → ebnfDiagram-ZINNZB2B-B-Z0HLzJ.js} +1 -1
  50. package/dist/assets/{elk-276RUBZZ-jWdq6gqH.js → elk-276RUBZZ-Bzypn7lU.js} +1 -1
  51. package/dist/assets/engine-javascript-D5Xfc425.js +141 -0
  52. package/dist/assets/{erDiagram-OPXOYQCR-DArPVf8u.js → erDiagram-OPXOYQCR-vl1xKmbS.js} +1 -1
  53. package/dist/assets/{flowDiagram-KWPJA3E3-D2HZ-2f7.js → flowDiagram-KWPJA3E3-DQXCXm7z.js} +1 -1
  54. package/dist/assets/{ganttDiagram-FUAMR5RP--o0KIxEo.js → ganttDiagram-FUAMR5RP-B0wwnP2j.js} +1 -1
  55. package/dist/assets/{gitGraphDiagram-X574FWY7-CsoXr2D7.js → gitGraphDiagram-X574FWY7-HfEVZRuR.js} +1 -1
  56. package/dist/assets/{image-plus-BbVjGHin.js → image-plus-D9o3v9bi.js} +1 -1
  57. package/dist/assets/{index-C3NqfdVU.js → index-CyM91z0J.js} +205 -205
  58. package/dist/assets/{index-BrPW5HDO.css → index-DbN_Hfjs.css} +1 -1
  59. package/dist/assets/{infoDiagram-VRGFBTTK-g-6lNp7r.js → infoDiagram-VRGFBTTK-BcCklxlu.js} +1 -1
  60. package/dist/assets/{ishikawaDiagram-OU5B5YK6-B1d6qVTV.js → ishikawaDiagram-OU5B5YK6-Bw7Fp8yZ.js} +1 -1
  61. package/dist/assets/{journeyDiagram-ZHPQQLJL-BTPuiU6T.js → journeyDiagram-ZHPQQLJL-BqFaTPiB.js} +1 -1
  62. package/dist/assets/{kanban-definition-PNTS6WVX-bDDuRJvP.js → kanban-definition-PNTS6WVX-BdGyimUh.js} +1 -1
  63. package/dist/assets/{linear-BVVKbEMy.js → linear-DezKjgv7.js} +1 -1
  64. package/dist/assets/{mermaid.core-JJdoKyKB.js → mermaid.core-97L7xtx0.js} +5 -5
  65. package/dist/assets/{mindmap-definition-NLK3R4M7-B4aE3T0q.js → mindmap-definition-NLK3R4M7-DP6qz87C.js} +1 -1
  66. package/dist/assets/{page-visible-BBo_Zm8g.js → page-visible-BehtKJDm.js} +1 -1
  67. package/dist/assets/{pegDiagram-GJSIUBJH-DFKQWpR7.js → pegDiagram-GJSIUBJH-CsYSloVz.js} +1 -1
  68. package/dist/assets/{pieDiagram-5QR66LMP-Iv7OKVJi.js → pieDiagram-5QR66LMP-CFI2Dcix.js} +1 -1
  69. package/dist/assets/{quadrantDiagram-O4NWA36T-U2YnfwBk.js → quadrantDiagram-O4NWA36T-DqTdAE5z.js} +1 -1
  70. package/dist/assets/{railroadDiagram-XR7U4H2S-CuPy6RqM.js → railroadDiagram-XR7U4H2S-t_rszRIF.js} +1 -1
  71. package/dist/assets/{remote-desktop-panel-BNEj7tUx.js → remote-desktop-panel-WTaz_HI9.js} +1 -1
  72. package/dist/assets/{requirementDiagram-PLB6GJNP-bMI5AK7T.js → requirementDiagram-PLB6GJNP-1MU-l7p8.js} +1 -1
  73. package/dist/assets/ruby-Ct1eTYOr.js +1 -0
  74. package/dist/assets/{sankeyDiagram-IPEJSGJF-BKcFE2WQ.js → sankeyDiagram-IPEJSGJF-CtkrwAvo.js} +1 -1
  75. package/dist/assets/{sequenceDiagram-PO4LG4MO-Bn9q88kl.js → sequenceDiagram-PO4LG4MO-CJOwBubv.js} +1 -1
  76. package/dist/assets/{sizeCapture-INFHLROL-Cvmdq9HD.js → sizeCapture-INFHLROL-DhQVhKCc.js} +1 -1
  77. package/dist/assets/{stateDiagram-v2-GCMORJYK-JJQYw3eQ.js → stateDiagram-v2-GCMORJYK-CaQK9yA9.js} +1 -1
  78. package/dist/assets/{swimlanes-2SLR337P-BkSCBEwp.js → swimlanes-2SLR337P-Ct8cqf01.js} +1 -1
  79. package/dist/assets/swimlanesDiagram-TC7HE7FX-BBUcFKOA.js +8 -0
  80. package/dist/assets/{timeline-definition-EJHVYXUP-DsaM4GHr.js → timeline-definition-EJHVYXUP-qim2Km3q.js} +1 -1
  81. package/dist/assets/{usecaseDiagram-POWQR4AR-3FOwLq7B.js → usecaseDiagram-POWQR4AR-BIzcxmgX.js} +1 -1
  82. package/dist/assets/{user-DcZBnAuv.js → user-EQ8rI0f4.js} +1 -1
  83. package/dist/assets/{vennDiagram-UO4OBE2U-DyNjJsKt.js → vennDiagram-UO4OBE2U-BFEuz8Ba.js} +1 -1
  84. package/dist/assets/{wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js → wardleyDiagram-VNRHLVJA-JRkAN01c.js} +1 -1
  85. package/dist/assets/{xychartDiagram-PMCCYNJV-BXZpg955.js → xychartDiagram-PMCCYNJV-WBsvmu0R.js} +1 -1
  86. package/dist/index.html +2 -2
  87. package/dist-server/drivers/agents-proxy.js +10 -9
  88. package/dist-server/index.js +1483 -813
  89. package/dist-server/openmausbot.js +248 -152
  90. package/dist-server/pair-cli.js +248 -152
  91. package/dist-server/server/activity.js +2 -0
  92. package/dist-server/server/checkpoints.js +55 -39
  93. package/dist-server/server/direct-apply.js +20 -0
  94. package/dist-server/server/drivers/agents-call.js +1 -1
  95. package/dist-server/server/drivers/agents-catalog.js +11 -8
  96. package/dist-server/server/drivers/codex.js +147 -24
  97. package/dist-server/server/drivers/device-auth.js +5 -4
  98. package/dist-server/server/drivers/openai-chat.js +5 -4
  99. package/dist-server/server/harness/registry.js +63 -45
  100. package/dist-server/server/index.js +203 -23
  101. package/dist-server/server/mcp-oauth.js +125 -47
  102. package/dist-server/server/mcp-probe.js +18 -10
  103. package/dist-server/server/message-file.js +95 -3
  104. package/dist-server/server/model-requests.js +55 -4
  105. package/dist-server/server/profile-requests.js +80 -4
  106. package/dist-server/server/request-auth.js +1 -0
  107. package/dist-server/server/routes/undo.js +17 -0
  108. package/dist-server/server/routine-requests.js +210 -6
  109. package/dist-server/server/setup-mode.js +1 -1
  110. package/dist-server/server/skill-learn.js +2 -2
  111. package/dist-server/server/skills.js +30 -0
  112. package/dist-server/server/system-prompt.js +1 -1
  113. package/dist-server/server/turn-continuation.js +13 -18
  114. package/dist-server/server/workspace-backup.js +6 -1
  115. package/dist-server/workspace-backup.worker.js +1 -1
  116. package/package.json +1 -1
  117. package/skills/create-verification-skill/SKILL.md +9 -8
  118. package/skills/create-verification-skill/manifest.json +1 -1
  119. package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +0 -7
  120. package/dist/assets/InspectorPanel-OVaBVrzC.js +0 -5
  121. package/dist/assets/NewBotDialog-1qGOAdUM.js +0 -2
  122. package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +0 -1
  123. package/dist/assets/RoutinesPage-BM2D4qGi.js +0 -1
  124. package/dist/assets/WebhooksPanel--YQyCjKO.js +0 -3
  125. package/dist/assets/abap-BdImnpbu.js +0 -1
  126. package/dist/assets/actionscript-3-B3316cI-.js +0 -1
  127. package/dist/assets/ada-bCR0ucgS.js +0 -1
  128. package/dist/assets/ahk-CsyLZFj1.js +0 -1
  129. package/dist/assets/ahk2-8Zs4aa1G.js +0 -1
  130. package/dist/assets/andromeeda-C4gqWexZ.js +0 -1
  131. package/dist/assets/angular-html-DA-rfuFy.js +0 -1
  132. package/dist/assets/angular-ts-BrjP3tb8.js +0 -1
  133. package/dist/assets/apache-Pmp26Uib.js +0 -1
  134. package/dist/assets/apex-DhZFqWV2.js +0 -1
  135. package/dist/assets/apl-CORt7UWP.js +0 -1
  136. package/dist/assets/applescript-Co6uUVPk.js +0 -1
  137. package/dist/assets/ara-BRHolxvo.js +0 -1
  138. package/dist/assets/asciidoc-CSVQ5wI8.js +0 -1
  139. package/dist/assets/asm-D_Q5rh1f.js +0 -1
  140. package/dist/assets/astro-HNnZUWAn.js +0 -1
  141. package/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
  142. package/dist/assets/awk-DMzUqQB5.js +0 -1
  143. package/dist/assets/ayu-dark-DYE7WIF3.js +0 -1
  144. package/dist/assets/ayu-light-BA47KaF1.js +0 -1
  145. package/dist/assets/ayu-mirage-32ctXXKs.js +0 -1
  146. package/dist/assets/ballerina-BFfxhgS-.js +0 -1
  147. package/dist/assets/bat-CickPsom.js +0 -1
  148. package/dist/assets/beancount-k_qm7-4y.js +0 -1
  149. package/dist/assets/berry-uYugtg8r.js +0 -1
  150. package/dist/assets/bibtex-CHM0blh-.js +0 -1
  151. package/dist/assets/bicep-Bmn6On1c.js +0 -1
  152. package/dist/assets/bird2-Bx8U0n9b.js +0 -1
  153. package/dist/assets/blade-2xfisSek.js +0 -1
  154. package/dist/assets/bsl-DlhNcFeZ.js +0 -1
  155. package/dist/assets/c3-Dp5svz6Z.js +0 -1
  156. package/dist/assets/cadence-Bv_4Rxtq.js +0 -1
  157. package/dist/assets/cairo-KRGpt6FW.js +0 -1
  158. package/dist/assets/catppuccin-frappe-CZL1YF0i.js +0 -1
  159. package/dist/assets/catppuccin-latte-DH-8KZSZ.js +0 -1
  160. package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +0 -1
  161. package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +0 -1
  162. package/dist/assets/channel-WhFyujhZ.js +0 -1
  163. package/dist/assets/chapel-DTp_pixX.js +0 -1
  164. package/dist/assets/clarity-Dn5IMItf.js +0 -1
  165. package/dist/assets/clojure-P80f7IUj.js +0 -1
  166. package/dist/assets/cmake-D1j8_8rp.js +0 -1
  167. package/dist/assets/cobol-nBiQ_Alo.js +0 -1
  168. package/dist/assets/codeowners-Bp6g37R7.js +0 -1
  169. package/dist/assets/codeql-DsOJ9woJ.js +0 -1
  170. package/dist/assets/coffee-Ch7k5sss.js +0 -1
  171. package/dist/assets/common-lisp-Cg-RD9OK.js +0 -1
  172. package/dist/assets/coq-C7JzOVbR.js +0 -1
  173. package/dist/assets/cpp-BMRokrvK.js +0 -1
  174. package/dist/assets/crystal-DGywbUpC.js +0 -1
  175. package/dist/assets/csv-fuZLfV_i.js +0 -1
  176. package/dist/assets/cue-D82EKSYY.js +0 -1
  177. package/dist/assets/cypher-COkxafJQ.js +0 -1
  178. package/dist/assets/d-85-TOEBH.js +0 -1
  179. package/dist/assets/dark-plus-C3mMm8J8.js +0 -1
  180. package/dist/assets/dax-CEL-wOlO.js +0 -1
  181. package/dist/assets/desktop-BmXAJ9_W.js +0 -1
  182. package/dist/assets/dotenv-Da5cRb03.js +0 -1
  183. package/dist/assets/dracula-BzJJZx-M.js +0 -1
  184. package/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
  185. package/dist/assets/dream-maker-BtqSS_iP.js +0 -1
  186. package/dist/assets/edge-FbVlp4U3.js +0 -1
  187. package/dist/assets/elixir-CkH2-t6x.js +0 -1
  188. package/dist/assets/elm-DbKCFpqz.js +0 -1
  189. package/dist/assets/emacs-lisp-C_m_b--Z.js +0 -1
  190. package/dist/assets/erb-DXfck5VN.js +0 -1
  191. package/dist/assets/erlang-DsQrWhSR.js +0 -1
  192. package/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
  193. package/dist/assets/everforest-light-C8M2exoo.js +0 -1
  194. package/dist/assets/fennel-BYunw83y.js +0 -1
  195. package/dist/assets/fluent-C4IJs8-o.js +0 -1
  196. package/dist/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
  197. package/dist/assets/fortran-free-form-BxgE0vQu.js +0 -1
  198. package/dist/assets/fsharp-CXgrBDvD.js +0 -1
  199. package/dist/assets/gdresource-TyuKm33G.js +0 -1
  200. package/dist/assets/gdscript-DqcFQ5yU.js +0 -1
  201. package/dist/assets/gdshader-DkwncUOv.js +0 -1
  202. package/dist/assets/genie-D0YGMca9.js +0 -1
  203. package/dist/assets/gherkin-DyxjwDmM.js +0 -1
  204. package/dist/assets/git-commit-F4YmCXRG.js +0 -1
  205. package/dist/assets/git-rebase-r7XF79zn.js +0 -1
  206. package/dist/assets/github-dark-DHJKELXO.js +0 -1
  207. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  208. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  209. package/dist/assets/github-light-DAi9KRSo.js +0 -1
  210. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  211. package/dist/assets/gleam-BspZqrRM.js +0 -1
  212. package/dist/assets/glimmer-js-ByusRIyA.js +0 -1
  213. package/dist/assets/glimmer-ts-BfAWNZQY.js +0 -1
  214. package/dist/assets/glsl-DplSGwfg.js +0 -1
  215. package/dist/assets/gn-n2N0HUVH.js +0 -1
  216. package/dist/assets/gnuplot-DdkO51Og.js +0 -1
  217. package/dist/assets/groovy-gcz8RCvz.js +0 -1
  218. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  219. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  220. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  221. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  222. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  223. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  224. package/dist/assets/hack-BWmVpMyf.js +0 -1
  225. package/dist/assets/haml-D5jkg6IW.js +0 -1
  226. package/dist/assets/handlebars-BpdQsYii.js +0 -1
  227. package/dist/assets/haskell-Df6bDoY_.js +0 -1
  228. package/dist/assets/haxe-CfZj7gIn.js +0 -1
  229. package/dist/assets/hcl-BWvSN4gD.js +0 -1
  230. package/dist/assets/hjson-D5-asLiD.js +0 -1
  231. package/dist/assets/hlsl-D3lLCCz7.js +0 -1
  232. package/dist/assets/horizon-BUw7H-hv.js +0 -1
  233. package/dist/assets/horizon-bright-CUuTKBJd.js +0 -1
  234. package/dist/assets/houston-DnULxvSX.js +0 -1
  235. package/dist/assets/html-derivative-DlHx6ybY.js +0 -1
  236. package/dist/assets/http-jrhK8wxY.js +0 -1
  237. package/dist/assets/hurl-irOxFIW8.js +0 -1
  238. package/dist/assets/hxml-2-FPmUDs.js +0 -1
  239. package/dist/assets/hy-DFXneXwc.js +0 -1
  240. package/dist/assets/imba-DGztddWO.js +0 -1
  241. package/dist/assets/index-CJTp9JEp.js +0 -153
  242. package/dist/assets/ini-BEwlwnbL.js +0 -1
  243. package/dist/assets/jinja-f2NsQr07.js +0 -1
  244. package/dist/assets/jison-wvAkD_A8.js +0 -1
  245. package/dist/assets/jsonl-DcaNXYhu.js +0 -1
  246. package/dist/assets/jsonnet-DFQXde-d.js +0 -1
  247. package/dist/assets/jssm-C2t-YnRu.js +0 -1
  248. package/dist/assets/julia-5Bft2YPA.js +0 -1
  249. package/dist/assets/just-Cwhn7H3k.js +0 -1
  250. package/dist/assets/kanagawa-dragon-BuwD2xS4.js +0 -1
  251. package/dist/assets/kanagawa-lotus-C3DzagqV.js +0 -1
  252. package/dist/assets/kanagawa-wave-DFGoQZhC.js +0 -1
  253. package/dist/assets/kdl-DV7GczEv.js +0 -1
  254. package/dist/assets/kusto-wEQ09or8.js +0 -1
  255. package/dist/assets/laserwave-DUszq2jm.js +0 -1
  256. package/dist/assets/latex-D5pSuvFb.js +0 -1
  257. package/dist/assets/lean-BZvkOJ9d.js +0 -1
  258. package/dist/assets/light-plus-B7mTdjB0.js +0 -1
  259. package/dist/assets/liquid-C0sCDyMI.js +0 -1
  260. package/dist/assets/llvm-BZoOZj88.js +0 -1
  261. package/dist/assets/log-2UxHyX5q.js +0 -1
  262. package/dist/assets/logo-BtOb2qkB.js +0 -1
  263. package/dist/assets/luau-BnpPk5vE.js +0 -1
  264. package/dist/assets/marko-DjSrsDqO.js +0 -1
  265. package/dist/assets/material-theme-D5KoaKCx.js +0 -1
  266. package/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
  267. package/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  268. package/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
  269. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  270. package/dist/assets/matlab-D7o27uSR.js +0 -1
  271. package/dist/assets/mdc-D1_yUvq7.js +0 -1
  272. package/dist/assets/mermaid-CQcHuHx7.js +0 -1
  273. package/dist/assets/min-dark-CafNBF8u.js +0 -1
  274. package/dist/assets/min-light-CTRr51gU.js +0 -1
  275. package/dist/assets/mipsasm-CKIfxQSi.js +0 -1
  276. package/dist/assets/mojo-DJz3ZmWd.js +0 -1
  277. package/dist/assets/monokai-BRVnQi9A.js +0 -1
  278. package/dist/assets/moonbit-CHtswR0a.js +0 -1
  279. package/dist/assets/move-el3G9tDJ.js +0 -1
  280. package/dist/assets/narrat-DRg8JJMk.js +0 -1
  281. package/dist/assets/nextflow-C-mBbutL.js +0 -1
  282. package/dist/assets/nextflow-groovy-vE_lwT2v.js +0 -1
  283. package/dist/assets/nginx-BpAMiNFr.js +0 -1
  284. package/dist/assets/night-owl-C39BiMTA.js +0 -1
  285. package/dist/assets/night-owl-light-CMTm3GFP.js +0 -1
  286. package/dist/assets/nim-BIad80T-.js +0 -1
  287. package/dist/assets/nix-CwoSXNpI.js +0 -1
  288. package/dist/assets/nord-Ddv68eIx.js +0 -1
  289. package/dist/assets/nsis-BlV79W_Q.js +0 -1
  290. package/dist/assets/nushell-D3jzshHO.js +0 -1
  291. package/dist/assets/objective-c-DXmwc3jG.js +0 -1
  292. package/dist/assets/objective-cpp-CLxacb5B.js +0 -1
  293. package/dist/assets/ocaml-C0hk2d4L.js +0 -1
  294. package/dist/assets/odin-BBf5iR-q.js +0 -1
  295. package/dist/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  296. package/dist/assets/one-light-C3Wv6jpd.js +0 -1
  297. package/dist/assets/openscad-C4EeE6gA.js +0 -1
  298. package/dist/assets/org-DM6o9KBp.js +0 -1
  299. package/dist/assets/pascal-D93ZcfNL.js +0 -1
  300. package/dist/assets/perl-B9cMNwum.js +0 -1
  301. package/dist/assets/pkl-u5AG7uiY.js +0 -1
  302. package/dist/assets/plastic-3e1v2bzS.js +0 -1
  303. package/dist/assets/plsql-ChMvpjG-.js +0 -1
  304. package/dist/assets/po-BTJTHyun.js +0 -1
  305. package/dist/assets/poimandres-CS3Unz2-.js +0 -1
  306. package/dist/assets/polar-C0HS_06l.js +0 -1
  307. package/dist/assets/postcss-CXtECtnM.js +0 -1
  308. package/dist/assets/powerquery-CEu0bR-o.js +0 -1
  309. package/dist/assets/prisma-Vru482bI.js +0 -1
  310. package/dist/assets/prolog-CbFg5uaA.js +0 -1
  311. package/dist/assets/pug-DKIMFp6K.js +0 -1
  312. package/dist/assets/puppet-BMWR74SV.js +0 -1
  313. package/dist/assets/purescript-CklMAg4u.js +0 -1
  314. package/dist/assets/qml-3beO22l8.js +0 -1
  315. package/dist/assets/qmldir-C8lEn-DE.js +0 -1
  316. package/dist/assets/qss-IeuSbFQv.js +0 -1
  317. package/dist/assets/racket-BqYA7rlc.js +0 -1
  318. package/dist/assets/raku-DXvB9xmW.js +0 -1
  319. package/dist/assets/razor-BjBPvh-w.js +0 -1
  320. package/dist/assets/rbs-CpoqiR4B.js +0 -1
  321. package/dist/assets/red-hvxz__6c.js +0 -1
  322. package/dist/assets/reg-C-SQnVFl.js +0 -1
  323. package/dist/assets/regexp-CDVJQ6XC.js +0 -1
  324. package/dist/assets/rel-C3B-1QV4.js +0 -1
  325. package/dist/assets/riscv-BM1_JUlF.js +0 -1
  326. package/dist/assets/ron-D8l8udqQ.js +0 -1
  327. package/dist/assets/rose-pine-5qJOZa0Y.js +0 -1
  328. package/dist/assets/rose-pine-dawn-zx0QlTCp.js +0 -1
  329. package/dist/assets/rose-pine-moon-C9pEdX9L.js +0 -1
  330. package/dist/assets/rosmsg-BJDFO7_C.js +0 -1
  331. package/dist/assets/rst-bs7f0vWN.js +0 -1
  332. package/dist/assets/ruby-C0TQ7zu5.js +0 -1
  333. package/dist/assets/sas-DEy46yEz.js +0 -1
  334. package/dist/assets/scala-CqE71os6.js +0 -1
  335. package/dist/assets/scheme-C98Dy4si.js +0 -1
  336. package/dist/assets/sdbl-DVxCFoDh.js +0 -1
  337. package/dist/assets/shaderlab-Dg9Lc6iA.js +0 -1
  338. package/dist/assets/shellsession-BADoaaVG.js +0 -1
  339. package/dist/assets/slack-dark-BthQWCQV.js +0 -1
  340. package/dist/assets/slack-ochin-DqwNpetd.js +0 -1
  341. package/dist/assets/smalltalk-BOQMe2GC.js +0 -1
  342. package/dist/assets/smithy-cds9vsN8.js +0 -1
  343. package/dist/assets/snazzy-light-Bw305WKR.js +0 -1
  344. package/dist/assets/solarized-dark-CpvCGNkr.js +0 -1
  345. package/dist/assets/solarized-light-Dlz6yCKv.js +0 -1
  346. package/dist/assets/solidity-DijEV5ha.js +0 -1
  347. package/dist/assets/soy-8wufbnw4.js +0 -1
  348. package/dist/assets/sparql-rVzFXLq3.js +0 -1
  349. package/dist/assets/splunk-BtCnVYZw.js +0 -1
  350. package/dist/assets/ssh-config-_ykCGR6B.js +0 -1
  351. package/dist/assets/stata-DI20mbqo.js +0 -1
  352. package/dist/assets/stylus-BEDo0Tqx.js +0 -1
  353. package/dist/assets/surrealql-Cjom0U5J.js +0 -1
  354. package/dist/assets/svelte-Cy7k_4gC.js +0 -1
  355. package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +0 -8
  356. package/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
  357. package/dist/assets/system-verilog-0hqHdDBg.js +0 -1
  358. package/dist/assets/systemd-4A_iFExJ.js +0 -1
  359. package/dist/assets/talonscript-CkByrt1z.js +0 -1
  360. package/dist/assets/tasl-QIJgUcNo.js +0 -1
  361. package/dist/assets/tcl-dwOrl1Do.js +0 -1
  362. package/dist/assets/templ-DhtptRzy.js +0 -1
  363. package/dist/assets/terraform-BETggiCN.js +0 -1
  364. package/dist/assets/tex-D96PA37w.js +0 -1
  365. package/dist/assets/tokyo-night-hegEt444.js +0 -1
  366. package/dist/assets/ts-tags-D351s5mN.js +0 -1
  367. package/dist/assets/tsv-B_m7g4N7.js +0 -1
  368. package/dist/assets/turtle-BsS91CYL.js +0 -1
  369. package/dist/assets/twig-27uCiNez.js +0 -1
  370. package/dist/assets/typespec-CAFt9gP4.js +0 -1
  371. package/dist/assets/typst-BUadGCkm.js +0 -1
  372. package/dist/assets/v-BGw2Nkan.js +0 -1
  373. package/dist/assets/vala-CsfeWuGM.js +0 -1
  374. package/dist/assets/vb-Cu-pLBUe.js +0 -1
  375. package/dist/assets/verilog-nZwndyjY.js +0 -1
  376. package/dist/assets/vesper-DRje8inN.js +0 -1
  377. package/dist/assets/vhdl-CeAyd5Ju.js +0 -1
  378. package/dist/assets/viml-CJc9bBzg.js +0 -1
  379. package/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
  380. package/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
  381. package/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
  382. package/dist/assets/vue-BqiEGhQt.js +0 -1
  383. package/dist/assets/vue-html-AaS7Mt5G.js +0 -1
  384. package/dist/assets/vue-vine-BoDAl6tE.js +0 -1
  385. package/dist/assets/vyper-CDx5xZoG.js +0 -1
  386. package/dist/assets/wasm-CG6Dc4jp.js +0 -1
  387. package/dist/assets/wenyan-BV7otONQ.js +0 -1
  388. package/dist/assets/wgsl-Dx-B1_4e.js +0 -1
  389. package/dist/assets/wikitext-BhOHFoWU.js +0 -1
  390. package/dist/assets/wit-5i3qLPDT.js +0 -1
  391. package/dist/assets/wolfram-lXgVvXCa.js +0 -1
  392. package/dist/assets/xsl-CtQFsRM5.js +0 -1
  393. package/dist/assets/zenscript-DVFEvuxE.js +0 -1
  394. package/dist/assets/zig-VOosw3JB.js +0 -1
  395. /package/dist/assets/{shellscript-Yzrsuije.js → bash-Yzrsuije.js} +0 -0
  396. /package/dist/assets/{docker-BcOcwvcX.js → dockerfile-BcOcwvcX.js} +0 -0
  397. /package/dist/assets/{make-CHLpvVh8.js → makefile-CHLpvVh8.js} +0 -0
@@ -1,9 +1,11 @@
1
1
  // OAuth sign-in for URL MCP servers, the way MCP clients do it: discover
2
2
  // the authorization server from the MCP server's 401, sign in as the app
3
3
  // the person registered for this server or else register as a public
4
- // client when the server allows it, run PKCE through the person's browser
5
- // with a loopback callback (or a URL pasted from another computer), then
6
- // keep the tokens fresh.
4
+ // client when the server allows it, run PKCE through the person's browser,
5
+ // then keep the tokens fresh. The browser comes back to a loopback listener
6
+ // on this machine; a browser on another computer (My Cloud, a hosted
7
+ // workspace) comes back to this server's own https address instead, and
8
+ // where it cannot, the person pastes the address the browser ended on.
7
9
  // Engines never see this: they get an ordinary Authorization header.
8
10
  //
9
11
  // The loopback listener and callback checks follow
@@ -27,7 +29,10 @@ const DEFAULT_LIFETIME_MS = 5 * 60_000;
27
29
  * that lapses mid-way. */
28
30
  const REFRESH_MARGIN_MS = 2 * 60_000;
29
31
  const REQUEST_TIMEOUT_MS = 10_000;
30
- const CALLBACK_PATH = "/mcp-oauth/callback";
32
+ /** The callback path, on a loopback listener and on this server's own
33
+ * https address alike. */
34
+ export const MCP_OAUTH_CALLBACK_PATH = "/mcp-oauth/callback";
35
+ const CALLBACK_PATH = MCP_OAUTH_CALLBACK_PATH;
31
36
  /** complete()'s answer for a flow that ended while its code was spent. */
32
37
  const DISCARDED = "discarded";
33
38
  export class McpSignInError extends Error {
@@ -53,6 +58,33 @@ function preferredPort(url) {
53
58
  export function mcpOAuthRedirectUri(url) {
54
59
  return `http://127.0.0.1:${preferredPort(url)}${CALLBACK_PATH}`;
55
60
  }
61
+ /** An exact https origin (no path, no credentials), or null. */
62
+ function httpsOrigin(value) {
63
+ if (!value)
64
+ return null;
65
+ try {
66
+ const url = new URL(value);
67
+ return url.protocol === "https:" && !url.username && !url.password && value.replace(/\/+$/, "") === url.origin ? url.origin : null;
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /** The https address of this server that a browser on another computer
74
+ * comes back to after approving a sign-in, or null for the loopback
75
+ * callback. `browserOrigin` is the origin that browser proved it is using
76
+ * (its Origin header, equal to the request's own origin); `configured` are
77
+ * this server's public addresses (a Cloud home's, a hosted workspace's, a
78
+ * connected domain, OMB_PUBLIC_URL). The browser's own origin wins when it
79
+ * is one of them or none is set; otherwise the first configured one does,
80
+ * never a Host header nobody vouched for. */
81
+ export function mcpCallbackOrigin(browserOrigin, configured) {
82
+ const known = configured.map(httpsOrigin).filter((origin) => origin !== null);
83
+ const proven = httpsOrigin(browserOrigin);
84
+ if (proven && (known.length === 0 || known.includes(proven)))
85
+ return proven;
86
+ return known[0] ?? null;
87
+ }
56
88
  /** RFC 8414 §2: an absent list means client_secret_basic. */
57
89
  function tokenAuthMethod(meta, secret) {
58
90
  if (!secret)
@@ -116,6 +148,9 @@ function tokensFrom(body, previous) {
116
148
  ...(typeof body.scope === "string" ? { scope: body.scope } : {}),
117
149
  };
118
150
  }
151
+ /** The authorization server answered a registration, and said no. */
152
+ class RegistrationRefused extends Error {
153
+ }
119
154
  export class McpOAuthManager {
120
155
  store;
121
156
  lifetimeMs;
@@ -156,7 +191,7 @@ export class McpOAuthManager {
156
191
  const { tokens: _dropped, ...rest } = this.store.get(name, url) ?? { url };
157
192
  this.store.put(name, { ...rest, url, state: "needs-sign-in" });
158
193
  }
159
- start(name, url, wwwAuthenticate, owner = "loopback") {
194
+ start(name, url, wwwAuthenticate, owner = "loopback", browser = { remote: false }) {
160
195
  const current = this.flows.get(name);
161
196
  if (current?.status.phase === "waiting" && !this.isOwnerLive(current.owner)) {
162
197
  this.finish(current, "cancelled", "Your session ended. Start again.");
@@ -174,7 +209,7 @@ export class McpOAuthManager {
174
209
  }
175
210
  return pending.promise;
176
211
  }
177
- const starting = { owner, url, cancelled: false, promise: undefined };
212
+ const starting = { owner, url, browser, cancelled: false, promise: undefined };
178
213
  starting.promise = this.begin(name, url, starting, wwwAuthenticate).finally(() => this.starting.delete(name));
179
214
  this.starting.set(name, starting);
180
215
  return starting.promise;
@@ -225,6 +260,24 @@ export class McpOAuthManager {
225
260
  await this.acceptCallback(flow, callback);
226
261
  return { ...flow.status };
227
262
  }
263
+ /** The browser came back to this server's own https address. `host` is
264
+ * the address it asked for (the Host header) and `search` the query it
265
+ * brought. The flow is the one that redirect URI and state belong to; it
266
+ * then passes the same one-shot checks as the listener and paste-back.
267
+ * The scheme is the redirect URI's: a proxy in front of this server may
268
+ * end TLS, so the request itself cannot say it was https. */
269
+ async publicCallback(host, search) {
270
+ const state = new URLSearchParams(search).get("state") ?? "";
271
+ let match;
272
+ for (const flow of this.flows.values()) {
273
+ const expected = new URL(flow.redirectUri);
274
+ if (!flow.server && expected.host === host?.trim().toLowerCase() && sameSecret(state, flow.state))
275
+ match = flow;
276
+ }
277
+ if (!match)
278
+ return { status: 400, text: "Invalid sign-in callback. Return to OpenMausBot and try again." };
279
+ return this.answer(match, new URL(`${new URL(match.redirectUri).origin}${CALLBACK_PATH}${search.startsWith("?") ? search : ""}`));
280
+ }
228
281
  /** Revoke with the server when it offers that (best effort), then forget. */
229
282
  async signOut(name, url) {
230
283
  this.cancel(name);
@@ -342,7 +395,7 @@ export class McpOAuthManager {
342
395
  });
343
396
  const body = await response.json().catch(() => ({}));
344
397
  if (!response.ok || typeof body.client_id !== "string" || !body.client_id) {
345
- throw new Error("This server would not register OpenMausBot for sign-in. Try again later.");
398
+ throw new RegistrationRefused("This server would not register OpenMausBot for sign-in. Try again later.");
346
399
  }
347
400
  return body.client_id;
348
401
  }
@@ -354,6 +407,23 @@ export class McpOAuthManager {
354
407
  if (!meta)
355
408
  throw new McpOAuthError("not-oauth", "This server does not offer an OAuth sign-in. Add its token as a header instead.");
356
409
  const registered = this.clients(name, url);
410
+ // A browser on another computer comes back to this server's own https
411
+ // address. Only for a client registered now, for that address: an app
412
+ // registered in advance keeps the loopback redirect it was given. A
413
+ // server that refuses the address gets the loopback callback, pasted.
414
+ if (!registered && starting.browser.remote && starting.browser.origin) {
415
+ const redirectUri = `${starting.browser.origin}${CALLBACK_PATH}`;
416
+ let clientId = null;
417
+ try {
418
+ clientId = await this.register(meta, redirectUri);
419
+ }
420
+ catch (error) {
421
+ if (!(error instanceof RegistrationRefused))
422
+ throw error;
423
+ }
424
+ if (clientId)
425
+ return { ...this.open(name, url, starting, meta, redirectUri, { id: clientId, method: "none" }, null, meta.scopes).status };
426
+ }
357
427
  const server = createServer();
358
428
  try {
359
429
  await listen(server, preferredPort(url));
@@ -374,40 +444,46 @@ export class McpOAuthManager {
374
444
  ? { id: registered.clientId, ...(registered.clientSecret ? { secret: registered.clientSecret } : {}), method: tokenAuthMethod(meta, registered.clientSecret) }
375
445
  : { id: await this.register(meta, redirectUri), method: "none" };
376
446
  const scopes = registered?.scopes?.length ? registered.scopes : meta.scopes;
377
- if (starting.cancelled || !this.isOwnerLive(starting.owner))
378
- throw new McpSignInError("Sign-in cancelled because the session or server changed.", 409);
379
- const state = randomBytes(32).toString("base64url");
380
- const verifier = randomBytes(32).toString("base64url");
381
- const authorize = new URL(meta.authorizationEndpoint);
382
- authorize.searchParams.set("response_type", "code");
383
- authorize.searchParams.set("client_id", client.id);
384
- authorize.searchParams.set("redirect_uri", redirectUri);
385
- authorize.searchParams.set("state", state);
386
- authorize.searchParams.set("code_challenge", createHash("sha256").update(verifier).digest("base64url"));
387
- authorize.searchParams.set("code_challenge_method", "S256");
388
- authorize.searchParams.set("resource", meta.resource);
389
- if (scopes?.length)
390
- authorize.searchParams.set("scope", scopes.join(" "));
391
- const status = {
392
- phase: "waiting",
393
- flowId: randomUUID(),
394
- authorizationUrl: authorize.toString(),
395
- expiresAt: new Date(Date.now() + this.lifetimeMs).toISOString(),
396
- };
397
- const flow = {
398
- owner: starting.owner, name, url, status, state, verifier, redirectUri, client, meta, server, consumed: false,
399
- expiry: setTimeout(() => this.finish(flow, "expired", "Sign-in expired. Start again."), this.lifetimeMs),
400
- };
401
- flow.expiry.unref();
447
+ const flow = this.open(name, url, starting, meta, redirectUri, client, server, scopes);
402
448
  server.on("request", (request, response) => void this.callback(flow, request, response));
403
- this.flows.set(name, flow);
404
- return { ...status };
449
+ return { ...flow.status };
405
450
  }
406
451
  catch (error) {
407
452
  server.close();
408
453
  throw error;
409
454
  }
410
455
  }
456
+ /** Make the authorize link and keep the flow waiting for its callback. */
457
+ open(name, url, starting, meta, redirectUri, client, server, scopes) {
458
+ if (starting.cancelled || !this.isOwnerLive(starting.owner))
459
+ throw new McpSignInError("Sign-in cancelled because the session or server changed.", 409);
460
+ const state = randomBytes(32).toString("base64url");
461
+ const verifier = randomBytes(32).toString("base64url");
462
+ const authorize = new URL(meta.authorizationEndpoint);
463
+ authorize.searchParams.set("response_type", "code");
464
+ authorize.searchParams.set("client_id", client.id);
465
+ authorize.searchParams.set("redirect_uri", redirectUri);
466
+ authorize.searchParams.set("state", state);
467
+ authorize.searchParams.set("code_challenge", createHash("sha256").update(verifier).digest("base64url"));
468
+ authorize.searchParams.set("code_challenge_method", "S256");
469
+ authorize.searchParams.set("resource", meta.resource);
470
+ if (scopes?.length)
471
+ authorize.searchParams.set("scope", scopes.join(" "));
472
+ const status = {
473
+ phase: "waiting",
474
+ flowId: randomUUID(),
475
+ authorizationUrl: authorize.toString(),
476
+ expiresAt: new Date(Date.now() + this.lifetimeMs).toISOString(),
477
+ ...(server && starting.browser.remote ? { pasteBack: true } : {}),
478
+ };
479
+ const flow = {
480
+ owner: starting.owner, name, url, status, state, verifier, redirectUri, client, meta, server, consumed: false,
481
+ expiry: setTimeout(() => this.finish(flow, "expired", "Sign-in expired. Start again."), this.lifetimeMs),
482
+ };
483
+ flow.expiry.unref();
484
+ this.flows.set(name, flow);
485
+ return flow;
486
+ }
411
487
  async callback(flow, request, response) {
412
488
  response.setHeader("Cache-Control", "no-store");
413
489
  response.setHeader("Content-Type", "text/plain; charset=utf-8");
@@ -426,23 +502,25 @@ export class McpOAuthManager {
426
502
  response.writeHead(400).end("Invalid sign-in callback. Return to OpenMausBot and try again.");
427
503
  return;
428
504
  }
505
+ const answer = request.method === "GET"
506
+ ? await this.answer(flow, callback)
507
+ : { status: 400, text: "Invalid or expired sign-in. Return to OpenMausBot and try again." };
508
+ response.writeHead(answer.status).end(answer.text);
509
+ }
510
+ /** Spend a callback the browser brought back, and say how it went in
511
+ * words fit for a plain page: never the code or the server's own text. */
512
+ async answer(flow, callback) {
429
513
  try {
430
- if (request.method !== "GET")
431
- throw new McpSignInError("Invalid sign-in callback.", 400);
432
514
  await this.acceptCallback(flow, callback);
433
- if (flow.status.phase === "failed") {
434
- response.writeHead(400).end(`${flow.status.message} You can close this tab.`);
435
- }
436
- else {
437
- response.end("Signed in. You can close this tab and return to OpenMausBot.");
438
- }
515
+ return flow.status.phase === "failed"
516
+ ? { status: 400, text: `${flow.status.message} You can close this tab.` }
517
+ : { status: 200, text: "Signed in. You can close this tab and return to OpenMausBot." };
439
518
  }
440
519
  catch (error) {
441
- response.writeHead(error instanceof McpSignInError ? error.status : 400)
442
- .end("Invalid or expired sign-in. Return to OpenMausBot and try again.");
520
+ return { status: error instanceof McpSignInError ? error.status : 400, text: "Invalid or expired sign-in. Return to OpenMausBot and try again." };
443
521
  }
444
522
  }
445
- /** Both the listener and authenticated paste-back use the same one-shot checks. */
523
+ /** The listener, the public callback and authenticated paste-back all use the same one-shot checks. */
446
524
  async acceptCallback(flow, callback) {
447
525
  if (!this.isOwnerLive(flow.owner))
448
526
  this.finish(flow, "cancelled", "Your session ended. Start again.");
@@ -519,8 +597,8 @@ export class McpOAuthManager {
519
597
  if (flow.status.phase !== "waiting")
520
598
  return;
521
599
  clearTimeout(flow.expiry);
522
- flow.server.close();
523
- flow.server.closeIdleConnections();
600
+ flow.server?.close();
601
+ flow.server?.closeIdleConnections();
524
602
  flow.status = { ...flow.status, phase, authorizationUrl: null, ...(message ? { message } : {}) };
525
603
  }
526
604
  }
@@ -8,7 +8,13 @@ import { killCliTree, spawnCli } from "./procs.js";
8
8
  export const SIGN_IN_REQUIRED = "This server needs you to sign in.";
9
9
  const MAX_STDOUT_BYTES = 1_048_576;
10
10
  const MAX_TOOLS = 100;
11
- const DEFAULT_TIMEOUT_MS = 8_000;
11
+ /** A command starts on this computer and answers fast. */
12
+ export const STDIO_PROBE_TIMEOUT_MS = 8_000;
13
+ /** A URL server answers over the internet, and initialize + tools/list for
14
+ * an account with many businesses (Whop, admin scope) can take well over
15
+ * 8 seconds. The probe only runs when the person asks for it, never on a
16
+ * bot's turn. */
17
+ export const REMOTE_PROBE_TIMEOUT_MS = 30_000;
12
18
  function probeEnvironment(server) {
13
19
  const env = { ...process.env, PATH: augmentedPath() };
14
20
  stripWorkspaceCredentialEnv(env);
@@ -58,19 +64,20 @@ function publicTools(raw, secrets) {
58
64
  /** Prove the MCP handshake and list the tools of one configured server,
59
65
  * whichever way it is reached. Neither path returns anything the renderer
60
66
  * must not see: child stderr, environment values, header values. */
61
- export function probeMcpServer(server, timeoutMs = DEFAULT_TIMEOUT_MS, signal) {
67
+ export function probeMcpServer(server, timeoutMs, signal) {
62
68
  return isRemoteMcpServer(server)
63
- ? probeRemoteMcpServer(server, timeoutMs, signal)
64
- : probeStdioMcpServer(server, timeoutMs, signal);
69
+ ? probeRemoteMcpServer(server, timeoutMs ?? REMOTE_PROBE_TIMEOUT_MS, signal)
70
+ : probeStdioMcpServer(server, timeoutMs ?? STDIO_PROBE_TIMEOUT_MS, signal);
65
71
  }
66
- /** Connect to a remote server over its transport, bounded by the same
67
- * timeout as a command. HTTP status codes are safe to show and are the one
68
- * detail that tells a wrong token from a wrong address. */
72
+ /** Connect to a remote server over its transport, bounded by one timeout
73
+ * for initialize + tools/list together. HTTP status codes are safe to show
74
+ * and are the one detail that tells a wrong token from a wrong address. */
69
75
  async function probeRemoteMcpServer(server, timeoutMs, signal) {
70
76
  if (signal?.aborted)
71
77
  return { ok: false, error: publicProbeError("cancelled") };
72
- const timeout = AbortSignal.timeout(timeoutMs);
73
- const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
78
+ const timeout = new AbortController();
79
+ const timer = setTimeout(() => timeout.abort(), timeoutMs);
80
+ const combined = signal ? AbortSignal.any([signal, timeout.signal]) : timeout.signal;
74
81
  const client = new RemoteMcpClient(server);
75
82
  try {
76
83
  await client.initialize("OpenMausBot", combined);
@@ -84,7 +91,7 @@ async function probeRemoteMcpServer(server, timeoutMs, signal) {
84
91
  catch (error) {
85
92
  if (signal?.aborted)
86
93
  return { ok: false, error: publicProbeError("cancelled") };
87
- if (timeout.aborted)
94
+ if (timeout.signal.aborted)
88
95
  return { ok: false, error: publicProbeError("timeout") };
89
96
  // A 401 that names an OAuth sign-in is not a wrong address or header:
90
97
  // the person has to sign in. Anything else keeps the plain status.
@@ -105,6 +112,7 @@ async function probeRemoteMcpServer(server, timeoutMs, signal) {
105
112
  return { ok: false, error: "Could not reach this address. Check the URL and your network." };
106
113
  }
107
114
  finally {
115
+ clearTimeout(timer);
108
116
  await client.close().catch(() => { });
109
117
  }
110
118
  }
@@ -3,9 +3,9 @@
3
3
  // reader: the HTTP route supplies only roots derived from the exact bot
4
4
  // message, and this helper keeps the eventual file handle inside one of them.
5
5
  import { constants } from "node:fs";
6
- import { open, realpath, stat } from "node:fs/promises";
7
- import { basename, extname, isAbsolute, posix, relative, resolve, sep, win32 } from "node:path";
8
- import { fileURLToPath } from "node:url";
6
+ import { lstat, open, readdir, realpath, stat } from "node:fs/promises";
7
+ import { basename, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
9
  import { fromMarkdown } from "mdast-util-from-markdown";
10
10
  import { windowsPathDestinations } from "../shared/markdown-windows-paths.js";
11
11
  export const MESSAGE_FILE_MAX_BYTES = 25 * 1024 * 1024;
@@ -392,6 +392,8 @@ export async function openMessageFile(href, roots) {
392
392
  const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
393
393
  handle = await open(canonicalBefore, constants.O_RDONLY | noFollow);
394
394
  const opened = await handle.stat();
395
+ if (opened.isDirectory())
396
+ throw statusError(400, "the link points to a folder, not a file", "directory");
395
397
  if (!opened.isFile())
396
398
  throw statusError(400, "the link does not point to a regular file");
397
399
  if (opened.size > MESSAGE_FILE_MAX_BYTES) {
@@ -422,6 +424,96 @@ export async function openMessageFile(href, roots) {
422
424
  throw outsideWorkspace();
423
425
  throw statusError(404, "the linked file is unavailable");
424
426
  }
427
+ export const MESSAGE_FOLDER_MAX_ENTRIES = 200;
428
+ /**
429
+ * List the regular files directly inside a message-linked folder, under the
430
+ * same roots and canonical containment as openMessageFile. Subfolders,
431
+ * sockets and any entry that resolves outside the root are left out. Each
432
+ * entry is then opened by name through openMessageFile, which re-checks it.
433
+ */
434
+ export async function listMessageFolder(href, roots) {
435
+ const { path: _path, ...listing } = await readMessageFolder(href, roots);
436
+ return listing;
437
+ }
438
+ /** Open one file of a listed folder by its plain name, with every file check. */
439
+ export async function openMessageFolderEntry(href, entry, roots) {
440
+ const folder = await readMessageFolder(href, roots);
441
+ if (!folder.entries.some((listed) => listed.name === entry)) {
442
+ throw statusError(404, "the linked file is unavailable");
443
+ }
444
+ return openMessageFile(pathToFileURL(join(folder.path, entry)).href, roots);
445
+ }
446
+ async function readMessageFolder(href, roots) {
447
+ const requested = referencedPath(href);
448
+ const canonicalRoots = (await Promise.all(roots.map(async (root) => {
449
+ try {
450
+ const canonical = await realpath(root);
451
+ return (await stat(canonical)).isDirectory() ? canonical : null;
452
+ }
453
+ catch {
454
+ return null;
455
+ }
456
+ }))).filter((root) => Boolean(root));
457
+ if (canonicalRoots.length === 0)
458
+ throw statusError(404, "the linked folder is unavailable");
459
+ const candidates = isAbsolute(requested)
460
+ ? [resolve(requested)]
461
+ : canonicalRoots.map((root) => resolve(root, requested));
462
+ let sawOutsideRoot = false;
463
+ for (const candidate of new Set(candidates)) {
464
+ let folder;
465
+ try {
466
+ folder = await realpath(candidate);
467
+ }
468
+ catch {
469
+ continue;
470
+ }
471
+ const root = canonicalRoots.find((allowed) => containedBy(allowed, folder));
472
+ if (!root) {
473
+ sawOutsideRoot = true;
474
+ continue;
475
+ }
476
+ if (!(await stat(folder)).isDirectory())
477
+ throw statusError(400, "the link does not point to a folder");
478
+ const names = (await readdir(folder)).sort((a, b) => a.localeCompare(b));
479
+ const entries = [];
480
+ let truncated = false;
481
+ for (const name of names) {
482
+ const child = join(folder, name);
483
+ try {
484
+ const own = await lstat(child);
485
+ if (!own.isFile() && !own.isSymbolicLink())
486
+ continue;
487
+ const target = await realpath(child);
488
+ if (!containedBy(root, target))
489
+ continue;
490
+ const info = await stat(target);
491
+ if (!info.isFile())
492
+ continue;
493
+ if (entries.length >= MESSAGE_FOLDER_MAX_ENTRIES) {
494
+ truncated = true;
495
+ break;
496
+ }
497
+ entries.push({ name, bytes: info.size, mime: mimeFor(target) });
498
+ }
499
+ catch {
500
+ continue;
501
+ }
502
+ }
503
+ return { name: basename(folder), entries, truncated, path: folder };
504
+ }
505
+ if (sawOutsideRoot)
506
+ throw outsideWorkspace();
507
+ throw statusError(404, "the linked folder is unavailable");
508
+ }
509
+ /** One plain file name inside a listed folder; never a path or a dot entry. */
510
+ export function messageFolderEntryName(value) {
511
+ if (typeof value !== "string" || !value || value.length > 255)
512
+ return null;
513
+ if (value === "." || value === ".." || /[\\/\0]/.test(value))
514
+ return null;
515
+ return value;
516
+ }
425
517
  /** A safe attachment header with a readable ASCII fallback and UTF-8 name. */
426
518
  export function messageFileDisposition(name) {
427
519
  const safeName = safeDisplayName(name);
@@ -1,5 +1,6 @@
1
1
  import { harnessCapabilityLines } from "./harness-capabilities.js";
2
2
  import { newId } from "./contracts.js";
3
+ import { UNDO_STALE } from "./direct-apply.js";
3
4
  import { redactSecretsInText } from "./redact.js";
4
5
  const MAX_REASON = 500;
5
6
  const STALE = "This bot's default model changed after this card was prepared. Ask the bot to review it and propose again.";
@@ -128,10 +129,11 @@ export class ModelRequestService {
128
129
  before,
129
130
  };
130
131
  const copy = modelCardCopy({ targetName, crossBot, before, selection, reason, capabilityLines });
131
- const persistence = this.canPersist?.(args.botId, args.threadId);
132
+ const grant = submitted ? this.autoApply?.(args.botId, args.threadId, targetBotId) ?? null : null;
133
+ const automatic = grant !== null;
134
+ const persistence = this.canPersist?.(args.botId, args.threadId, !automatic);
132
135
  if (persistence && !persistence.ok)
133
136
  throw new ModelRequestError(persistence.error, persistence.status);
134
- const automatic = submitted && this.autoApply?.(args.botId, args.threadId) === true;
135
137
  const messageInput = {
136
138
  role: "bot",
137
139
  kind: "options",
@@ -152,10 +154,59 @@ export class ModelRequestService {
152
154
  if (!automatic)
153
155
  return proposal;
154
156
  const result = this.resolve({ botId: args.botId, threadId: args.threadId, requestId, behavior: "allow" });
155
- if (result.state === "applied")
156
- return { ...proposal, result };
157
+ if (result.state === "applied") {
158
+ if (!result.settlementPending)
159
+ this.recordAutoApplied(args.threadId, message.id);
160
+ return { ...proposal, result, appliedBy: grant };
161
+ }
157
162
  throw new ModelRequestError(result.state === "invalid" ? result.error : "The model change could not be applied", result.state === "invalid" ? result.status : 409);
158
163
  }
164
+ /** Marks a card whose change applied without a person. A failure here only
165
+ * loses the Undo. */
166
+ recordAutoApplied(threadId, messageId) {
167
+ try {
168
+ const card = this.store.messagesFor(threadId).find((candidate) => candidate.id === messageId)?.card;
169
+ if (card?.modelRequest)
170
+ this.store.patchMessage(threadId, messageId, { card: { ...card, autoApplied: true } });
171
+ }
172
+ catch {
173
+ // The model change itself is durable; only its Undo is lost.
174
+ }
175
+ }
176
+ /** Puts the previous default model back after a change that applied
177
+ * without a person, through the same checks a switch gets (validateModel:
178
+ * a pending approval-level change, or permissions the old engine cannot
179
+ * keep, refuse it). Refuses, applying nothing, once the default moved. */
180
+ undo(args) {
181
+ const message = this.store
182
+ .messagesFor(args.threadId)
183
+ .find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.modelRequest);
184
+ const card = message?.card;
185
+ const payload = card?.modelRequest;
186
+ if (!message || !card || !payload)
187
+ return { claimed: false };
188
+ if (card.undone)
189
+ return { claimed: true, state: "already_undone" };
190
+ const cannot = (error, status = 409) => ({ claimed: true, state: "invalid", error, status });
191
+ if (!card.autoApplied || card.answered !== "allow")
192
+ return cannot("Only a change that applied on its own can be undone here.");
193
+ if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
194
+ return cannot("This model change belongs to another conversation", 403);
195
+ }
196
+ const target = this.store.bot(payload.targetBotId);
197
+ if (!target || !sameSelection(target.modelSelection, payload.selection)) {
198
+ return { claimed: true, state: "invalid", error: UNDO_STALE, status: 409, stale: true };
199
+ }
200
+ if (this.validateModel) {
201
+ const refusal = this.validateModel(payload.before, target);
202
+ if (refusal)
203
+ return cannot(refusal);
204
+ }
205
+ if (!this.store.applyModelDefault(target.id, payload.before))
206
+ return cannot(NO_SUCH_BOT, 404);
207
+ this.store.patchMessage(args.threadId, message.id, { card: { ...card, undone: true } });
208
+ return { claimed: true, state: "undone", targetBotId: target.id };
209
+ }
159
210
  /** Claims a model card even after it was settled, so a duplicate click
160
211
  * never re-applies an already-applied change. */
161
212
  resolve(args) {
@@ -11,6 +11,7 @@ import { PROFILE_REQUEST_FIELDS } from "../shared/profile-request.js";
11
11
  import { parseBotProfilePatch } from "./bot-profile.js";
12
12
  import { validateBotCwd } from "./bot-cwd.js";
13
13
  import { newId } from "./contracts.js";
14
+ import { UNDO_STALE } from "./direct-apply.js";
14
15
  import { profileRevision, profileSnapshot } from "./profile-revision.js";
15
16
  import { recordProfileChange } from "./profile-versions.js";
16
17
  import { redactSecretsInText } from "./redact.js";
@@ -205,6 +206,9 @@ export class ProfileRequestService {
205
206
  const snapshot = profileSnapshot(target);
206
207
  const before = {};
207
208
  const finalChanges = {};
209
+ // Undo restores `before`, so it is offered only when the card holds the
210
+ // old values exactly (credential-shaped text is scrubbed from cards).
211
+ let exactBefore = true;
208
212
  for (const field of PROFILE_REQUEST_FIELDS) {
209
213
  if (field === "notifications" || field === "speakReplies") {
210
214
  const value = changes[field];
@@ -218,6 +222,8 @@ export class ProfileRequestService {
218
222
  if (value === undefined || value === snapshot[field])
219
223
  continue;
220
224
  before[field] = redactSecretsInText(snapshot[field]);
225
+ if (before[field] !== snapshot[field])
226
+ exactBefore = false;
221
227
  finalChanges[field] = value;
222
228
  }
223
229
  }
@@ -240,11 +246,14 @@ export class ProfileRequestService {
240
246
  expectedRevision: profileRevision(target),
241
247
  };
242
248
  const copy = profileCardCopy({ name: targetName, crossBot }, snapshot, before, finalChanges, reason);
243
- const persistence = this.canPersist?.(args.botId, args.threadId);
249
+ const grant = submitted ? this.autoApply?.(args.botId, args.threadId, targetBotId) ?? null : null;
250
+ // A new working folder widens what the bot's tools read and write
251
+ // without asking, so below Full access it keeps today's card.
252
+ const automatic = grant === "full-access" || (grant === "self" && finalChanges.cwd === undefined);
253
+ const persistence = this.canPersist?.(args.botId, args.threadId, !automatic);
244
254
  if (persistence && !persistence.ok) {
245
255
  throw new ProfileRequestError(persistence.error, persistence.status);
246
256
  }
247
- const automatic = submitted && this.autoApply?.(args.botId, args.threadId) === true;
248
257
  const messageInput = {
249
258
  role: "bot",
250
259
  kind: "options",
@@ -267,10 +276,77 @@ export class ProfileRequestService {
267
276
  // The hidden receipt is persisted before the profile changes. The same
268
277
  // validation and durable commit marker serve both automatic and human decisions.
269
278
  const result = this.resolve({ botId: args.botId, threadId: args.threadId, requestId, behavior: "allow" });
270
- if (result.state === "applied")
271
- return { ...proposal, result };
279
+ if (result.state === "applied") {
280
+ if (!result.settlementPending)
281
+ this.recordAutoApplied(args.threadId, message.id, targetBotId, exactBefore);
282
+ return { ...proposal, result, appliedBy: grant };
283
+ }
272
284
  throw new ProfileRequestError(result.state === "invalid" ? result.error : "The profile change could not be applied", result.state === "invalid" ? result.status : 409);
273
285
  }
286
+ /** Marks a card whose change applied without a person, with the revision
287
+ * its Undo checks. A failure here only loses the Undo. */
288
+ recordAutoApplied(threadId, messageId, targetBotId, exactBefore) {
289
+ try {
290
+ const card = this.store.messagesFor(threadId).find((candidate) => candidate.id === messageId)?.card;
291
+ const target = this.store.bot(targetBotId);
292
+ if (!card?.profileRequest || !target)
293
+ return;
294
+ this.store.patchMessage(threadId, messageId, {
295
+ card: {
296
+ ...card,
297
+ autoApplied: true,
298
+ profileRequest: { ...card.profileRequest, ...(exactBefore ? { undo: { appliedRevision: profileRevision(target) } } : {}) },
299
+ },
300
+ });
301
+ }
302
+ catch {
303
+ // The profile change itself is durable; only its Undo is lost.
304
+ }
305
+ }
306
+ /** Puts back the fields a change that applied without a person changed.
307
+ * Authorized by the caller exactly like answering the card; refuses,
308
+ * applying nothing, once the profile changed since. */
309
+ undo(args) {
310
+ const message = this.store
311
+ .messagesFor(args.threadId)
312
+ .find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.profileRequest);
313
+ const card = message?.card;
314
+ const payload = card?.profileRequest;
315
+ if (!message || !card || !payload)
316
+ return { claimed: false };
317
+ if (card.undone)
318
+ return { claimed: true, state: "already_undone" };
319
+ const cannot = (error, status = 409) => ({ claimed: true, state: "invalid", error, status });
320
+ if (!card.autoApplied || card.answered !== "allow")
321
+ return cannot("Only a change that applied on its own can be undone here.");
322
+ if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
323
+ return cannot("This profile change belongs to another conversation", 403);
324
+ }
325
+ if (!payload.undo)
326
+ return cannot("This change can't be undone here.");
327
+ const target = this.store.bot(payload.targetBotId);
328
+ if (!target || profileRevision(target) !== payload.undo.appliedRevision) {
329
+ return { claimed: true, state: "invalid", error: UNDO_STALE, status: 409, stale: true };
330
+ }
331
+ const { cwd, ...rest } = payload.before;
332
+ const patch = {};
333
+ for (const field of PROFILE_REQUEST_FIELDS) {
334
+ if (field === "cwd" || payload.changes[field] === undefined)
335
+ continue;
336
+ patch[field] = rest[field];
337
+ }
338
+ if (payload.changes.cwd !== undefined) {
339
+ const checked = validateBotCwd(cwd || null);
340
+ if (!checked.ok)
341
+ return cannot(checked.error);
342
+ patch.cwd = checked.cwd ?? undefined;
343
+ }
344
+ if (!this.store.patchBotProfile(target.id, patch))
345
+ return cannot(NO_SUCH_BOT, 404);
346
+ recordProfileChange(target.id, "user", `undo:${message.id}`, payload.changes, payload.before);
347
+ this.store.patchMessage(args.threadId, message.id, { card: { ...card, undone: true } });
348
+ return { claimed: true, state: "undone", targetBotId: target.id };
349
+ }
274
350
  /** Claims a profile card even after it was settled, so a duplicate click
275
351
  * never re-applies an already-applied change. */
276
352
  resolve(args) {
@@ -283,6 +283,7 @@ export const CLIENT_ALLOW = [
283
283
  // approvals and cards
284
284
  { methods: ["POST"], path: /^\/api\/bots\/[\w-]+\/respond$/ },
285
285
  { methods: ["POST"], path: /^\/api\/threads\/[\w-]+\/respond$/ },
286
+ { methods: ["POST"], path: /^\/api\/threads\/[\w-]+\/undo$/ }, // whoever may answer the card: see the handler
286
287
  { methods: ["PATCH"], path: /^\/api\/bots\/[\w-]+\/cards\/[\w-]+$/ },
287
288
  { methods: ["POST"], path: /^\/api\/bots\/[\w-]+\/secret-cards\/[\w-]+\/(?:resume|dismiss)$/ },
288
289
  { methods: ["GET"], path: /^\/api\/bots\/[\w-]+\/connector-cards\/[\w-]+\/status$/ },