openmausbot 0.1.97 → 0.1.99

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 (434) hide show
  1. package/dist/assets/ActivityPanel-CgUgtumZ.js +1 -0
  2. package/dist/assets/BotSettingsDialog-lJj6oiir.js +7 -0
  3. package/dist/assets/CloudBackendPicker-C5-_arUz.js +1 -0
  4. package/dist/assets/ComputerPanel-euJ2CSlH.js +1 -0
  5. package/dist/assets/DesktopViewer-XCln1Pn_.js +5 -0
  6. package/dist/assets/InspectorPanel-DKYyQZlo.js +5 -0
  7. package/dist/assets/KeyboardShortcutsModal-DkkZWZRI.js +1 -0
  8. package/dist/assets/LocalVmWorkspace-Cwt3_XSx.js +1 -0
  9. package/dist/assets/MacLocalControl-B28RbdCS.js +1 -0
  10. package/dist/assets/NewBotDialog-dMWDs5xm.js +2 -0
  11. package/dist/assets/RemoteAgentSettingsPanel-B5KWXnqH.js +1 -0
  12. package/dist/assets/RoutinesPage-DOosRYDK.js +1 -0
  13. package/dist/assets/RoutinesSection-uOfOTfkB.js +1 -0
  14. package/dist/assets/SettingsModal-BTGkFbj1.js +11 -0
  15. package/dist/assets/TeamMapPage-DqNdJn96.js +8 -0
  16. package/dist/assets/TriggersPanel-c6xkG9mi.js +1 -0
  17. package/dist/assets/VisibilitySection-DD4ffIR5.js +12 -0
  18. package/dist/assets/WebhooksPanel-Cz17K-_O.js +3 -0
  19. package/dist/assets/{abnfDiagram-O67JEVCF-k1UCNXb7.js → abnfDiagram-O67JEVCF-CX6tDpkg.js} +1 -1
  20. package/dist/assets/activity-BAVWO0Zj.js +1 -0
  21. package/dist/assets/{arc-BUWGa9UU.js → arc-h45-9NFk.js} +1 -1
  22. package/dist/assets/architectureDiagram-NJMV4G6O-CgLFXbg6.js +36 -0
  23. package/dist/assets/{blockDiagram-BEXU5L5S-Ag9nLW4H.js → blockDiagram-BEXU5L5S-DGDFFc9M.js} +1 -1
  24. package/dist/assets/brain-CeCdoTb2.js +1 -0
  25. package/dist/assets/{c4Diagram-YGBWAQC7-Bh0sjgUa.js → c4Diagram-YGBWAQC7-B9aklAUj.js} +1 -1
  26. package/dist/assets/channel-TsOXCiPg.js +1 -0
  27. package/dist/assets/{chunk-2Q5K7J3B-BFBcIIqu.js → chunk-2Q5K7J3B-BC0KmWsM.js} +1 -1
  28. package/dist/assets/{chunk-3FUC2YCW-Dvfpex-f.js → chunk-3FUC2YCW-BbDFHsJ1.js} +2 -2
  29. package/dist/assets/{chunk-5VM5RSS4-CMjP2YZW.js → chunk-5VM5RSS4-DtGbsjOM.js} +1 -1
  30. package/dist/assets/{chunk-GTNCS2PH-BryoeTc7.js → chunk-GTNCS2PH-VMghMcEl.js} +1 -1
  31. package/dist/assets/{chunk-GWA4HPMP-BhfvcchO.js → chunk-GWA4HPMP-BP_JyGWQ.js} +1 -1
  32. package/dist/assets/{chunk-JWPE2WC7-bcCVp8rp.js → chunk-JWPE2WC7-CYFyzgAU.js} +1 -1
  33. package/dist/assets/{chunk-NETBCI7D-yKIjIlkQ.js → chunk-NETBCI7D-DBNa_kqm.js} +1 -1
  34. package/dist/assets/{chunk-WEXAMYUT-D3Zqbmup.js → chunk-WEXAMYUT-BKEeM7CJ.js} +1 -1
  35. package/dist/assets/{chunk-XXDRQBXY-CjVoIMG6.js → chunk-XXDRQBXY-C__m3ZXl.js} +1 -1
  36. package/dist/assets/{classDiagram-v2-NBCMYWYE-mUCWdVyH.js → classDiagram-v2-NBCMYWYE-CBUrTSAX.js} +1 -1
  37. package/dist/assets/computer-control-NLov352p.js +1 -0
  38. package/dist/assets/core-6lvx-ypA.js +12 -0
  39. package/dist/assets/cose-bilkent-JH36ORCC-GRfL-1Vb.js +1 -0
  40. package/dist/assets/cpp-DddhVWhK.js +1 -0
  41. package/dist/assets/{cynefin-OW5HDTMX-BpRrxoO4.js → cynefin-OW5HDTMX-LopwGUjm.js} +1 -1
  42. package/dist/assets/{cynefinDiagram-VND7K2PF-D3VBWCPK.js → cynefinDiagram-VND7K2PF-DSB8ZRNS.js} +1 -1
  43. package/dist/assets/{dagre-6A5THRUB-DxEIACOO.js → dagre-6A5THRUB-DL3Kgdfk.js} +1 -1
  44. package/dist/assets/{diagram-22UHCM2B-BANgZEOS.js → diagram-22UHCM2B-DIxN34BM.js} +1 -1
  45. package/dist/assets/{diagram-3UASUU5V-Cnq7nJir.js → diagram-3UASUU5V-CJnSvXaI.js} +1 -1
  46. package/dist/assets/{diagram-ATOU4E4O-4TBDIWnz.js → diagram-ATOU4E4O-C-XF-lO8.js} +1 -1
  47. package/dist/assets/{diagram-CDSNMT55-CD_B39D4.js → diagram-CDSNMT55-Df2yZPi_.js} +1 -1
  48. package/dist/assets/{diagram-MLGK6HIB-CzvnFhEC.js → diagram-MLGK6HIB-BBs9oZFs.js} +1 -1
  49. package/dist/assets/{diagram-MPIPVDR6-C4w2SNml.js → diagram-MPIPVDR6-CEbK11mM.js} +1 -1
  50. package/dist/assets/{ebnfDiagram-ZINNZB2B-gkUEBm2s.js → ebnfDiagram-ZINNZB2B-BNq6Yv3C.js} +1 -1
  51. package/dist/assets/{elk-276RUBZZ-B_56X6mw.js → elk-276RUBZZ-CFbQ2bGm.js} +1 -1
  52. package/dist/assets/engine-javascript-D5Xfc425.js +141 -0
  53. package/dist/assets/{erDiagram-OPXOYQCR-C_oByoDS.js → erDiagram-OPXOYQCR-Bz8vmjN4.js} +1 -1
  54. package/dist/assets/{flowDiagram-KWPJA3E3-CR9xXJkC.js → flowDiagram-KWPJA3E3-cQUfEYrx.js} +1 -1
  55. package/dist/assets/ganttDiagram-FUAMR5RP-BY8n9tbI.js +292 -0
  56. package/dist/assets/{gitGraphDiagram-X574FWY7-VJ6-Tp_p.js → gitGraphDiagram-X574FWY7-KduSOMDN.js} +1 -1
  57. package/dist/assets/image-plus-DNIWU160.js +1 -0
  58. package/dist/assets/index-C8xb62XS.js +585 -0
  59. package/dist/assets/index-DbN_Hfjs.css +1 -0
  60. package/dist/assets/{infoDiagram-VRGFBTTK-2Lu9te4D.js → infoDiagram-VRGFBTTK-xBKgUkpL.js} +1 -1
  61. package/dist/assets/{ishikawaDiagram-OU5B5YK6-DR49XY-e.js → ishikawaDiagram-OU5B5YK6-_ei09tAJ.js} +1 -1
  62. package/dist/assets/{journeyDiagram-ZHPQQLJL-ZoR0M305.js → journeyDiagram-ZHPQQLJL-Fp49MkSp.js} +1 -1
  63. package/dist/assets/{kanban-definition-PNTS6WVX-DLKHxrO-.js → kanban-definition-PNTS6WVX-CY9lLm7V.js} +1 -1
  64. package/dist/assets/{linear-BASsfWly.js → linear-D2ai4eM2.js} +1 -1
  65. package/dist/assets/{mermaid.core-BVidiPkk.js → mermaid.core-DP6qPqu1.js} +5 -5
  66. package/dist/assets/{mindmap-definition-NLK3R4M7-g_Qx0U5C.js → mindmap-definition-NLK3R4M7-g1ynEw_d.js} +1 -1
  67. package/dist/assets/module-DCUsy2BQ.js +3 -0
  68. package/dist/assets/page-visible-BZn8304W.js +1 -0
  69. package/dist/assets/{pegDiagram-GJSIUBJH-DI9Cjs8w.js → pegDiagram-GJSIUBJH-Bt4ObsZj.js} +1 -1
  70. package/dist/assets/{pieDiagram-5QR66LMP-sRI0Uhr5.js → pieDiagram-5QR66LMP-CeLld8bY.js} +1 -1
  71. package/dist/assets/{quadrantDiagram-O4NWA36T-CamKTby3.js → quadrantDiagram-O4NWA36T-D4WWZw3x.js} +1 -1
  72. package/dist/assets/{railroadDiagram-XR7U4H2S-DOLlkbf3.js → railroadDiagram-XR7U4H2S-D95P5NqU.js} +1 -1
  73. package/dist/assets/remote-desktop-panel-DOIMmT4T.js +1 -0
  74. package/dist/assets/{requirementDiagram-PLB6GJNP-DrRQ5r97.js → requirementDiagram-PLB6GJNP-D12p3RhR.js} +1 -1
  75. package/dist/assets/ruby-Ct1eTYOr.js +1 -0
  76. package/dist/assets/{sankeyDiagram-IPEJSGJF-DGS_lXcX.js → sankeyDiagram-IPEJSGJF-Co26Dwcs.js} +1 -1
  77. package/dist/assets/{sequenceDiagram-PO4LG4MO-CkjWqwtf.js → sequenceDiagram-PO4LG4MO-y5YxaDKt.js} +1 -1
  78. package/dist/assets/{sizeCapture-INFHLROL-CepRgful.js → sizeCapture-INFHLROL-D49hZ8U5.js} +1 -1
  79. package/dist/assets/{stateDiagram-v2-GCMORJYK-hykr-9RS.js → stateDiagram-v2-GCMORJYK-C0sylr-r.js} +1 -1
  80. package/dist/assets/{swimlanes-2SLR337P-CKOHEZbI.js → swimlanes-2SLR337P-DmRWsDIs.js} +1 -1
  81. package/dist/assets/swimlanesDiagram-TC7HE7FX-DzhbBHtd.js +8 -0
  82. package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
  83. package/dist/assets/{timeline-definition-EJHVYXUP-CB8pJ6AC.js → timeline-definition-EJHVYXUP-DVmHYs3w.js} +1 -1
  84. package/dist/assets/{usecaseDiagram-POWQR4AR-DrclJHdl.js → usecaseDiagram-POWQR4AR-BQ1q0VV6.js} +1 -1
  85. package/dist/assets/user-B9lohN4n.js +1 -0
  86. package/dist/assets/{vennDiagram-UO4OBE2U-zS23qjv8.js → vennDiagram-UO4OBE2U-C_cnfST0.js} +1 -1
  87. package/dist/assets/{wardleyDiagram-VNRHLVJA-52jgeKxz.js → wardleyDiagram-VNRHLVJA-Bb3BSu12.js} +1 -1
  88. package/dist/assets/{xychartDiagram-PMCCYNJV-DSdl9rQ8.js → xychartDiagram-PMCCYNJV-CcwEuuEi.js} +1 -1
  89. package/dist/index.html +2 -2
  90. package/dist-server/cloud-home-start.js +19 -8
  91. package/dist-server/container-mcp.js +8 -0
  92. package/dist-server/desktop-entry.mjs +54 -0
  93. package/dist-server/drivers/agents-proxy.js +30 -12
  94. package/dist-server/index.js +2745 -1487
  95. package/dist-server/openmausbot.js +1004 -642
  96. package/dist-server/pair-cli.js +1004 -642
  97. package/dist-server/permission-proxy.js +2 -0
  98. package/dist-server/server/activity.js +2 -0
  99. package/dist-server/server/atomic.js +70 -2
  100. package/dist-server/server/checkpoints.js +55 -39
  101. package/dist-server/server/cloud-home-start.js +3 -3
  102. package/dist-server/server/config.js +22 -1
  103. package/dist-server/server/connector-card-text.js +15 -0
  104. package/dist-server/server/default-model-selection.js +7 -4
  105. package/dist-server/server/direct-apply.js +20 -0
  106. package/dist-server/server/drivers/acp/core.js +10 -0
  107. package/dist-server/server/drivers/acp/grok.js +47 -5
  108. package/dist-server/server/drivers/agents-call.js +1 -1
  109. package/dist-server/server/drivers/agents-catalog.js +34 -11
  110. package/dist-server/server/drivers/agents-proxy.js +1 -0
  111. package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
  112. package/dist-server/server/drivers/claude-login-auth.js +1 -1
  113. package/dist-server/server/drivers/claude.js +35 -11
  114. package/dist-server/server/drivers/codex-device-auth.js +27 -319
  115. package/dist-server/server/drivers/codex.js +147 -24
  116. package/dist-server/server/drivers/device-auth.js +343 -0
  117. package/dist-server/server/drivers/openai-chat.js +17 -8
  118. package/dist-server/server/fleet-agent.js +18 -5
  119. package/dist-server/server/harness/http.js +5 -2
  120. package/dist-server/server/harness/registry.js +82 -45
  121. package/dist-server/server/harness-capabilities.js +7 -4
  122. package/dist-server/server/hosted-models.js +3 -1
  123. package/dist-server/server/index.js +471 -77
  124. package/dist-server/server/key-rejections.js +57 -0
  125. package/dist-server/server/mcp-oauth.js +125 -47
  126. package/dist-server/server/message-file.js +102 -7
  127. package/dist-server/server/model-requests.js +55 -4
  128. package/dist-server/server/permission-proxy.js +4 -0
  129. package/dist-server/server/profile-requests.js +80 -4
  130. package/dist-server/server/provider-key-check.js +5 -1
  131. package/dist-server/server/request-auth.js +2 -1
  132. package/dist-server/server/room-turn-timeout.js +28 -0
  133. package/dist-server/server/routes/thread-models.js +36 -0
  134. package/dist-server/server/routes/undo.js +17 -0
  135. package/dist-server/server/routine-requests.js +210 -6
  136. package/dist-server/server/routines.js +2 -2
  137. package/dist-server/server/setup-mode.js +1 -1
  138. package/dist-server/server/skill-learn.js +2 -2
  139. package/dist-server/server/skills.js +33 -2
  140. package/dist-server/server/store.js +148 -60
  141. package/dist-server/server/system-prompt.js +1 -1
  142. package/dist-server/server/team-backup.js +19 -4
  143. package/dist-server/server/thread-model.js +25 -0
  144. package/dist-server/server/turn-continuation.js +17 -16
  145. package/dist-server/server/webhook-ingress.js +8 -3
  146. package/dist-server/server/webhooks.js +40 -5
  147. package/dist-server/server/workspace-backup.js +6 -1
  148. package/dist-server/shared/device-sign-in.js +57 -0
  149. package/dist-server/shared/team-backup.js +2 -0
  150. package/dist-server/shared/thread-model.js +13 -0
  151. package/dist-server/vps-container-mcp.js +8 -0
  152. package/dist-server/workspace-backup.worker.js +42 -23
  153. package/package.json +1 -1
  154. package/skills/create-verification-skill/SKILL.md +9 -8
  155. package/skills/create-verification-skill/manifest.json +1 -1
  156. package/dist/assets/DesktopViewer-CPidMCtt.js +0 -5
  157. package/dist/assets/abap-BdImnpbu.js +0 -1
  158. package/dist/assets/actionscript-3-B3316cI-.js +0 -1
  159. package/dist/assets/ada-bCR0ucgS.js +0 -1
  160. package/dist/assets/ahk-CsyLZFj1.js +0 -1
  161. package/dist/assets/ahk2-8Zs4aa1G.js +0 -1
  162. package/dist/assets/andromeeda-C4gqWexZ.js +0 -1
  163. package/dist/assets/angular-html-DA-rfuFy.js +0 -1
  164. package/dist/assets/angular-ts-BrjP3tb8.js +0 -1
  165. package/dist/assets/apache-Pmp26Uib.js +0 -1
  166. package/dist/assets/apex-DhZFqWV2.js +0 -1
  167. package/dist/assets/apl-CORt7UWP.js +0 -1
  168. package/dist/assets/applescript-Co6uUVPk.js +0 -1
  169. package/dist/assets/ara-BRHolxvo.js +0 -1
  170. package/dist/assets/architectureDiagram-NJMV4G6O-DiPor3UT.js +0 -36
  171. package/dist/assets/asciidoc-CSVQ5wI8.js +0 -1
  172. package/dist/assets/asm-D_Q5rh1f.js +0 -1
  173. package/dist/assets/astro-HNnZUWAn.js +0 -1
  174. package/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
  175. package/dist/assets/awk-DMzUqQB5.js +0 -1
  176. package/dist/assets/ayu-dark-DYE7WIF3.js +0 -1
  177. package/dist/assets/ayu-light-BA47KaF1.js +0 -1
  178. package/dist/assets/ayu-mirage-32ctXXKs.js +0 -1
  179. package/dist/assets/ballerina-BFfxhgS-.js +0 -1
  180. package/dist/assets/bat-CickPsom.js +0 -1
  181. package/dist/assets/beancount-k_qm7-4y.js +0 -1
  182. package/dist/assets/berry-uYugtg8r.js +0 -1
  183. package/dist/assets/bibtex-CHM0blh-.js +0 -1
  184. package/dist/assets/bicep-Bmn6On1c.js +0 -1
  185. package/dist/assets/bird2-Bx8U0n9b.js +0 -1
  186. package/dist/assets/blade-2xfisSek.js +0 -1
  187. package/dist/assets/bsl-DlhNcFeZ.js +0 -1
  188. package/dist/assets/c3-Dp5svz6Z.js +0 -1
  189. package/dist/assets/cadence-Bv_4Rxtq.js +0 -1
  190. package/dist/assets/cairo-KRGpt6FW.js +0 -1
  191. package/dist/assets/catppuccin-frappe-CZL1YF0i.js +0 -1
  192. package/dist/assets/catppuccin-latte-DH-8KZSZ.js +0 -1
  193. package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +0 -1
  194. package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +0 -1
  195. package/dist/assets/channel-DR8eD6QW.js +0 -1
  196. package/dist/assets/chapel-DTp_pixX.js +0 -1
  197. package/dist/assets/clarity-Dn5IMItf.js +0 -1
  198. package/dist/assets/clojure-P80f7IUj.js +0 -1
  199. package/dist/assets/cmake-D1j8_8rp.js +0 -1
  200. package/dist/assets/cobol-nBiQ_Alo.js +0 -1
  201. package/dist/assets/codeowners-Bp6g37R7.js +0 -1
  202. package/dist/assets/codeql-DsOJ9woJ.js +0 -1
  203. package/dist/assets/coffee-Ch7k5sss.js +0 -1
  204. package/dist/assets/common-lisp-Cg-RD9OK.js +0 -1
  205. package/dist/assets/coq-C7JzOVbR.js +0 -1
  206. package/dist/assets/cose-bilkent-JH36ORCC-BBmR1bbX.js +0 -1
  207. package/dist/assets/cpp-BMRokrvK.js +0 -1
  208. package/dist/assets/crystal-DGywbUpC.js +0 -1
  209. package/dist/assets/csv-fuZLfV_i.js +0 -1
  210. package/dist/assets/cue-D82EKSYY.js +0 -1
  211. package/dist/assets/cypher-COkxafJQ.js +0 -1
  212. package/dist/assets/d-85-TOEBH.js +0 -1
  213. package/dist/assets/dark-plus-C3mMm8J8.js +0 -1
  214. package/dist/assets/dax-CEL-wOlO.js +0 -1
  215. package/dist/assets/desktop-BmXAJ9_W.js +0 -1
  216. package/dist/assets/dotenv-Da5cRb03.js +0 -1
  217. package/dist/assets/dracula-BzJJZx-M.js +0 -1
  218. package/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
  219. package/dist/assets/dream-maker-BtqSS_iP.js +0 -1
  220. package/dist/assets/edge-FbVlp4U3.js +0 -1
  221. package/dist/assets/elixir-CkH2-t6x.js +0 -1
  222. package/dist/assets/elm-DbKCFpqz.js +0 -1
  223. package/dist/assets/emacs-lisp-C_m_b--Z.js +0 -1
  224. package/dist/assets/erb-DXfck5VN.js +0 -1
  225. package/dist/assets/erlang-DsQrWhSR.js +0 -1
  226. package/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
  227. package/dist/assets/everforest-light-C8M2exoo.js +0 -1
  228. package/dist/assets/fennel-BYunw83y.js +0 -1
  229. package/dist/assets/fluent-C4IJs8-o.js +0 -1
  230. package/dist/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
  231. package/dist/assets/fortran-free-form-BxgE0vQu.js +0 -1
  232. package/dist/assets/fsharp-CXgrBDvD.js +0 -1
  233. package/dist/assets/ganttDiagram-FUAMR5RP-gtYBTu0B.js +0 -292
  234. package/dist/assets/gdresource-TyuKm33G.js +0 -1
  235. package/dist/assets/gdscript-DqcFQ5yU.js +0 -1
  236. package/dist/assets/gdshader-DkwncUOv.js +0 -1
  237. package/dist/assets/genie-D0YGMca9.js +0 -1
  238. package/dist/assets/gherkin-DyxjwDmM.js +0 -1
  239. package/dist/assets/git-commit-F4YmCXRG.js +0 -1
  240. package/dist/assets/git-rebase-r7XF79zn.js +0 -1
  241. package/dist/assets/github-dark-DHJKELXO.js +0 -1
  242. package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
  243. package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
  244. package/dist/assets/github-light-DAi9KRSo.js +0 -1
  245. package/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
  246. package/dist/assets/gleam-BspZqrRM.js +0 -1
  247. package/dist/assets/glimmer-js-ByusRIyA.js +0 -1
  248. package/dist/assets/glimmer-ts-BfAWNZQY.js +0 -1
  249. package/dist/assets/glsl-DplSGwfg.js +0 -1
  250. package/dist/assets/gn-n2N0HUVH.js +0 -1
  251. package/dist/assets/gnuplot-DdkO51Og.js +0 -1
  252. package/dist/assets/groovy-gcz8RCvz.js +0 -1
  253. package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
  254. package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
  255. package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
  256. package/dist/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
  257. package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
  258. package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
  259. package/dist/assets/hack-BWmVpMyf.js +0 -1
  260. package/dist/assets/haml-D5jkg6IW.js +0 -1
  261. package/dist/assets/handlebars-BpdQsYii.js +0 -1
  262. package/dist/assets/haskell-Df6bDoY_.js +0 -1
  263. package/dist/assets/haxe-CfZj7gIn.js +0 -1
  264. package/dist/assets/hcl-BWvSN4gD.js +0 -1
  265. package/dist/assets/hjson-D5-asLiD.js +0 -1
  266. package/dist/assets/hlsl-D3lLCCz7.js +0 -1
  267. package/dist/assets/horizon-BUw7H-hv.js +0 -1
  268. package/dist/assets/horizon-bright-CUuTKBJd.js +0 -1
  269. package/dist/assets/houston-DnULxvSX.js +0 -1
  270. package/dist/assets/html-derivative-DlHx6ybY.js +0 -1
  271. package/dist/assets/http-jrhK8wxY.js +0 -1
  272. package/dist/assets/hurl-irOxFIW8.js +0 -1
  273. package/dist/assets/hxml-2-FPmUDs.js +0 -1
  274. package/dist/assets/hy-DFXneXwc.js +0 -1
  275. package/dist/assets/imba-DGztddWO.js +0 -1
  276. package/dist/assets/index-Cp4S4NOL.js +0 -153
  277. package/dist/assets/index-CpaIXRLp.js +0 -614
  278. package/dist/assets/index-Z81SMsgR.css +0 -1
  279. package/dist/assets/ini-BEwlwnbL.js +0 -1
  280. package/dist/assets/jinja-f2NsQr07.js +0 -1
  281. package/dist/assets/jison-wvAkD_A8.js +0 -1
  282. package/dist/assets/jsonl-DcaNXYhu.js +0 -1
  283. package/dist/assets/jsonnet-DFQXde-d.js +0 -1
  284. package/dist/assets/jssm-C2t-YnRu.js +0 -1
  285. package/dist/assets/julia-5Bft2YPA.js +0 -1
  286. package/dist/assets/just-Cwhn7H3k.js +0 -1
  287. package/dist/assets/kanagawa-dragon-BuwD2xS4.js +0 -1
  288. package/dist/assets/kanagawa-lotus-C3DzagqV.js +0 -1
  289. package/dist/assets/kanagawa-wave-DFGoQZhC.js +0 -1
  290. package/dist/assets/kdl-DV7GczEv.js +0 -1
  291. package/dist/assets/kusto-wEQ09or8.js +0 -1
  292. package/dist/assets/laserwave-DUszq2jm.js +0 -1
  293. package/dist/assets/latex-D5pSuvFb.js +0 -1
  294. package/dist/assets/lean-BZvkOJ9d.js +0 -1
  295. package/dist/assets/light-plus-B7mTdjB0.js +0 -1
  296. package/dist/assets/liquid-C0sCDyMI.js +0 -1
  297. package/dist/assets/llvm-BZoOZj88.js +0 -1
  298. package/dist/assets/log-2UxHyX5q.js +0 -1
  299. package/dist/assets/logo-BtOb2qkB.js +0 -1
  300. package/dist/assets/luau-BnpPk5vE.js +0 -1
  301. package/dist/assets/marko-DjSrsDqO.js +0 -1
  302. package/dist/assets/material-theme-D5KoaKCx.js +0 -1
  303. package/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
  304. package/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
  305. package/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
  306. package/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
  307. package/dist/assets/matlab-D7o27uSR.js +0 -1
  308. package/dist/assets/mdc-D1_yUvq7.js +0 -1
  309. package/dist/assets/mermaid-CQcHuHx7.js +0 -1
  310. package/dist/assets/min-dark-CafNBF8u.js +0 -1
  311. package/dist/assets/min-light-CTRr51gU.js +0 -1
  312. package/dist/assets/mipsasm-CKIfxQSi.js +0 -1
  313. package/dist/assets/mojo-DJz3ZmWd.js +0 -1
  314. package/dist/assets/monokai-BRVnQi9A.js +0 -1
  315. package/dist/assets/moonbit-CHtswR0a.js +0 -1
  316. package/dist/assets/move-el3G9tDJ.js +0 -1
  317. package/dist/assets/narrat-DRg8JJMk.js +0 -1
  318. package/dist/assets/nextflow-C-mBbutL.js +0 -1
  319. package/dist/assets/nextflow-groovy-vE_lwT2v.js +0 -1
  320. package/dist/assets/nginx-BpAMiNFr.js +0 -1
  321. package/dist/assets/night-owl-C39BiMTA.js +0 -1
  322. package/dist/assets/night-owl-light-CMTm3GFP.js +0 -1
  323. package/dist/assets/nim-BIad80T-.js +0 -1
  324. package/dist/assets/nix-CwoSXNpI.js +0 -1
  325. package/dist/assets/nord-Ddv68eIx.js +0 -1
  326. package/dist/assets/nsis-BlV79W_Q.js +0 -1
  327. package/dist/assets/nushell-D3jzshHO.js +0 -1
  328. package/dist/assets/objective-c-DXmwc3jG.js +0 -1
  329. package/dist/assets/objective-cpp-CLxacb5B.js +0 -1
  330. package/dist/assets/ocaml-C0hk2d4L.js +0 -1
  331. package/dist/assets/odin-BBf5iR-q.js +0 -1
  332. package/dist/assets/one-dark-pro-DVMEJ2y_.js +0 -1
  333. package/dist/assets/one-light-C3Wv6jpd.js +0 -1
  334. package/dist/assets/openscad-C4EeE6gA.js +0 -1
  335. package/dist/assets/org-DM6o9KBp.js +0 -1
  336. package/dist/assets/pascal-D93ZcfNL.js +0 -1
  337. package/dist/assets/perl-B9cMNwum.js +0 -1
  338. package/dist/assets/pkl-u5AG7uiY.js +0 -1
  339. package/dist/assets/plastic-3e1v2bzS.js +0 -1
  340. package/dist/assets/plsql-ChMvpjG-.js +0 -1
  341. package/dist/assets/po-BTJTHyun.js +0 -1
  342. package/dist/assets/poimandres-CS3Unz2-.js +0 -1
  343. package/dist/assets/polar-C0HS_06l.js +0 -1
  344. package/dist/assets/postcss-CXtECtnM.js +0 -1
  345. package/dist/assets/powerquery-CEu0bR-o.js +0 -1
  346. package/dist/assets/prisma-Vru482bI.js +0 -1
  347. package/dist/assets/prolog-CbFg5uaA.js +0 -1
  348. package/dist/assets/pug-DKIMFp6K.js +0 -1
  349. package/dist/assets/puppet-BMWR74SV.js +0 -1
  350. package/dist/assets/purescript-CklMAg4u.js +0 -1
  351. package/dist/assets/qml-3beO22l8.js +0 -1
  352. package/dist/assets/qmldir-C8lEn-DE.js +0 -1
  353. package/dist/assets/qss-IeuSbFQv.js +0 -1
  354. package/dist/assets/racket-BqYA7rlc.js +0 -1
  355. package/dist/assets/raku-DXvB9xmW.js +0 -1
  356. package/dist/assets/razor-BjBPvh-w.js +0 -1
  357. package/dist/assets/rbs-CpoqiR4B.js +0 -1
  358. package/dist/assets/red-hvxz__6c.js +0 -1
  359. package/dist/assets/reg-C-SQnVFl.js +0 -1
  360. package/dist/assets/regexp-CDVJQ6XC.js +0 -1
  361. package/dist/assets/rel-C3B-1QV4.js +0 -1
  362. package/dist/assets/riscv-BM1_JUlF.js +0 -1
  363. package/dist/assets/ron-D8l8udqQ.js +0 -1
  364. package/dist/assets/rose-pine-5qJOZa0Y.js +0 -1
  365. package/dist/assets/rose-pine-dawn-zx0QlTCp.js +0 -1
  366. package/dist/assets/rose-pine-moon-C9pEdX9L.js +0 -1
  367. package/dist/assets/rosmsg-BJDFO7_C.js +0 -1
  368. package/dist/assets/rst-bs7f0vWN.js +0 -1
  369. package/dist/assets/ruby-C0TQ7zu5.js +0 -1
  370. package/dist/assets/sas-DEy46yEz.js +0 -1
  371. package/dist/assets/scala-CqE71os6.js +0 -1
  372. package/dist/assets/scheme-C98Dy4si.js +0 -1
  373. package/dist/assets/sdbl-DVxCFoDh.js +0 -1
  374. package/dist/assets/shaderlab-Dg9Lc6iA.js +0 -1
  375. package/dist/assets/shellsession-BADoaaVG.js +0 -1
  376. package/dist/assets/slack-dark-BthQWCQV.js +0 -1
  377. package/dist/assets/slack-ochin-DqwNpetd.js +0 -1
  378. package/dist/assets/smalltalk-BOQMe2GC.js +0 -1
  379. package/dist/assets/smithy-cds9vsN8.js +0 -1
  380. package/dist/assets/snazzy-light-Bw305WKR.js +0 -1
  381. package/dist/assets/solarized-dark-CpvCGNkr.js +0 -1
  382. package/dist/assets/solarized-light-Dlz6yCKv.js +0 -1
  383. package/dist/assets/solidity-DijEV5ha.js +0 -1
  384. package/dist/assets/soy-8wufbnw4.js +0 -1
  385. package/dist/assets/sparql-rVzFXLq3.js +0 -1
  386. package/dist/assets/splunk-BtCnVYZw.js +0 -1
  387. package/dist/assets/ssh-config-_ykCGR6B.js +0 -1
  388. package/dist/assets/stata-DI20mbqo.js +0 -1
  389. package/dist/assets/stylus-BEDo0Tqx.js +0 -1
  390. package/dist/assets/surrealql-Cjom0U5J.js +0 -1
  391. package/dist/assets/svelte-Cy7k_4gC.js +0 -1
  392. package/dist/assets/swimlanesDiagram-TC7HE7FX-D-8vqZ4-.js +0 -8
  393. package/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
  394. package/dist/assets/system-verilog-0hqHdDBg.js +0 -1
  395. package/dist/assets/systemd-4A_iFExJ.js +0 -1
  396. package/dist/assets/talonscript-CkByrt1z.js +0 -1
  397. package/dist/assets/tasl-QIJgUcNo.js +0 -1
  398. package/dist/assets/tcl-dwOrl1Do.js +0 -1
  399. package/dist/assets/templ-DhtptRzy.js +0 -1
  400. package/dist/assets/terraform-BETggiCN.js +0 -1
  401. package/dist/assets/tex-D96PA37w.js +0 -1
  402. package/dist/assets/tokyo-night-hegEt444.js +0 -1
  403. package/dist/assets/ts-tags-D351s5mN.js +0 -1
  404. package/dist/assets/tsv-B_m7g4N7.js +0 -1
  405. package/dist/assets/turtle-BsS91CYL.js +0 -1
  406. package/dist/assets/twig-27uCiNez.js +0 -1
  407. package/dist/assets/typespec-CAFt9gP4.js +0 -1
  408. package/dist/assets/typst-BUadGCkm.js +0 -1
  409. package/dist/assets/v-BGw2Nkan.js +0 -1
  410. package/dist/assets/vala-CsfeWuGM.js +0 -1
  411. package/dist/assets/vb-Cu-pLBUe.js +0 -1
  412. package/dist/assets/verilog-nZwndyjY.js +0 -1
  413. package/dist/assets/vesper-DRje8inN.js +0 -1
  414. package/dist/assets/vhdl-CeAyd5Ju.js +0 -1
  415. package/dist/assets/viml-CJc9bBzg.js +0 -1
  416. package/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
  417. package/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
  418. package/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
  419. package/dist/assets/vue-BqiEGhQt.js +0 -1
  420. package/dist/assets/vue-html-AaS7Mt5G.js +0 -1
  421. package/dist/assets/vue-vine-BoDAl6tE.js +0 -1
  422. package/dist/assets/vyper-CDx5xZoG.js +0 -1
  423. package/dist/assets/wasm-CG6Dc4jp.js +0 -1
  424. package/dist/assets/wenyan-BV7otONQ.js +0 -1
  425. package/dist/assets/wgsl-Dx-B1_4e.js +0 -1
  426. package/dist/assets/wikitext-BhOHFoWU.js +0 -1
  427. package/dist/assets/wit-5i3qLPDT.js +0 -1
  428. package/dist/assets/wolfram-lXgVvXCa.js +0 -1
  429. package/dist/assets/xsl-CtQFsRM5.js +0 -1
  430. package/dist/assets/zenscript-DVFEvuxE.js +0 -1
  431. package/dist/assets/zig-VOosw3JB.js +0 -1
  432. /package/dist/assets/{shellscript-Yzrsuije.js → bash-Yzrsuije.js} +0 -0
  433. /package/dist/assets/{docker-BcOcwvcX.js → dockerfile-BcOcwvcX.js} +0 -0
  434. /package/dist/assets/{make-CHLpvVh8.js → makefile-CHLpvVh8.js} +0 -0
@@ -48,11 +48,11 @@ import { buildTurnDigest, coverageForDriver, digestPromptLine, renderDigest, too
48
48
  import { appendDecision, bindDecisionRetention, boundRetentionDays, decisionRetentionDays, decisionsCsv, flushDecisionLog, pruneDecisions, readDecisionRange, readDecisions, withDecisionActor } from "./decision-log.js";
49
49
  import { validateBotCwd } from "./bot-cwd.js";
50
50
  import { ATTACHMENTS_DIR, attachmentExists, cleanupStaleAttachmentPartials, deleteAttachment, extensionForMime, FILE_MAX_BYTES, IMAGE_MAX_BYTES, parseAudioRange, readAttachment, saveAudio, saveFile, saveImage, saveImageUpload, validateAttachmentUploadId, } from "./attachments.js";
51
- import { messageFileDisposition, messageFileDownloadName, messageAttachmentName, messageFileRoots, messageImageTargetAt, messageReferencesFile, openMessageFile, } from "./message-file.js";
51
+ import { messageFileDisposition, messageFileDownloadName, messageAttachmentName, messageFileRoots, messageImageTargetAt, messageReferencesFile, listMessageFolder, messageFolderEntryName, openMessageFile, openMessageFolderEntry, } from "./message-file.js";
52
52
  import { avatarGenerationRequestSchema, avatarGenerationStateMatches, generateAvatarImage, avatarImageStatus, snapshotAvatarGenerationState, } from "./avatar-image.js";
53
53
  import { fitsOnOneLine, parseBotProfilePatch } from "./bot-profile.js";
54
54
  import { groupTurnCwd } from "./room-cwd.js";
55
- import { RoomTurnDeadline, RoomTurnStallRegistry, roomTurnTimeoutMessage } from "./room-turn-timeout.js";
55
+ import { RoomTurnDeadline, RoomTurnStallRegistry, effectiveRoomTurnTimeoutMinutes, parseConversationTurnTimeout, roomTurnTimeoutMessage } from "./room-turn-timeout.js";
56
56
  import { roomTurnEnd } from "./room-turn-end.js";
57
57
  import { pickComputer } from "./computer-selection.js";
58
58
  import * as boat from "./boat.js";
@@ -67,6 +67,7 @@ import { liveCardKind, liveDecisionRefusal } from "../shared/live-approval.js";
67
67
  import { boatAccountResourceChangeError, cloudBackendChangeError, vpsAliasResourceChangeError, } from "./cloud-backend.js";
68
68
  import * as composio from "./composio.js";
69
69
  import { connectorCallFromFrame, connectorRefusalText, connectorUnrecognizedText, evaluateConnectorTools } from "./connector-verdict.js";
70
+ import { connectorCardText } from "./connector-card-text.js";
70
71
  import { chiefOfStaffSystemPrompt } from "./chief-of-staff.js";
71
72
  import { buildRecall } from "./recall.js";
72
73
  import { createMemoryUpkeep, upkeepEnabled } from "./memory-upkeep.js";
@@ -74,7 +75,7 @@ import { appendAboutMe, commitLearned, planLearned } from "./profile-learned.js"
74
75
  import { canAccessTeam, canReachPeer, coordinatorSupervises, livePeerRoster, livePeerRosterBlock, peerAllowed, peerName, peerRosterSystemPrompt, peerStatus, peerStatusWords, reachablePeers, resolveTeammate, roomPeerRosterSystemPrompt, roomRosterLine, PEER_ACCESS_HELP } from "./peer-roster.js";
75
76
  import { containerComputerAction, containerComputerExists, containerComputerFrame, containerComputerMcp, containerComputerScreenshot, containerComputerStatus, containerExec, containerRuntimeStatus, localVmWakeAction, localVmWorkspaceExists, perBotLocalVmTarget, poolLocalVmTarget, SHARED_LOCAL_VM_TARGET, setupCommands, VM_WORKSPACE_GUEST, } from "./container-computer.js";
76
77
  import { attachForTurn, saveBotAttachment } from "./bot-attachment.js";
77
- import { cacheUntilConfigChanges, ensureDirs, instanceConfigs, loadConfig, providerReloadKeys, localVmIdleTimeoutMinutes, localVmMaxInstances, localVmMode, parseConfigPatch, roomTurnTimeoutMinutes, threadEventLogMaxBytes, maxConcurrentBotThreads, threadEventLogRetentionDays, saveConfig, showToolCallsEnabled, routinesInConversationEnabled, claudeUserMcpEnabled, skillAuthoringEnabled, autoRecallEnabled, captureQuietMs, tidyHour, sharedComputersEnabled, builtInBrowserEnabled, llmThreadTitlesEnabled, browserProfileReplacementConflict, browserProfilePartitionTarget, syncCredentialEnv, stripWorkspaceCredentialEnv, PROVIDER_CREDENTIAL_ENV, withInstanceCli, persistableInstanceConfigs, vpsSshAlias, browserEngineAttachCdpUrl, DATA_DIR, skillsLibraryEnabled, EVENTS_DIR, NATIVE_DIR, customMcpServers, roomHandoffLimits, onConfigSaved, CLAUDE_API_INSTANCE, liveSettingsFor, mergeOpenCodeProviderKeys, openCodeProviderKeys, } from "./config.js";
78
+ import { cacheUntilConfigChanges, ensureDirs, instanceConfigs, loadConfig, providerReloadKeys, localVmIdleTimeoutMinutes, localVmMaxInstances, localVmMode, parseConfigPatch, roomTurnTimeoutMinutes, mcpCallTimeoutMinutes, threadEventLogMaxBytes, maxConcurrentBotThreads, threadEventLogRetentionDays, saveConfig, showToolCallsEnabled, routinesInConversationEnabled, claudeUserMcpEnabled, skillAuthoringEnabled, autoRecallEnabled, captureQuietMs, tidyHour, sharedComputersEnabled, builtInBrowserEnabled, llmThreadTitlesEnabled, browserProfileReplacementConflict, browserProfilePartitionTarget, syncCredentialEnv, stripWorkspaceCredentialEnv, PROVIDER_CREDENTIAL_ENV, withInstanceCli, persistableInstanceConfigs, vpsSshAlias, browserEngineAttachCdpUrl, DATA_DIR, skillsLibraryEnabled, EVENTS_DIR, NATIVE_DIR, customMcpServers, roomHandoffLimits, onConfigSaved, CLAUDE_API_INSTANCE, liveSettingsFor, mergeOpenCodeProviderKeys, openCodeProviderKeys, } from "./config.js";
78
79
  import { sweepThreadEventLogs } from "./thread-retention.js";
79
80
  import { ComputerControl } from "./computer-control.js";
80
81
  import { augmentedPath, findCliCandidates, resetPathCache } from "./env-path.js";
@@ -83,7 +84,8 @@ import { appendUsage, parseUsageRange, readUsage, summarizeUsage, usageCsv, USAG
83
84
  import { loadPlanUsage, planAccountsFromInstances } from "./plan-usage.js";
84
85
  import { GroupUsageReader } from "./group-thread-usage.js";
85
86
  import { ledgerCost } from "./model-prices.js";
86
- import { checkProviderKey, PROVIDER_KEY_KINDS } from "./provider-key-check.js";
87
+ import { checkProviderKey, PROVIDER_KEY_KINDS, providerBaseUrl } from "./provider-key-check.js";
88
+ import { forgetKey, noteKeyAccepted, noteKeyRejected, onKeyRejectionChange } from "./key-rejections.js";
87
89
  import { assertWithinBudget, noteSpend, spendAlertText, spendState, takeSpendAlert } from "./spend.js";
88
90
  import { fleetAvailable, fleetRequest, fleetSocketPath } from "./fleet-client.js";
89
91
  import { entitled } from "./enterprise.js";
@@ -95,7 +97,7 @@ import { RETRY_MAX_ATTEMPTS } from "./drivers/retry.js";
95
97
  import { recoveryCapabilityError } from "./automatic-recovery.js";
96
98
  import { decodeGeneratedImage } from "./generated-image.js";
97
99
  import { addDisabledMcpServer, MAX_MCP_SERVERS, isRemoteMcpServer, listMcpServers, mcpServerNameError, parseMcpServerMutation, parseMcpServersImport, parseStoredMcpServer, } from "./mcp-registry.js";
98
- import { McpOAuthError, McpSignInError, McpOAuthManager, mcpOAuthRedirectUri, withMcpSignIn, withoutPendingSignIn } from "./mcp-oauth.js";
100
+ import { MCP_OAUTH_CALLBACK_PATH, McpOAuthError, McpSignInError, McpOAuthManager, mcpCallbackOrigin, mcpOAuthRedirectUri, withMcpSignIn, withoutPendingSignIn } from "./mcp-oauth.js";
99
101
  import { probeMcpServer } from "./mcp-probe.js";
100
102
  import { GROUP_GOAL_MAX_TURNS, groupGoalAssignmentKey, groupGoalCompletionTurnId, groupGoalCoordinatorInstructions, groupGoalWorkerInstructions, parseGroupGoalDecision, resolveGroupGoalMember, selectGroupGoalCoordinator, } from "./group-goal-run.js";
101
103
  import { BUILT_IN_DRIVERS } from "./drivers/builtIn.js";
@@ -126,12 +128,13 @@ import { createCloudMoveRoutes, workspaceShared } from "./cloud-move-http.js";
126
128
  import { RESTART_EXIT_CODE } from "./restart.js";
127
129
  import { holdIncludedServices } from "./included-services.js";
128
130
  import { selectDefaultModelSelection, withNewBotEffort } from "./default-model-selection.js";
131
+ import { threadModelFallback } from "./thread-model.js";
129
132
  import { computerEngineMoveText, removedComputerInstanceIds, writeComputerEngineMoveLines } from "./computer-engine-removal.js";
130
133
  import { cancelPeerApprovalsFor, cancelPeerApprovalsForThread, dismissStalePeerCards, peerApprovalFailure, requestPeerApproval, resolvePeerComms } from "./peer-approval.js";
131
134
  import { peerDeliveryReceipt } from "./peer-delivery.js";
132
135
  import { peerProvenanceNote, withPeerProvenance } from "./peer-provenance.js";
133
136
  import { decideRoomPost, emptyRoomPostBudget } from "./room-post-budget.js";
134
- import { isProjectEmoji, mentionedBots, parseConnectorTools, roomResponders, sectionKey, Store, titleFromLlm, toWireTask, } from "./store.js";
137
+ import { isProjectEmoji, mentionedBots, parseConnectorTools, roomResponders, sectionKey, Store, threadTitleFrom, titleFromLlm, toWireTask, } from "./store.js";
135
138
  import * as tts from "./tts/index.js";
136
139
  import { createDecider, deciderIncludedHere, deciderReady, deciderSavePatch, describeDecider } from "./decider/index.js";
137
140
  import { decideRoomResponder } from "./decider/room-routing.js";
@@ -150,7 +153,7 @@ import { ensureWorkspace, ensureTaskWorkspace, workspaceLocationsPrompt, support
150
153
  import { listMemoryTopics, memoryDate, readMemoryFile, readMemoryTopic, writeMemoryFile, writeMemoryTopic } from "./workspace.js";
151
154
  import { beginMemoryTurn, endMemoryTurn, flushAllMemoryJournals, journalMemoryWrite, } from "./memory-journal.js";
152
155
  import { readSectionContext, readSections, sectionContextKey, sectionContextLabel, sectionContextSystemPrompt, writeSectionContext, SECTION_CONTEXT_MAX_BYTES, } from "./section-context.js";
153
- import { applyStagedSkillWrite, applySkillWriteWithReceipt, getStagedSkillWrite, installOrgSkill, installSkill, listBotSkillsWithOrigin, resolveBotSkills, parseSkillMd, scanSkillText, listSkills, listStagedSkillWrites, isSkillName, readSkillFile, rejectStagedSkillWrite, removeSkill, setSkillEnabled, skillPackageStamps, skillsSystemPrompt, stageSkillWrite, } from "./skills.js";
156
+ import { applyStagedSkillWrite, applySkillWriteWithReceipt, getStagedSkillWrite, installOrgSkill, installSkill, listBotSkillsWithOrigin, resolveBotSkills, parseSkillMd, scanSkillText, listSkills, listStagedSkillWrites, isSkillName, readSkillFile, rejectStagedSkillWrite, removeSkill, setSkillEnabled, skillPackageStamps, skillsSystemPrompt, stageSkillWrite, undoSkillWrite, } from "./skills.js";
154
157
  import { fetchSkillFromSource } from "./skill-fetch.js";
155
158
  import { pickBotName } from "./names.js";
156
159
  import { botSkillTemplateSchema, newBotDefaultsSchema, resolveBotCreationDefaults } from "./new-bot-defaults.js";
@@ -176,6 +179,7 @@ import { agentBrowserFrame, agentBrowserIntegration, browserEngineEncryptionKey,
176
179
  import { createScreenFrameSource } from "./screen-frame-source.js";
177
180
  import { screenFrameHash, screenSurfaceForTool, screenTouchingTool, settledFrameIsNews } from "./screen-frame-gate.js";
178
181
  import { asSchedule, RoutineRequestService } from "./routine-requests.js";
182
+ import { directApply, UNDO_STALE } from "./direct-apply.js";
179
183
  import { createOptionsCard } from "./options-card.js";
180
184
  import { buildBotOverview, connectedAppsFacts } from "./bot-overview.js";
181
185
  import { ProfileRequestService } from "./profile-requests.js";
@@ -228,7 +232,7 @@ import { applyPendingWorkspaceRestore, readLastWorkspaceRestore } from "./worksp
228
232
  import { createCustomDomainVerifier, customDomainIpv4, normalizeCustomDomain } from "./custom-domain.js";
229
233
  import { allowedScopes, createEmailSignIn, parseAllowList } from "./account-signin.js";
230
234
  import { ProviderAuthSessions } from "./provider-auth-sessions.js";
231
- import { clearSessionCookie, clientBotPatchViolation, clientGroupPatchViolation, isLoopbackHost, isProxied, labelFromUserAgent, requestOrigin, requestSource, requiredScope, resolveLoopbackTrust, resolveRequestAuth, parseCookies, serializeSessionCookie, sessionCookieName, } from "./request-auth.js";
235
+ import { clearSessionCookie, clientBotPatchViolation, clientGroupPatchViolation, isLoopbackHost, isProxied, labelFromUserAgent, provesSameOrigin, requestOrigin, requestSource, requiredScope, resolveLoopbackTrust, resolveRequestAuth, parseCookies, serializeSessionCookie, sessionCookieName, } from "./request-auth.js";
232
236
  import { cookieMaxAgeSeconds, formatPairingCode, SessionRegistry } from "./sessions.js";
233
237
  import { ThreadStarters } from "./thread-starters.js";
234
238
  import { activityCsv, activityEntries, appendAdminAction, botAuditSnapshot, botChangeRows, configChangeRows, parseActivityWhat, flushAdminActivity, pruneAdminActivity, readAdminActivityRange, sharedSignIn, signInListsOf, } from "./admin-activity.js";
@@ -244,6 +248,8 @@ import { createHostedSlackRoutes } from "./routes/hosted-slack.js";
244
248
  import { createBotPresetRoutes } from "./routes/bot-presets.js";
245
249
  import { createBotMemoryRoutes } from "./routes/bot-memory.js";
246
250
  import { createDeciderRoutes } from "./routes/decider.js";
251
+ import { createThreadModelRoutes } from "./routes/thread-models.js";
252
+ import { createUndoRoutes } from "./routes/undo.js";
247
253
  import { createDesktopViewer, desktopViewerUrl } from "./routes/desktop-viewer.js";
248
254
  import { localDesktopTarget, localVmViewerStatus, viewerTargetId } from "./desktop-viewer-targets.js";
249
255
  import { createAntigravityLeftoverRoutes } from "./routes/antigravity-leftovers.js";
@@ -835,12 +841,13 @@ function openerFrom(fromThreadId) {
835
841
  return person;
836
842
  return person && !cloudOwnerPerson(person) ? person : CLOUD_NOBODY_KEY;
837
843
  }
838
- /** The model a thread a bot opens from one of its own threads starts on:
839
- * that thread's, not the bot default the person may have moved away from
840
- * (#1950). Only the bot's own thread counts — a room or a teammate's thread
841
- * is not one of its tasks, and another bot's model belongs to another engine.
842
- * Approval stays the bot's, except that a level the inherited engine would
843
- * have to confirm on a model switch starts the new thread in Ask. */
844
+ /** The model a thread a bot opens from one of its own threads starts on
845
+ * (#1950): the model a person picked in that thread, kept as the new thread's
846
+ * own. A thread that follows the bot opens one that follows it too. Only the
847
+ * bot's own thread counts — a room or a teammate's thread is not one of its
848
+ * tasks, and another bot's model belongs to another engine. Approval stays
849
+ * the bot's, except that a level the inherited engine would have to confirm
850
+ * on a model switch starts the new thread in Ask. */
844
851
  function parentThreadModel(botId, parentThreadId) {
845
852
  // The stored profile, never a per-thread projection: the comparison is
846
853
  // against the defaults the new thread would otherwise get.
@@ -1668,6 +1675,9 @@ const managedPolicy = new ManagedDesktopPolicy({ onChange: () => {
1668
1675
  broadcast({ kind: "config", ...configStatus() });
1669
1676
  resyncOpenCodeProviderKeys();
1670
1677
  } });
1678
+ // A key the provider refused, or accepted again, changes what Model
1679
+ // providers and the picker show; the config event refreshes /api/instances.
1680
+ onKeyRejectionChange(() => broadcast({ kind: "config", ...configStatus() }));
1671
1681
  /** A computer kind this server will not use, refused before anything is
1672
1682
  * prepared: a Cloud home never offers this computer or a Local VM
1673
1683
  * (cloud-home.ts), and an enrolled organisation may disallow any kind. */
@@ -2055,6 +2065,9 @@ function agentsIntegration(botId, threadId, depth, skillAuthoring, generation, r
2055
2065
  const speaking = botForThread(botId, threadId) ?? store.bot(botId);
2056
2066
  return tts.voiceReady(cfg, speaking?.voice) && speaking?.voiceNotes !== false ? "1" : "0";
2057
2067
  })(),
2068
+ // And for a role: team setup, bot creation and deletion, rooms and
2069
+ // retries are shown only to a Chief of Staff, whom their routes require.
2070
+ OMB_CHIEF_OF_STAFF: store.bot(botId)?.chiefOfStaff === true ? "1" : "0",
2058
2071
  },
2059
2072
  };
2060
2073
  }
@@ -3005,6 +3018,62 @@ function checkedTaskModelSwitch(current, raw, updateBotDefault, resetApprovalToA
3005
3018
  }
3006
3019
  return checked;
3007
3020
  }
3021
+ /** An engine as a turn's start sees it, without waiting on its CLI: the live
3022
+ * instance and what its last read said (registry.lastSnapshot). Before the
3023
+ * first read it counts as available, so only what is known stops a pick. */
3024
+ function threadEngine(instanceId) {
3025
+ const live = registry.get(instanceId);
3026
+ if (!live)
3027
+ return undefined;
3028
+ return {
3029
+ instanceId, driverKind: live.driverKind, enabled: live.enabled, models: live.models,
3030
+ capabilities: { modelVariants: live.adapter.capabilities.modelVariants === true },
3031
+ snapshot: registry.lastSnapshot(instanceId) ?? { state: "available" },
3032
+ access: providerConfigs()[instanceId]?.access ?? registry.access(instanceId),
3033
+ };
3034
+ }
3035
+ /** This thread's own model, when it gives way to its bot's now
3036
+ * (threadModelFallback); null when the thread follows its bot or its own
3037
+ * model runs as picked. */
3038
+ function threadModelGivingWay(botId, threadId) {
3039
+ const bot = store.bot(botId);
3040
+ const own = store.taskByThread(botId, threadId)?.modelSelection;
3041
+ if (!bot || !own)
3042
+ return null;
3043
+ return threadModelFallback(own, bot.modelSelection, threadEngine, {
3044
+ refusal: policyModelRefusal,
3045
+ ...(hostedModels ? { allows: (selection) => hostedModels.allows(selection) } : {}),
3046
+ }) ? own : null;
3047
+ }
3048
+ /** The model a turn in this thread would run on now (healThreadModel). */
3049
+ function turnSelection(botId, threadId) {
3050
+ return threadModelGivingWay(botId, threadId) ? store.bot(botId)?.modelSelection : store.projectBotForTask(botId, threadId)?.modelSelection;
3051
+ }
3052
+ /** A model as a person reads it: its catalog label, after its engine's name
3053
+ * while that engine is here. */
3054
+ function modelName(selection) {
3055
+ const instance = registry.get(selection.instanceId);
3056
+ const label = instance?.models.options.find((option) => option.id === selection.model)?.label ?? selection.model;
3057
+ return instance ? `${instance.displayName || instance.driverKind} · ${label}` : label;
3058
+ }
3059
+ /** A thread's own model that can't run here gives way to its bot's: the turn
3060
+ * about to start runs on the bot's model, the thread follows the bot from
3061
+ * then on, and one plain line in it says so. Every turn starts through
3062
+ * startTurn (a person's message, a routine, a delegation, a webhook, a
3063
+ * queued follow-up), so this is the one place it happens; a room turn
3064
+ * already runs on each member's own model. */
3065
+ function healThreadModel(botId, threadId) {
3066
+ const bot = store.bot(botId);
3067
+ const own = threadModelGivingWay(botId, threadId);
3068
+ if (!bot || !own || bot.approvalGrant || threadBusy(botId, threadId))
3069
+ return;
3070
+ if (!store.followBotModel(botId, [threadId], (from, to) => hostedModels?.resetTask(from, to) ?? {}).length)
3071
+ return;
3072
+ store.appendMessage(threadId, { role: "bot", kind: "activity", tool: {
3073
+ name: `notice: This thread's model (${modelName(own)}) isn't available, so it now uses ${bot.name}'s model (${modelName(bot.modelSelection)}).`,
3074
+ ok: true,
3075
+ } });
3076
+ }
3008
3077
  function checkedExportSkillNames(value, bots) {
3009
3078
  // Sharing instructions is explicit: ordinary package exports include none.
3010
3079
  if (value === undefined)
@@ -3201,15 +3270,27 @@ function checkedMemberIds(value) {
3201
3270
  return { ok: true, memberIds };
3202
3271
  }
3203
3272
  let bootSelection = { instanceId: "", model: "" };
3273
+ /** The engines read at start, while it runs (below). It is where the server
3274
+ * learns what each engine supports (a Claude CLI's version and flags), so a
3275
+ * turn waits for it, as every turn did when the server listened only after
3276
+ * that read. */
3277
+ let enginesRead = null;
3204
3278
  /** Preset bots for New bot, from shared files and the organization (presets.ts). */
3205
3279
  const presetStore = createPresetStore();
3206
- const store = new Store(() => bootSelection, (selection) => withNewBotEffort(selection, cfg.newBots?.effort, registry.get(selection.instanceId)?.adapter.capabilities.effortLevels));
3280
+ const store = new Store(() => bootSelection, (selection) => withNewBotEffort(selection, cfg.newBots?.effort, registry.get(selection.instanceId)?.adapter.capabilities.effortLevels), (instanceId) => registry.cliTarget(instanceId)?.driverKind);
3207
3281
  const teamComputers = new TeamComputers(join(DATA_DIR, "team-computers.json"), ENVIRONMENT_ID);
3208
3282
  let followupsReady = false;
3209
3283
  const sendSequencer = new SendSequencer();
3210
3284
  // Before the new-bot default is read: a saved default may name the engine.
3211
3285
  await retryComputerEngineMove();
3212
- bootSelection = await defaultSelection();
3286
+ // Reading the new-bot default probes every engine CLI (each up to its
3287
+ // timeout), and at start only the first-run seed needs the answer. A later
3288
+ // start listens without waiting: the same read runs behind it, and turns
3289
+ // wait for it (enginesRead).
3290
+ if (store.needsSeed())
3291
+ bootSelection = await defaultSelection();
3292
+ else
3293
+ enginesRead = defaultSelection().then((selection) => { bootSelection = selection; }, (error) => console.warn(`[engines] reading the engines at start failed: ${error instanceof Error ? error.message : String(error)}`)).finally(() => { enginesRead = null; });
3213
3294
  store.seedIfEmpty();
3214
3295
  hostedModels?.reconcile(store);
3215
3296
  // Skills library boot sweep (features.skillsLibrary): migrate per-bot
@@ -3263,11 +3344,13 @@ if (browserCleanupReferencesReconciled)
3263
3344
  * than the desktop window did. Stripped here rather than at each call site
3264
3345
  * so a new broadcast cannot forget. */
3265
3346
  let activeCoordinationForThread = (_threadId) => false;
3266
- const wireTask = (task) => {
3347
+ /** One thread as a client sees it. `botModel` is its bot's model: a thread
3348
+ * without a model of its own runs on it (toWireTask). */
3349
+ const wireTask = (task, botModel) => {
3267
3350
  // Time-based snoozes heal on read against the server clock — no client
3268
3351
  // timer, no device skew. The 0 sentinel ("until new activity") is not a
3269
3352
  // time and survives reads; only a wake event in the store clears it.
3270
- const { snoozedUntil, ...base } = toWireTask(task);
3353
+ const { snoozedUntil, ...base } = toWireTask(task, botModel);
3271
3354
  const coordinated = { ...base, waitingForTeammates: activeCoordinationForThread(task.threadId) && !task.busy };
3272
3355
  const asleep = snoozedUntil === 0 || (snoozedUntil !== undefined && snoozedUntil > Date.now());
3273
3356
  return asleep ? { ...coordinated, snoozedUntil } : coordinated;
@@ -3281,7 +3364,7 @@ const wireBot = (bot) => {
3281
3364
  ? { ...rest, approvalMode: "ask", autoApprove: false }
3282
3365
  : rest;
3283
3366
  return { ...visible, waitingForTeammates: activeCoordinationForThread(bot.threadId) && !threadBusy(bot.id, bot.threadId),
3284
- avatarUrl: visible.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map(wireTask) } : {}) };
3367
+ avatarUrl: visible.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map((task) => wireTask(task, bot.modelSelection)) } : {}) };
3285
3368
  };
3286
3369
  function toolScopeForTurn(botId) {
3287
3370
  const bot = store.bot(botId);
@@ -3296,7 +3379,7 @@ function toolScopeForTurn(botId) {
3296
3379
  * can validate it before sending the confirmation that makes it effective. */
3297
3380
  const wireTrustedApprovalBot = (bot) => {
3298
3381
  const { resumeCursors: _resumeCursors, tasks, approvalGrant: _approvalGrant, lastProfileRequestId: _lastProfileRequestId, lastTeamSetupReceipt: _lastTeamSetupReceipt, packageBase: _packageBase, assignedSkills: _assignedSkills, ...rest } = bot;
3299
- return { ...rest, approvalMode: approvalModeFor(rest), avatarUrl: rest.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map(wireTask) } : {}) };
3382
+ return { ...rest, approvalMode: approvalModeFor(rest), avatarUrl: rest.avatarUrl ?? null, ...(tasks ? { tasks: tasks.map((task) => wireTask(task, bot.modelSelection)) } : {}) };
3300
3383
  };
3301
3384
  /** A settings-based preview, not a receipt of a dispatched turn. No
3302
3385
  * provisioning or credentials are needed to inspect it. The selected engine
@@ -3481,6 +3564,24 @@ function sourceApprovalMode(botId, threadId) {
3481
3564
  function fullAccessForSource(botId, threadId) {
3482
3565
  return sourceApprovalMode(botId, threadId) === "full";
3483
3566
  }
3567
+ /** On a Cloud home a turn a guest drives keeps a card for every change,
3568
+ * even one to the bot itself. */
3569
+ function selfApplyBlocked(threadId) {
3570
+ return cloudGuestDriven(threadId);
3571
+ }
3572
+ /** Whether a bot's routine, skill, profile or model change applies without
3573
+ * a person, and why: Full access applies everything as it always has, and
3574
+ * at any level a change to the bot itself applies, shown as a one-line
3575
+ * receipt with Undo (server/direct-apply.ts). Team setup and peer approvals
3576
+ * stay Full access only (fullAccessForSource). */
3577
+ function appliesDirectly(botId, threadId, targetBotId) {
3578
+ return directApply({
3579
+ fullAccess: fullAccessForSource(botId, threadId),
3580
+ botId,
3581
+ targetBotId,
3582
+ blocked: selfApplyBlocked(threadId) || !connectorThread(botId, threadId),
3583
+ });
3584
+ }
3484
3585
  function peerReviewRequired(bot, threadId) {
3485
3586
  return Boolean(bot.approvePeerComms && !fullAccessForSource(bot.id, threadId));
3486
3587
  }
@@ -3950,7 +4051,7 @@ const storedAvatarExists = (avatarUrl) => attachmentExists(avatarUrl.slice("/api
3950
4051
  const publicBot = (bot) => ({
3951
4052
  ...wireBot(bot),
3952
4053
  ...messagePage(bot.threadId, DEFAULT_PAGE),
3953
- tasks: store.tasks(bot.id).map(wireTask),
4054
+ tasks: store.tasks(bot.id).map((task) => wireTask(task, bot.modelSelection)),
3954
4055
  });
3955
4056
  const publicBotQueuedMessages = () => queuedSteerSnapshot((botId, threadId) => Boolean(store.taskByThread(botId, threadId)));
3956
4057
  // busyBotId names only the speaker that currently owns the provider process.
@@ -4142,6 +4243,15 @@ function updateChannel(groupId, value) {
4142
4243
  throw Object.assign(new Error("invalid default responder"), { status: 400 });
4143
4244
  patch.defaultResponder = responder;
4144
4245
  }
4246
+ if (Object.prototype.hasOwnProperty.call(body, "turnTimeoutMinutes")) {
4247
+ if (!existing.dm) {
4248
+ throw Object.assign(new Error("set the turn limit on that conversation"), { status: 400 });
4249
+ }
4250
+ const parsed = parseConversationTurnTimeout(body.turnTimeoutMinutes);
4251
+ if (!parsed.ok)
4252
+ throw Object.assign(new Error(parsed.error), { status: 400 });
4253
+ patch.turnTimeoutMinutes = parsed.minutes;
4254
+ }
4145
4255
  if (body.cwd !== undefined) {
4146
4256
  if (existing.dm)
4147
4257
  throw Object.assign(new Error("direct-message channels cannot have a working folder"), { status: 400 });
@@ -4462,7 +4572,14 @@ function publicGroupState(record) {
4462
4572
  usage = groupUsageReader.forThread(group.threadId);
4463
4573
  }
4464
4574
  catch { /* accounting must not block chat */ }
4465
- return { ...group, usage: usage ?? null, working: groupIsWorking(group) || [...roomHandoffs.nodes.values()].some(n => n.groupId === group.id && !["completed", "failed", "cancelled"].includes(n.status)) };
4575
+ return {
4576
+ ...group,
4577
+ // Always present so a client can tell "back to the group default"
4578
+ // apart from a partial patch that did not mention the field.
4579
+ turnTimeoutMinutes: record.turnTimeoutMinutes ?? null,
4580
+ usage: usage ?? null,
4581
+ working: groupIsWorking(group) || [...roomHandoffs.nodes.values()].some(n => n.groupId === group.id && !["completed", "failed", "cancelled"].includes(n.status)),
4582
+ };
4466
4583
  }
4467
4584
  function beginGroupTurnOperation(groupId, threadId, botIds = []) {
4468
4585
  const operation = {
@@ -8847,6 +8964,8 @@ async function startTurn(botId, text, opts) {
8847
8964
  },
8848
8965
  };
8849
8966
  }
8967
+ // A thread whose own model can't run runs on its bot's from now on.
8968
+ healThreadModel(botId, threadId);
8850
8969
  const bot = store.projectBotForTask(botId, threadId);
8851
8970
  if (!bot)
8852
8971
  throw Object.assign(new Error("no such task"), { status: 404 });
@@ -9088,6 +9207,11 @@ async function startTurn(botId, text, opts) {
9088
9207
  turnContext.delete(threadId);
9089
9208
  void (async () => {
9090
9209
  try {
9210
+ if (enginesRead) {
9211
+ await enginesRead;
9212
+ if (!directTurnClaimIsCurrent(bot.id, dispatchClaimId, threadId))
9213
+ throw new DirectTurnSetupCancelled("turn stopped while the engines were read");
9214
+ }
9091
9215
  // Readiness can wait on the network. Admit the task first so its busy
9092
9216
  // state, Stop action and watchdog own that wait just like VM setup.
9093
9217
  if (opts?.runOn === "cloud") {
@@ -10042,6 +10166,7 @@ async function startTurn(botId, text, opts) {
10042
10166
  mentionTurn: tagged.length > 0,
10043
10167
  integrations,
10044
10168
  mcpFromUserConfig: claudeUserMcpEnabled(cfg) && !managedPolicy.restrictsMcp(),
10169
+ mcpCallTimeoutMs: mcpCallTimeoutMinutes(cfg) * 60_000,
10045
10170
  cwd,
10046
10171
  }), () => !directTurnClaimExists(bot.id, dispatchClaimId, threadId), async () => {
10047
10172
  await instance.adapter.interruptTurn(threadId).catch(() => { });
@@ -10279,6 +10404,9 @@ function routineSourceOwner(run) {
10279
10404
  ? { bot, group: undefined, threadId: task.threadId }
10280
10405
  : null;
10281
10406
  }
10407
+ // Without a results snapshot (a run from before every routine reported to
10408
+ // its bot's main thread, or a bot with no visible thread) the chat that
10409
+ // made the routine is the destination.
10282
10410
  const threadId = run.sourceThreadId?.trim();
10283
10411
  if (!threadId)
10284
10412
  return null;
@@ -10299,6 +10427,21 @@ function routineSourceOwner(run) {
10299
10427
  const group = store.groupByThread(threadId);
10300
10428
  return group?.memberIds.includes(bot.id) ? { bot, group, threadId } : null;
10301
10429
  }
10430
+ /** There is no durable "main thread" (bot.threadId is only the UI
10431
+ * selection), so a routine reports into the bot's oldest open conversation:
10432
+ * the one it was created with until that is deleted or archived. Deleting
10433
+ * the last one already gives the bot a fresh conversation (Store.deleteTask). */
10434
+ function botMainThread(botId) {
10435
+ const visible = store.tasks(botId).filter((task) => !task.routineRunId);
10436
+ const open = visible.filter((task) => task.archivedAt === undefined);
10437
+ return (open.length ? open : visible).reduce((oldest, task) => (!oldest || task.createdAt < oldest.createdAt ? task : oldest), undefined)?.threadId;
10438
+ }
10439
+ /** Routines without a chosen thread used to get an automatic "<name> ·
10440
+ * Results" thread. One nobody renamed is that automatic default, not a
10441
+ * choice: new runs move to the main thread, and the old thread stays. */
10442
+ function legacyResultsThread(routineName, task) {
10443
+ return task.title.endsWith(" · Results") || task.title === threadTitleFrom(`${routineName} · Results`);
10444
+ }
10302
10445
  function routineSourceThread(run) {
10303
10446
  return routineSourceOwner(run)?.threadId ?? null;
10304
10447
  }
@@ -10532,21 +10675,32 @@ routines = new RoutineManager({
10532
10675
  return Boolean(bot && !bot.hidden && task && !task.routineRunId);
10533
10676
  },
10534
10677
  resolveResultsThread: (routine, forceNew) => {
10535
- // A trusted chat source is already snapshotted as sourceThreadId on each
10536
- // run. Keep it distinct: it can belong to a teammate or room, whereas an
10537
- // explicit resultsThreadId must be a visible task owned by the running bot.
10538
- if (!forceNew && routineSourceOwner(routine))
10539
- return routine.resultsThreadId;
10678
+ // Every run reports into the routine's bot's main thread, wherever the
10679
+ // routine was made (chat, editor, API, or a teammate's request). A run
10680
+ // still executes in its own hidden task, which Run logs and Open run use.
10681
+ // A thread the person chose in "Post results to" keeps winning.
10682
+ const bot = store.bot(routine.botId);
10683
+ const chosen = !forceNew && routine.resultsThreadId && bot && !bot.hidden
10684
+ ? store.taskByThread(bot.id, routine.resultsThreadId) : undefined;
10685
+ // On a Cloud home a routine reports only where its writer may write: a
10686
+ // guest's (or nobody's) keeps a results conversation opened as theirs.
10687
+ const opener = CLOUD_HOME ? routineOpener(routine.id) : undefined;
10688
+ const main = bot && !bot.hidden ? botMainThread(bot.id) : undefined;
10689
+ const mainAllowed = main && (!CLOUD_HOME || (opener === CLOUD_OWNER_KEY && !cloudGuestOpened(main)));
10690
+ const keepChosen = chosen && !chosen.routineRunId && !(mainAllowed && legacyResultsThread(routine.name, chosen));
10691
+ if (keepChosen)
10692
+ return chosen.threadId;
10693
+ if (mainAllowed || !CLOUD_HOME)
10694
+ return main;
10540
10695
  const threadId = store.createTask(routine.botId, `${routine.name} · Results`, false)?.threadId;
10541
- // On a Cloud home the results conversation is opened by whoever wrote the
10542
- // routine: it carries their name for it (cloudGuestOpened).
10543
- if (threadId && CLOUD_HOME)
10544
- threadStarters.set(threadId, routineOpener(routine.id));
10696
+ if (threadId)
10697
+ threadStarters.set(threadId, opener);
10545
10698
  return threadId;
10546
10699
  },
10547
10700
  discardResultsThread: (botId, threadId) => {
10548
10701
  const task = store.taskByThread(botId, threadId);
10549
- if (task && !task.busy && !task.routineRunId && store.messagesFor(threadId).length === 0) {
10702
+ // The main thread is reused, never allocated, so a failed write keeps it.
10703
+ if (task && threadId !== botMainThread(botId) && !task.busy && !task.routineRunId && store.messagesFor(threadId).length === 0) {
10550
10704
  store.deleteTask(botId, threadId);
10551
10705
  handoffs.forget(threadId);
10552
10706
  }
@@ -10718,7 +10872,11 @@ if (recoveryOwners.length > 0) {
10718
10872
  // after the user confirms a durable card. Keeping this beside the scheduler
10719
10873
  // makes the card resolvable after an app restart without involving the model.
10720
10874
  async function cloudRoutineReadiness(botId, threadId) {
10721
- const bot = threadId ? store.projectBotForTask(botId, threadId) : store.bot(botId);
10875
+ const projected = threadId ? store.projectBotForTask(botId, threadId) : store.bot(botId);
10876
+ // The model its turn will run on: a thread's own model that can't run
10877
+ // gives way to the bot's when the turn starts (healThreadModel).
10878
+ const selection = threadId ? turnSelection(botId, threadId) : projected?.modelSelection;
10879
+ const bot = projected && selection ? { ...projected, modelSelection: selection } : projected;
10722
10880
  if (!bot || bot.hidden)
10723
10881
  return { ready: false, reason: "The routine's target bot no longer exists." };
10724
10882
  if (!boat.boatConfigured(cfg)) {
@@ -10758,7 +10916,7 @@ async function cloudRoutineReadiness(botId, threadId) {
10758
10916
  const routineRequests = new RoutineRequestService({
10759
10917
  store,
10760
10918
  routines,
10761
- autoApply: fullAccessForSource,
10919
+ autoApply: appliesDirectly,
10762
10920
  cloudReady: cloudRoutineReadiness,
10763
10921
  canPersist: proposalPersistence,
10764
10922
  // Cross-bot routines: the confirmation card can sit open indefinitely, so
@@ -11091,14 +11249,14 @@ function resolveAndSendTrust(res, threadId, requestId, behavior) {
11091
11249
  }
11092
11250
  const profileRequests = new ProfileRequestService({
11093
11251
  store,
11094
- autoApply: fullAccessForSource,
11252
+ autoApply: appliesDirectly,
11095
11253
  canPersist: proposalPersistence,
11096
11254
  // A Chief may change a section peer; anyone else only itself. Re-checked at confirm.
11097
11255
  validateTarget: chiefPeerTargetRule("profile"),
11098
11256
  });
11099
11257
  const modelRequests = new ModelRequestService({
11100
11258
  store,
11101
- autoApply: fullAccessForSource,
11259
+ autoApply: appliesDirectly,
11102
11260
  canPersist: proposalPersistence,
11103
11261
  // Same authority rule as profile proposals: a Chief may name one section peer.
11104
11262
  validateTarget: chiefPeerTargetRule("default model"),
@@ -11416,6 +11574,104 @@ function resolveAndSendModel(res, args) {
11416
11574
  json(res, 200, { ok: true, outcome: "rejected" });
11417
11575
  return true;
11418
11576
  }
11577
+ /** Undo for a learned skill a bot wrote without a person (undoSkillWrite). */
11578
+ function undoSkillCard(botId, threadId, messageId) {
11579
+ const message = store.messagesFor(threadId).find((candidate) => candidate.id === messageId);
11580
+ const card = message?.card;
11581
+ const request = card?.skillRequest;
11582
+ if (!message || !card || !request)
11583
+ return { claimed: false };
11584
+ if (card.undone)
11585
+ return { claimed: true, state: "already_undone" };
11586
+ if (!card.autoApplied || card.answered !== "allow") {
11587
+ return { claimed: true, state: "invalid", error: "Only a change that applied on its own can be undone here.", status: 409 };
11588
+ }
11589
+ if (request.botId !== botId || request.threadId !== threadId) {
11590
+ return { claimed: true, state: "invalid", error: "this skill request belongs to a different bot", status: 403 };
11591
+ }
11592
+ const undone = undoSkillWrite(botId, {
11593
+ stagedId: request.stagedId,
11594
+ name: request.name,
11595
+ action: request.action,
11596
+ ...(request.previous ? { previous: { skillMd: request.previous.preview, source: request.previous.source } } : {}),
11597
+ });
11598
+ if ("stale" in undone)
11599
+ return { claimed: true, state: "invalid", error: UNDO_STALE, status: 409, stale: true };
11600
+ if ("error" in undone)
11601
+ return { claimed: true, state: "invalid", error: undone.error, status: 409 };
11602
+ store.patchMessage(threadId, message.id, { card: { ...card, undone: true } });
11603
+ return { claimed: true, state: "undone" };
11604
+ }
11605
+ /** Undo on the one-line receipt of a change that applied without a person
11606
+ * (a bot's change to itself, or Full access). The caller authorizes it
11607
+ * exactly like answering that card; the card remembers it was undone. */
11608
+ function undoAppliedChange(res, threadId, requestId) {
11609
+ const message = store.messagesFor(threadId).find((candidate) => candidate.card?.requestId === requestId);
11610
+ const card = message?.card;
11611
+ if (!message || !card)
11612
+ return json(res, 404, { error: "this change is no longer in the conversation" });
11613
+ // The proposing bot comes from the conversation (a room card's trusted
11614
+ // sender, else the thread's bot), never from the card payload.
11615
+ const botId = message.from?.botId ?? store.botByThread(threadId)?.id;
11616
+ if (!botId)
11617
+ return json(res, 400, { error: "this change has no valid owner" });
11618
+ let result = { claimed: false };
11619
+ if (card.routineRequest) {
11620
+ const undone = routineRequests.undo({ botId, threadId, requestId });
11621
+ // On a Cloud home a routine Undo removed is forgotten, and one it put
11622
+ // back is the owner's only if it was theirs before the change.
11623
+ if (undone.claimed && undone.state === "undone" && cloudRoutineAuthors) {
11624
+ if (undone.action === "create")
11625
+ cloudRoutineAuthors.remove(undone.routineId);
11626
+ else if (undone.action === "update") {
11627
+ if (undone.ownersBefore)
11628
+ cloudOwnersRoutine(undone.routineId);
11629
+ else
11630
+ cloudRoutineAuthors.forget(undone.routineId);
11631
+ }
11632
+ else if (undone.action === "delete" && undone.restoredId) {
11633
+ if (undone.ownersBefore)
11634
+ cloudOwnersRoutine(undone.restoredId);
11635
+ else
11636
+ cloudRoutineAuthors.wrote(undone.restoredId, CLOUD_NOBODY_KEY);
11637
+ }
11638
+ }
11639
+ result = undone;
11640
+ }
11641
+ else if (card.profileRequest) {
11642
+ const undone = profileRequests.undo({ botId, threadId, requestId });
11643
+ if (undone.claimed && undone.state === "undone") {
11644
+ const target = store.bot(undone.targetBotId);
11645
+ if (target)
11646
+ broadcast({ kind: "bot", bot: wireBot(target) });
11647
+ }
11648
+ result = undone;
11649
+ }
11650
+ else if (card.modelRequest) {
11651
+ const undone = modelRequests.undo({ botId, threadId, requestId });
11652
+ if (undone.claimed && undone.state === "undone") {
11653
+ const target = store.bot(undone.targetBotId);
11654
+ if (target)
11655
+ broadcast({ kind: "bot", bot: wireBot(target) });
11656
+ }
11657
+ result = undone;
11658
+ }
11659
+ else if (card.skillRequest) {
11660
+ result = undoSkillCard(botId, threadId, message.id);
11661
+ }
11662
+ if (!result.claimed)
11663
+ return json(res, 409, { error: "Only a change that applied on its own can be undone here." });
11664
+ if (result.state === "invalid") {
11665
+ return json(res, result.status, { error: result.error, ...(result.stale ? { code: "changed-since" } : {}) });
11666
+ }
11667
+ if (result.state === "undone") {
11668
+ appendDecision(DATA_DIR, {
11669
+ threadId, requestId, botId, botName: store.bot(botId)?.name,
11670
+ tool: card.tool, summary: card.subtitle, decision: "user-undone", source: "user",
11671
+ });
11672
+ }
11673
+ json(res, 200, { ok: true, undone: true, ...(result.state === "already_undone" ? { alreadyUndone: true } : {}) });
11674
+ }
11419
11675
  // Webhook definitions are independent from calendar schedules, but every
11420
11676
  // delivery joins the same RoutineManager queue. That keeps unattended work
11421
11677
  // ordered behind a busy MAUS and gives webhook runs the same durable receipts.
@@ -11437,10 +11693,9 @@ const webhooks = new WebhookManager({
11437
11693
  return;
11438
11694
  store.appendMessage(threadId, { role: "bot", kind: "text", text, webhookPost: true });
11439
11695
  },
11440
- // Mirrors resolveResultsThread's routines wiring a few hundred lines up
11441
- // in this same file: create-on-first-use, never activated (so it never
11442
- // steals the bot's live selection the way POST /api/bots/:id/tasks
11443
- // does), reused forever after via trigger.resultsThreadId.
11696
+ // Create-on-first-use, never activated (so it never steals the bot's
11697
+ // live selection the way POST /api/bots/:id/tasks does), reused forever
11698
+ // after via trigger.resultsThreadId.
11444
11699
  resolvePostThread: (trigger, forceNew) => {
11445
11700
  if (!forceNew && trigger.resultsThreadId)
11446
11701
  return trigger.resultsThreadId;
@@ -12207,6 +12462,10 @@ setupRetry = 0) {
12207
12462
  { id: "skill-instructions", label: "Skill instructions", text: renderSkillInstructions(selectedSkills, { includeRoot: Boolean(workspace) }) },
12208
12463
  { id: "playbooks", label: "Playbooks", text: installedPlaybookInstructions(text, bot.playbooks) },
12209
12464
  ]);
12465
+ // The engines read at start is part of setup (it learns what this engine
12466
+ // supports): wait for it before the claim below.
12467
+ if (enginesRead)
12468
+ await enginesRead;
12210
12469
  // run the turn and wait for it to settle, folding the reply text so a
12211
12470
  // chained @mention can be routed afterwards
12212
12471
  // Claim only after setup succeeded. An unavailable, busy, unsupported, or
@@ -12246,7 +12505,10 @@ setupRetry = 0) {
12246
12505
  else
12247
12506
  markCancelledProviderHandshake(threadId, retirementOwner);
12248
12507
  };
12249
- const timeoutMinutes = roomTurnTimeoutMinutes(cfg);
12508
+ const liveGroup = store.group(groupId) ?? group;
12509
+ const timeoutMinutes = effectiveRoomTurnTimeoutMinutes(roomTurnTimeoutMinutes(cfg), liveGroup.dm
12510
+ ? liveGroup.turnTimeoutMinutes
12511
+ : store.groupTaskByThread(liveGroup.id, threadId)?.turnTimeoutMinutes);
12250
12512
  const outcome = await new Promise((resolve) => {
12251
12513
  let done = false;
12252
12514
  let unsub = () => { };
@@ -12334,6 +12596,7 @@ setupRetry = 0) {
12334
12596
  cwd,
12335
12597
  integrations,
12336
12598
  mcpFromUserConfig: claudeUserMcpEnabled(cfg) && !managedPolicy.restrictsMcp(),
12599
+ mcpCallTimeoutMs: mcpCallTimeoutMinutes(cfg) * 60_000,
12337
12600
  ...(instance.instanceId === readyBot.modelSelection.instanceId
12338
12601
  ? memberTurnSelection(readyBot.modelSelection)
12339
12602
  : { model: instance.models.default }),
@@ -13367,14 +13630,16 @@ function roomPostEligibility(bot, group) {
13367
13630
  }
13368
13631
  return { ok: true };
13369
13632
  }
13370
- function proposalPersistence(botId, threadId) {
13633
+ /** `opensCard` false for a change that applies directly: it adds no open
13634
+ * card, so the open-card budget does not hold it back. */
13635
+ function proposalPersistence(botId, threadId, opensCard = true) {
13371
13636
  if (!store.bot(botId)) {
13372
13637
  return { ok: false, status: 403, error: "unknown sender" };
13373
13638
  }
13374
13639
  if (!connectorThread(botId, threadId)) {
13375
13640
  return { ok: false, status: 403, error: "source conversation does not belong to sender" };
13376
13641
  }
13377
- if (fullAccessForSource(botId, threadId))
13642
+ if (!opensCard || fullAccessForSource(botId, threadId))
13378
13643
  return { ok: true };
13379
13644
  // Only cards on the visible branch can be acted on from the composer.
13380
13645
  // Abandoned branches must not permanently consume the proposal quota.
@@ -13387,14 +13652,14 @@ function proposalPersistence(botId, threadId) {
13387
13652
  ? { ok: false, status: 429, error: "confirm or cancel an existing proposal first" }
13388
13653
  : { ok: true };
13389
13654
  }
13390
- function skillProposalPersistence(botId, threadId) {
13655
+ function skillProposalPersistence(botId, threadId, opensCard = true) {
13391
13656
  if (!store.bot(botId)) {
13392
13657
  return { ok: false, status: 403, error: "unknown sender" };
13393
13658
  }
13394
13659
  if (!connectorThread(botId, threadId)) {
13395
13660
  return { ok: false, status: 403, error: "source conversation does not belong to sender" };
13396
13661
  }
13397
- if (fullAccessForSource(botId, threadId))
13662
+ if (!opensCard || fullAccessForSource(botId, threadId))
13398
13663
  return { ok: true };
13399
13664
  const openRequests = store.activePath(threadId).filter((message) => message.card?.skillRequest?.botId === botId &&
13400
13665
  !message.card.answered &&
@@ -13461,6 +13726,7 @@ function appendSkillRequestCard(args) {
13461
13726
  sha256: args.staged.sha256,
13462
13727
  warnings: args.staged.warnings,
13463
13728
  createdAt: Date.now(),
13729
+ ...(args.applied && args.previous ? { previous: args.previous } : {}),
13464
13730
  };
13465
13731
  const from = store.bot(args.botId);
13466
13732
  store.appendMessage(args.threadId, {
@@ -13471,7 +13737,7 @@ function appendSkillRequestCard(args) {
13471
13737
  title: copy.title,
13472
13738
  subtitle: copy.subtitle,
13473
13739
  options: args.applied ? [] : [args.staged.action === "create" ? "Enable" : "Update", "Deny"],
13474
- ...(args.applied ? { answered: "allow", title: `Skill "${args.staged.name}" ${args.staged.action === "create" ? "enabled" : "updated"}` } : {}),
13740
+ ...(args.applied ? { answered: "allow", autoApplied: true, title: `Skill "${args.staged.name}" ${args.staged.action === "create" ? "enabled" : "updated"}` } : {}),
13475
13741
  requestId,
13476
13742
  tool: copy.tool,
13477
13743
  skillRequest: payload,
@@ -14262,6 +14528,7 @@ function configStatus() {
14262
14528
  // not a secret — the settings picker shows it; "" = follow the system
14263
14529
  language: cfg.language ?? "",
14264
14530
  rooms: { turnTimeoutMinutes: roomTurnTimeoutMinutes(cfg) },
14531
+ mcp: { callTimeoutMinutes: mcpCallTimeoutMinutes(cfg) },
14265
14532
  automaticRecovery: cfg.automaticRecovery ?? { enabled: false },
14266
14533
  // absent effort = no level is sent, so clients can tell it from any level
14267
14534
  newBots: cfg.newBots?.effort ? { effort: cfg.newBots.effort } : {},
@@ -14617,15 +14884,22 @@ function serveStatic(res, path) {
14617
14884
  try {
14618
14885
  const data = readFileSync(file);
14619
14886
  const type = MIME[extname(file)] ?? "application/octet-stream";
14620
- res.writeHead(200, { "content-type": type, ...(type === "text/html" ? PAGE_HEADERS : {}) });
14887
+ // Vite puts a content hash in the name of every file it writes under
14888
+ // /assets/, so a URL there never changes: the browser keeps it instead of
14889
+ // downloading the bundle on every load. That only holds while public/ has
14890
+ // no assets/ dir (index.test.ts checks). Pages are revalidated, so a new
14891
+ // build shows up on the next load.
14892
+ const cache = type === "text/html" ? "no-cache" : safe.startsWith("/assets/") ? "public, max-age=31536000, immutable" : null;
14893
+ res.writeHead(200, { "content-type": type, ...(type === "text/html" ? PAGE_HEADERS : {}), ...(cache ? { "cache-control": cache } : {}) });
14621
14894
  res.end(data);
14622
14895
  return true;
14623
14896
  }
14624
14897
  catch {
14625
- // SPA fallback
14898
+ // SPA fallback. Never immutable: a missing chunk answered with the page
14899
+ // must not stay cached under the chunk's URL.
14626
14900
  try {
14627
14901
  const data = readFileSync(join(STATIC_DIR, "index.html"));
14628
- res.writeHead(200, { "content-type": "text/html", ...PAGE_HEADERS });
14902
+ res.writeHead(200, { "content-type": "text/html", "cache-control": "no-cache", ...PAGE_HEADERS });
14629
14903
  res.end(data);
14630
14904
  return true;
14631
14905
  }
@@ -14740,6 +15014,20 @@ ROUTES.push(createBotMemoryRoutes({
14740
15014
  },
14741
15015
  }));
14742
15016
  ROUTES.push(createDeciderRoutes({ decider }));
15017
+ // "Switch them too": a bot's threads on a model of their own follow its model again.
15018
+ ROUTES.push(createThreadModelRoutes({
15019
+ bot: (id) => store.bot(id),
15020
+ busy: (botId, threadId) => threadBusy(botId, threadId),
15021
+ follow: (botId, threadIds) => store.followBotModel(botId, threadIds, (from, to) => hostedModels?.resetTask(from, to) ?? {}),
15022
+ // Like updateBotDefault on a thread: on a Cloud home, the owner's alone.
15023
+ mayChangeModel: (auth) => !CLOUD_HOME || cloudOwnerSession(auth),
15024
+ reply: (botId) => publicBot(store.bot(botId)),
15025
+ }));
15026
+ // Undo on the one-line receipt of a change that applied without a person.
15027
+ ROUTES.push(createUndoRoutes({
15028
+ refusal: (auth, threadId, requestId) => cardAnswerRefusal(auth, threadId, requestId, "allow"),
15029
+ undo: (auth, res, threadId, requestId) => withDecisionActor(decisionActorFor(auth), () => undoAppliedChange(res, threadId, requestId)),
15030
+ }));
14743
15031
  ROUTES.push(createAntigravityLeftoverRoutes({
14744
15032
  hosted: Boolean(hostedModels),
14745
15033
  isAntigravity: (instanceId) => registry.get(instanceId)?.driverKind === "antigravityAgent",
@@ -14892,6 +15180,17 @@ const handleRequest = async (req, res) => {
14892
15180
  if (refusal)
14893
15181
  return json(res, 403, { error: refusal, code: "managed_policy" });
14894
15182
  }
15183
+ // An MCP server's sign-in, started in a browser on another computer,
15184
+ // comes back here (server/mcp-oauth.ts). Public like the loopback
15185
+ // listener it stands in for: the state is the credential, checked once.
15186
+ if (path === MCP_OAUTH_CALLBACK_PATH) {
15187
+ const answer = method === "GET"
15188
+ ? await mcpOAuth.publicCallback(req.headers.host, url.search)
15189
+ : { status: 400, text: "Invalid sign-in callback. Return to OpenMausBot and try again." };
15190
+ res.writeHead(answer.status, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "referrer-policy": "no-referrer" });
15191
+ res.end(answer.text);
15192
+ return;
15193
+ }
14895
15194
  // ── who is asking (server/request-auth.ts) ──────────────────────────
14896
15195
  // Two public routes come first: what this server is, and turning a pairing
14897
15196
  // code into a session. Everything else needs the loopback owner or a
@@ -15957,7 +16256,9 @@ const handleRequest = async (req, res) => {
15957
16256
  forBot = { botId: target.id, name: target.name };
15958
16257
  }
15959
16258
  }
15960
- const persistence = proposalPersistence(from.id, fromThreadId);
16259
+ // Sender and conversation only: the service checks the open-card
16260
+ // budget once it knows whether this change opens a card at all.
16261
+ const persistence = proposalPersistence(from.id, fromThreadId, false);
15961
16262
  if (!persistence.ok) {
15962
16263
  return json(res, persistence.status, { error: persistence.error });
15963
16264
  }
@@ -15991,6 +16292,12 @@ const handleRequest = async (req, res) => {
15991
16292
  cloudRoutineAuthors?.forget(applied.resultId);
15992
16293
  cloudRoutineAuthors?.wrote(applied.resultId, CLOUD_NOBODY_KEY);
15993
16294
  }
16295
+ // Undo puts an updated or deleted routine back as the owner's only
16296
+ // if it was theirs before this change.
16297
+ if (applied && wasOwners && proposedCard?.autoApplied && proposedCard.routineRequest?.undo) {
16298
+ store.patchMessage(fromThreadId, proposed.messageId, { card: { ...proposedCard,
16299
+ routineRequest: { ...proposedCard.routineRequest, undo: { ...proposedCard.routineRequest.undo, ownersBefore: true } } } });
16300
+ }
15994
16301
  appendDecision(DATA_DIR, {
15995
16302
  threadId: fromThreadId,
15996
16303
  requestId: proposed.requestId,
@@ -16001,7 +16308,7 @@ const handleRequest = async (req, res) => {
16001
16308
  // response returned to the model.
16002
16309
  summary: proposedCard?.subtitle ?? proposed.summary,
16003
16310
  decision: proposed.state === "applied" ? "auto-approved" : "card-shown",
16004
- source: proposed.state === "applied" ? "full-access" : "routine",
16311
+ source: proposed.state === "applied" ? proposed.appliedBy ?? "full-access" : "routine",
16005
16312
  });
16006
16313
  return json(res, 201, proposed);
16007
16314
  }
@@ -16086,7 +16393,7 @@ const handleRequest = async (req, res) => {
16086
16393
  appendDecision(DATA_DIR, {
16087
16394
  threadId: body.fromThreadId, requestId: proposed.requestId, botId: from.id, botName: from.name,
16088
16395
  tool: "update_profile", summary: proposed.detail, decision: proposed.state === "applied" ? "auto-approved" : "card-shown",
16089
- source: proposed.state === "applied" ? "full-access" : "profile",
16396
+ source: proposed.state === "applied" ? proposed.appliedBy ?? "full-access" : "profile",
16090
16397
  });
16091
16398
  return json(res, 201, proposed);
16092
16399
  }
@@ -16118,7 +16425,7 @@ const handleRequest = async (req, res) => {
16118
16425
  appendDecision(DATA_DIR, {
16119
16426
  threadId: body.fromThreadId, requestId: proposed.requestId, botId: from.id, botName: from.name,
16120
16427
  tool: "update_model", summary: proposed.detail, decision: proposed.state === "applied" ? "auto-approved" : "card-shown",
16121
- source: proposed.state === "applied" ? "full-access" : "model",
16428
+ source: proposed.state === "applied" ? proposed.appliedBy ?? "full-access" : "model",
16122
16429
  });
16123
16430
  return json(res, 201, proposed);
16124
16431
  }
@@ -16330,7 +16637,10 @@ const handleRequest = async (req, res) => {
16330
16637
  if (!connectorThread(from.id, fromThreadId)) {
16331
16638
  return json(res, 403, { error: "source conversation does not belong to sender" });
16332
16639
  }
16333
- const persistence = skillProposalPersistence(from.id, fromThreadId);
16640
+ // Skills written here are always the sender's own, so they apply at
16641
+ // any level, with a one-line receipt and Undo (appliesDirectly).
16642
+ const direct = appliesDirectly(from.id, fromThreadId, from.id);
16643
+ const persistence = skillProposalPersistence(from.id, fromThreadId, !direct);
16334
16644
  if (!persistence.ok)
16335
16645
  return json(res, persistence.status, { error: persistence.error });
16336
16646
  const action = body.action === "create" || body.action === "update" ? body.action : "";
@@ -16356,11 +16666,15 @@ const handleRequest = async (req, res) => {
16356
16666
  });
16357
16667
  if ("error" in staged)
16358
16668
  return json(res, 422, { error: staged.error });
16359
- if (fullAccessForSource(from.id, fromThreadId)) {
16669
+ if (direct) {
16670
+ // What an update replaces, so Undo can put it back.
16671
+ const previousSkillMd = staged.action === "update" ? readSkillFile(from.id, staged.name) : null;
16672
+ const previousSource = listSkills(from.id).find((skill) => skill.name === staged.name)?.source;
16673
+ const previous = previousSkillMd !== null && previousSource ? { preview: previousSkillMd, source: previousSource } : undefined;
16360
16674
  const applied = applySkillWriteWithReceipt(from.id, staged, () => {
16361
- const receipt = appendSkillRequestCard({ botId: from.id, threadId: fromThreadId, staged, applied: true });
16675
+ const receipt = appendSkillRequestCard({ botId: from.id, threadId: fromThreadId, staged, applied: true, previous });
16362
16676
  appendDecision(DATA_DIR, { threadId: fromThreadId, requestId: receipt.requestId, botId: from.id, botName: from.name,
16363
- tool: "stage_skill", summary: receipt.summary, decision: "auto-approved", source: "full-access" });
16677
+ tool: "stage_skill", summary: receipt.summary, decision: "auto-approved", source: direct });
16364
16678
  });
16365
16679
  if ("error" in applied) {
16366
16680
  rejectStagedSkillWrite(from.id, staged.id);
@@ -18048,6 +18362,9 @@ const handleRequest = async (req, res) => {
18048
18362
  const message = store.appendMessage(threadId, {
18049
18363
  role: "bot",
18050
18364
  kind: "connector",
18365
+ // Read only by clients that predate the card (older phones draw
18366
+ // an unknown kind by its text); see connector-card-text.ts.
18367
+ text: connectorCardText(toolkit.label, item.alias),
18051
18368
  ...(owner.group ? { from: { botId: owner.bot.id, name: owner.bot.name, color: owner.bot.color } } : {}),
18052
18369
  connector: {
18053
18370
  slug: item.slug,
@@ -18460,7 +18777,7 @@ const handleRequest = async (req, res) => {
18460
18777
  return json(res, 200, {
18461
18778
  bots: shownBots.map((bot) => memberBot({
18462
18779
  ...wireBot(bot),
18463
- tasks: store.tasks(bot.id).map(wireTask),
18780
+ tasks: store.tasks(bot.id).map((task) => wireTask(task, bot.modelSelection)),
18464
18781
  ...messagePage(bot.threadId, limit),
18465
18782
  }, visible)),
18466
18783
  botQueuedMessages: visible.everything ? queued : Object.fromEntries(Object.entries(queued).filter(([threadId]) => visible.thread(threadId))),
@@ -18593,7 +18910,28 @@ const handleRequest = async (req, res) => {
18593
18910
  }
18594
18911
  roots = messageFileRootsForThread(senderId, threadId);
18595
18912
  }
18596
- const file = await openMessageFile(href, roots);
18913
+ // A link to a folder answers with its files; each one is then fetched
18914
+ // by plain name under the same message grant and file checks.
18915
+ const folderEntry = method === "POST" && body.entry !== undefined
18916
+ ? messageFolderEntryName(body.entry)
18917
+ : undefined;
18918
+ if (folderEntry === null)
18919
+ return json(res, 400, { error: "entry must be one file name" });
18920
+ let file;
18921
+ try {
18922
+ file = folderEntry
18923
+ ? await openMessageFolderEntry(href, folderEntry, roots)
18924
+ : await openMessageFile(href, roots);
18925
+ }
18926
+ catch (error) {
18927
+ // Only a client that asks for listings gets one; older clients keep the 400.
18928
+ if (body?.listFolder !== true || folderEntry || error?.code !== "directory")
18929
+ throw error;
18930
+ const listing = await listMessageFolder(href, roots);
18931
+ res.setHeader("x-openmausbot-folder", "1");
18932
+ res.setHeader("cache-control", "private, no-store");
18933
+ return json(res, 200, listing);
18934
+ }
18597
18935
  if ((streamsMessageImage || botAttachment?.kind === "image") && !file.mime.startsWith("image/")) {
18598
18936
  await file.handle.close();
18599
18937
  return json(res, 415, { error: "only images can be previewed here" });
@@ -19424,7 +19762,7 @@ const handleRequest = async (req, res) => {
19424
19762
  if (!body || typeof body !== "object" || Array.isArray(body)) {
19425
19763
  return json(res, 400, { error: "body must be a JSON object" });
19426
19764
  }
19427
- const allowed = new Set(["title", "pinned"]);
19765
+ const allowed = new Set(["title", "pinned", "turnTimeoutMinutes"]);
19428
19766
  if (Object.keys(body).some((key) => !allowed.has(key)))
19429
19767
  return json(res, 400, { error: "unsupported channel thread setting" });
19430
19768
  const existing = store.groupTaskByThread(group.id, m[2]);
@@ -19432,12 +19770,16 @@ const handleRequest = async (req, res) => {
19432
19770
  return json(res, 404, { error: "no such channel task" });
19433
19771
  const pinning = body.pinned !== undefined;
19434
19772
  const renaming = body.title !== undefined;
19435
- if (!pinning && !renaming)
19773
+ const limiting = Object.prototype.hasOwnProperty.call(body, "turnTimeoutMinutes");
19774
+ if (!pinning && !renaming && !limiting)
19436
19775
  return json(res, 400, { error: "unsupported channel thread setting" });
19437
19776
  if (pinning && typeof body.pinned !== "boolean")
19438
19777
  return json(res, 400, { error: "pinned must be a boolean" });
19439
19778
  if (renaming && typeof body.title !== "string")
19440
19779
  return json(res, 400, { error: "title must be a string" });
19780
+ const parsedLimit = limiting ? parseConversationTurnTimeout(body.turnTimeoutMinutes) : null;
19781
+ if (parsedLimit && !parsedLimit.ok)
19782
+ return json(res, 400, { error: parsedLimit.error });
19441
19783
  // Echoing the current title lets a newer client pin on an older server
19442
19784
  // without the old handler turning a missing title into "Untitled".
19443
19785
  // That echo is not a rename. A real rename stays blocked while working.
@@ -19445,10 +19787,19 @@ const handleRequest = async (req, res) => {
19445
19787
  const notYours = titleChange ? cloudThreadRefusal(auth, m[2]) : null;
19446
19788
  if (notYours)
19447
19789
  return json(res, 403, { error: notYours });
19448
- if (channelTaskBlocked(group) && !(pinning && !titleChange)) {
19790
+ // A longer limit applies to the next turn, so it can be saved while
19791
+ // this one is still running. A rename still waits.
19792
+ const settingsOnly = !titleChange && (pinning || limiting);
19793
+ if (channelTaskBlocked(group) && !settingsOnly) {
19449
19794
  return json(res, 409, { error: "this channel is working or waiting on you — finish that turn first" });
19450
19795
  }
19451
19796
  let task = existing;
19797
+ if (limiting && parsedLimit?.ok) {
19798
+ const limited = store.setGroupTaskTurnTimeout(group.id, m[2], parsedLimit.minutes);
19799
+ if (!limited)
19800
+ return json(res, 404, { error: "no such channel task" });
19801
+ task = limited;
19802
+ }
19452
19803
  if (pinning) {
19453
19804
  const pinned = store.setGroupTaskPinned(group.id, m[2], body.pinned === true);
19454
19805
  if (!pinned)
@@ -20282,10 +20633,12 @@ const handleRequest = async (req, res) => {
20282
20633
  }
20283
20634
  // patchBot persists first and emits the canonical bot change, which the
20284
20635
  // store listener above turns into the slim wire-format SSE broadcast.
20636
+ // Every thread that follows the bot moves with it; the selected thread
20637
+ // follows it from now on too, as if picked there.
20285
20638
  const bot = store.patchBot(existing.id, { modelSelection: checked.selection });
20286
20639
  if (!bot)
20287
20640
  return json(res, 404, { error: "no such bot" });
20288
- store.patchTask(bot.id, selected.threadId, { modelSelection: checked.selection,
20641
+ store.patchTask(bot.id, selected.threadId, { modelSelection: undefined,
20289
20642
  ...hostedModels?.resetTask(selected.modelSelection, checked.selection) });
20290
20643
  return json(res, 200, { bot: wireBot(bot) });
20291
20644
  }
@@ -20971,8 +21324,10 @@ const handleRequest = async (req, res) => {
20971
21324
  // and pins that already match stay; returning to Auto sweeps nothing.
20972
21325
  if (computerSpecified && requestedComputer !== undefined)
20973
21326
  store.clearAutoSurfacePins(m[1], requestedComputer);
21327
+ // A new bot model moves every thread that follows the bot; the selected
21328
+ // thread follows it from now on too.
20974
21329
  if (normalizedSelection && selectedTask)
20975
- store.patchTask(bot.id, selectedTask.threadId, { modelSelection: normalizedSelection,
21330
+ store.patchTask(bot.id, selectedTask.threadId, { modelSelection: undefined,
20976
21331
  ...hostedModels?.resetTask(selectedTask.modelSelection, normalizedSelection) });
20977
21332
  if (existingBot && (bot.browserProfile !== beforeBrowserProfile || bot.browser !== beforeBrowserEnabled)) {
20978
21333
  browserLive.closeForBot(bot.id);
@@ -21769,12 +22124,15 @@ const handleRequest = async (req, res) => {
21769
22124
  if (phoneSecretSubmissions.hasThread(bot.threadId)) {
21770
22125
  return json(res, 409, { error: "this task is securely saving a credential — try again when it finishes" });
21771
22126
  }
21772
- if (!registry.get(bot.modelSelection.instanceId)) {
22127
+ // A thread's own model that can't run gives way to the bot's when the
22128
+ // rerun starts (healThreadModel), so check the model it will run on.
22129
+ const rerunSelection = turnSelection(bot.id, bot.threadId) ?? bot.modelSelection;
22130
+ if (!registry.get(rerunSelection.instanceId)) {
21773
22131
  return json(res, 409, {
21774
- error: `provider instance "${bot.modelSelection.instanceId}" is unavailable — pick another model in settings`,
22132
+ error: `provider instance "${rerunSelection.instanceId}" is unavailable — pick another model in settings`,
21775
22133
  });
21776
22134
  }
21777
- const rerunInstance = registry.get(bot.modelSelection.instanceId);
22135
+ const rerunInstance = registry.get(rerunSelection.instanceId);
21778
22136
  const rerunRefusal = policyModelRefusal(rerunInstance);
21779
22137
  if (rerunRefusal)
21780
22138
  return json(res, 409, { error: rerunRefusal });
@@ -22205,7 +22563,7 @@ const handleRequest = async (req, res) => {
22205
22563
  // Who opened it decides who may answer its cards on a shared workspace.
22206
22564
  if (auth.kind === "session")
22207
22565
  threadStarters.set(task.threadId, actorKey(auth));
22208
- return json(res, 201, { bot: publicBot(store.bot(bot.id)), task: wireTask(task) });
22566
+ return json(res, 201, { bot: publicBot(store.bot(bot.id)), task: wireTask(task, store.bot(bot.id).modelSelection) });
22209
22567
  }
22210
22568
  m = path.match(/^\/api\/bots\/([\w-]+)\/tasks\/([\w-]+)$/);
22211
22569
  if (m && method === "POST") {
@@ -22223,7 +22581,7 @@ const handleRequest = async (req, res) => {
22223
22581
  const switched = store.switchTask(bot.id, m[2]);
22224
22582
  if (!switched)
22225
22583
  return json(res, 404, { error: "no such task" });
22226
- const switchedSettings = { ...wireBot(switched), tasks: store.tasks(switched.id).map(wireTask) };
22584
+ const switchedSettings = { ...wireBot(switched), tasks: store.tasks(switched.id).map((task) => wireTask(task, switched.modelSelection)) };
22227
22585
  // "0" is settings only, a positive page is a bounded transcript, and no
22228
22586
  // parameter is the whole thread — the one branch that materialises it.
22229
22587
  // Search results land through that branch (src/lib/focus-message.ts):
@@ -22305,7 +22663,7 @@ const handleRequest = async (req, res) => {
22305
22663
  const task = store.refreshTaskPermissions(profile.id, m[2]);
22306
22664
  if (!task)
22307
22665
  return json(res, 404, { error: "no such task" });
22308
- return json(res, 200, { task: wireTask(task), bot: publicBot(store.bot(profile.id)) });
22666
+ return json(res, 200, { task: wireTask(task, store.bot(profile.id).modelSelection), bot: publicBot(store.bot(profile.id)) });
22309
22667
  }
22310
22668
  const patch = {};
22311
22669
  if (body.projectId !== undefined) {
@@ -22411,7 +22769,7 @@ const handleRequest = async (req, res) => {
22411
22769
  const task = patch.modelSelection
22412
22770
  ? store.switchTaskModel(m[1], m[2], patch.modelSelection, body.updateBotDefault === true, body.resetApprovalToAsk === true, { ...patch, ...hostedModels?.resetTask(current.modelSelection, patch.modelSelection) })
22413
22771
  : store.patchTask(m[1], m[2], patch);
22414
- return json(res, 200, { task: wireTask(task), bot: publicBot(store.bot(m[1])) });
22772
+ return json(res, 200, { task: wireTask(task, store.bot(m[1]).modelSelection), bot: publicBot(store.bot(m[1])) });
22415
22773
  }
22416
22774
  if (m && method === "DELETE") {
22417
22775
  const bot = store.bot(m[1]);
@@ -22483,7 +22841,7 @@ const handleRequest = async (req, res) => {
22483
22841
  const task = store.retitleTask(m[1], m[2], startedFrom, title) ?? store.taskByThread(m[1], m[2]);
22484
22842
  if (!task)
22485
22843
  return json(res, 404, { error: "no such task" });
22486
- return json(res, 200, { task: wireTask(task) });
22844
+ return json(res, 200, { task: wireTask(task, store.bot(m[1]).modelSelection) });
22487
22845
  }
22488
22846
  finally {
22489
22847
  titleRegenerations.delete(m[2]);
@@ -23061,7 +23419,17 @@ const handleRequest = async (req, res) => {
23061
23419
  return json(res, 400, { error: "That does not look like an API key." });
23062
23420
  const url = typeof body?.url === "string" && body.url.trim() ? body.url.trim() : (saved && "url" in saved ? saved.url : undefined);
23063
23421
  res.setHeader("cache-control", "no-store");
23064
- return json(res, 200, await checkProviderKey({ provider: kind, key, url }));
23422
+ const verdict = await checkProviderKey({ provider: kind, key, url });
23423
+ // A Test is a real use the person chose: the engines on this key agree
23424
+ // with its verdict. Claude runs its key through its CLI, not here.
23425
+ if (kind !== "anthropic") {
23426
+ const base = providerBaseUrl(kind, url);
23427
+ if (verdict.ok)
23428
+ noteKeyAccepted(base, key);
23429
+ else if (verdict.reason === "rejected")
23430
+ noteKeyRejected(base, key);
23431
+ }
23432
+ return json(res, 200, verdict);
23065
23433
  }
23066
23434
  if (method === "GET" && path === "/api/decisions") {
23067
23435
  const rawLimit = url.searchParams.get("limit");
@@ -23514,7 +23882,7 @@ const handleRequest = async (req, res) => {
23514
23882
  mcpProbesInFlight -= 1;
23515
23883
  }
23516
23884
  }
23517
- // The loopback listener and authenticated paste-back complete the same flow.
23885
+ // The loopback listener, the public callback and authenticated paste-back complete the same flow.
23518
23886
  const mcpSignIn = /^\/api\/mcp\/servers\/([a-z][a-z0-9_-]{0,31})\/(sign-in|sign-out)(?:\/([0-9a-f-]{36}))?$/.exec(path);
23519
23887
  if (mcpSignIn) {
23520
23888
  const [, name, action, flowId] = mcpSignIn;
@@ -23550,7 +23918,14 @@ const handleRequest = async (req, res) => {
23550
23918
  return json(res, 200, { auth: await mcpOAuth.completeCallback(name, flowId, body.callbackUrl, owner) });
23551
23919
  }
23552
23920
  if (action === "sign-in" && method === "POST" && !flowId) {
23553
- const started = await mcpOAuth.start(name, url, undefined, owner);
23921
+ // A browser on another computer cannot reach this machine's
23922
+ // loopback: it comes back to the https address it is using now,
23923
+ // when that is one this server vouches for (mcpCallbackOrigin).
23924
+ const remote = auth.kind === "session" && (isProxied(req) || !isLoopbackHost(req.headers.host));
23925
+ const origin = remote
23926
+ ? mcpCallbackOrigin(provesSameOrigin(req) ? requestOrigin(req) : null, [CLOUD_HOME?.publicOrigin, hostedWorkspaceConfiguration()?.tenant.origin, savedCustomDomain(), FALLBACK_PUBLIC_URL])
23927
+ : null;
23928
+ const started = await mcpOAuth.start(name, url, undefined, owner, { remote, origin });
23554
23929
  if (auth.kind === "session" && !sessions.isLive(auth.session.id)) {
23555
23930
  mcpOAuth.revokeOwner(owner);
23556
23931
  return json(res, 401, { error: "Your session ended. Start a new sign-in." });
@@ -24091,6 +24466,12 @@ const handleRequest = async (req, res) => {
24091
24466
  browserCleanup.abort(request);
24092
24467
  throw error;
24093
24468
  }
24469
+ // A saved or cleared key starts over: neither it nor the key it
24470
+ // replaced stays marked as rejected.
24471
+ const resetKeys = PROVIDER_KEY_KINDS.flatMap((kind) => {
24472
+ const next = patch[kind]?.key;
24473
+ return next === undefined ? [] : [cfg[kind]?.key, next];
24474
+ });
24094
24475
  let configWriteCommitted = false;
24095
24476
  const externalSecretStorage = url.searchParams.get("secretStorage") === "external";
24096
24477
  try {
@@ -24156,6 +24537,9 @@ const handleRequest = async (req, res) => {
24156
24537
  }
24157
24538
  throw error;
24158
24539
  }
24540
+ for (const key of resetKeys)
24541
+ if (key)
24542
+ forgetKey(key);
24159
24543
  // Desktops already counting down move to the new window at once, in
24160
24544
  // every isolation mode; busy work still defers its expiry.
24161
24545
  if (patch.localVm?.idleTimeoutMinutes !== undefined) {
@@ -24772,7 +25156,7 @@ const handleRequest = async (req, res) => {
24772
25156
  catch (e) {
24773
25157
  const status = e?.status ?? 500;
24774
25158
  const candidateCode = e?.code;
24775
- const code = typeof candidateCode === "string" && ["guarded_busy", "guarded_branch", "guarded_permissions", "guarded_request_changed", "guarded_request_untracked"].includes(candidateCode)
25159
+ const code = typeof candidateCode === "string" && ["guarded_busy", "guarded_branch", "guarded_permissions", "guarded_request_changed", "guarded_request_untracked", "outside_workspace"].includes(candidateCode)
24776
25160
  ? candidateCode : undefined;
24777
25161
  return json(res, status, { error: e instanceof Error ? e.message : String(e), ...(code ? { code } : {}) });
24778
25162
  }
@@ -24922,12 +25306,22 @@ restoreChannelMessages();
24922
25306
  if (movedAutoPins)
24923
25307
  console.log(`Works on: moved ${movedAutoPins} auto-pinned thread(s) to their bot's current Works on`);
24924
25308
  }
24925
- server.listen(PORT, "127.0.0.1", () => {
25309
+ server.listen(PORT, "127.0.0.1", async () => {
24926
25310
  companyRuntimeReady();
24927
25311
  console.log(`openmausbot server on http://127.0.0.1:${PORT}`);
25312
+ // Words queued before the restart start first, ahead of anything sent from
25313
+ // now on. Their turns wait for the engines read at start like every turn.
24928
25314
  followupsReady = true;
24929
25315
  drainQueuedSends();
24930
25316
  drainQueuedChannelSends();
25317
+ // The work below wakes delegators, and a woken delegator keeps its session
25318
+ // only on an engine that records what it was handed, which that read finds
25319
+ // out: it starts once the read is done.
25320
+ if (enginesRead) {
25321
+ await enginesRead;
25322
+ if (companyShutdown)
25323
+ return;
25324
+ }
24931
25325
  // Startup work uses the same turn dispatcher and local tool endpoint as
24932
25326
  // ordinary chat. Start only once every registry is initialized and the
24933
25327
  // endpoint is listening; earlier dispatch can hit uninitialized bindings.