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
@@ -5,7 +5,7 @@
5
5
  import { createHash } from "node:crypto";
6
6
  import { chmodSync, existsSync, readFileSync, mkdirSync, rmSync, statSync, unlinkSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { writeFileAtomic } from "./atomic.js";
8
+ import { writeFileAtomic, writeFileAtomicIfChanged } from "./atomic.js";
9
9
  import { parseToolScope, toolScopeWidens } from "../shared/tool-scope.js";
10
10
  import { ensureSections, readSections, changeEmptySection } from "./section-context.js";
11
11
  import { removeBotFolder, soulFile, soulHash, writeSoulMirror } from "./bot-folder.js";
@@ -22,6 +22,7 @@ import { approvalModeFor, isApprovalMode, modelSwitchNeedsAsk } from "../shared/
22
22
  import { REMOVED_COMPUTER_DRIVER } from "./computer-engine-removal.js";
23
23
  import { isMentionBoundary, isMentionNameContinuation } from "../shared/mention-boundary.js";
24
24
  import { CONNECTOR_SLUG_PATTERN, CONNECTOR_TOOL_NAME_PATTERN } from "../shared/wire.js";
25
+ import { sameModelSelection } from "../shared/thread-model.js";
25
26
  export const groupWireProjectionIsExact = true;
26
27
  // Unicode's complete emoji sequences include flags, skin tones and ZWJ
27
28
  // combinations. Also allow unqualified single symbols (e.g. ♥), but not
@@ -32,13 +33,19 @@ export function isProjectEmoji(value) {
32
33
  }
33
34
  export const taskWireProjectionIsExact = true;
34
35
  /** The typed wire projection for one task. Pairs with the assertion above:
35
- * returning WireTask means an undeclared server field cannot ride silently. */
36
- export function toWireTask(task) {
37
- const { resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, handedMessages: _handedMessages, appliedCompactionId: _appliedCompactionId, contextFloor: _contextFloor, lastContextModel: _lastContextModel, surfaceSource, ...wire } = task;
36
+ * returning WireTask means an undeclared server field cannot ride silently.
37
+ * A thread with no model of its own follows its bot: the wire carries the
38
+ * model it runs on either way, so every client reads one effective model. */
39
+ export function toWireTask(task, botModel) {
40
+ const { resumeCursors: _resumeCursors, lastInstanceId: _lastInstanceId, handedMessages: _handedMessages, appliedCompactionId: _appliedCompactionId, contextFloor: _contextFloor, lastContextModel: _lastContextModel, surfaceSource, ...stored } = task;
41
+ const wire = { ...stored, modelSelection: structuredClone(task.modelSelection ?? botModel), followsBotModel: task.modelSelection === undefined };
38
42
  // Who pinned stays private. A client learns only whether the pin is the
39
43
  // machine's own record, so it never presents one as the person's choice.
40
44
  return surfaceSource === "auto" && wire.surface !== undefined ? { ...wire, surfaceAuto: true } : wire;
41
45
  }
46
+ /** The level a thread drops to when its engine changes under a level the new
47
+ * engine would have to confirm (modelSwitchNeedsAsk), as on every switch. */
48
+ const ASK_NOW = { approvalMode: "ask", autoApprove: false, alwaysAllow: [] };
42
49
  const TASK_PATCH_FIELDS = [
43
50
  "title", "projectId", "modelSelection", "approvalMode", "autoApprove", "alwaysAllow",
44
51
  "unread", "rewound", "archivedAt", "pinned", "pinnedMessageId", "resumeCursors", "lastInstanceId", "cwd",
@@ -326,6 +333,9 @@ export function parseConnectorTools(value) {
326
333
  }
327
334
  const BOTS_FILE = join(DATA_DIR, "bots.json");
328
335
  const GROUPS_FILE = join(DATA_DIR, "groups.json");
336
+ /** Written once the copies older builds made of a bot's model on its threads
337
+ * are cleared (Store load), so that cleanup runs once per data directory. */
338
+ const THREAD_MODEL_COPIES_CLEARED = join(DATA_DIR, "thread-model-copies-cleared");
329
339
  /** The registries carry souls, project paths and per-bot settings, so they are
330
340
  * owner-only like the other data-dir stores. A file written by an older
331
341
  * release (or loosened by hand) is tightened on load. Best effort: failing to
@@ -456,6 +466,7 @@ export class Store {
456
466
  threads = new Map();
457
467
  defaultSelection;
458
468
  completeNewBotSelection;
469
+ driverKindOf;
459
470
  listeners = new Set();
460
471
  /** A broken team registry must not prevent loading independent chat data. */
461
472
  registeringInitialSections = true;
@@ -465,9 +476,14 @@ export class Store {
465
476
  constructor(defaultSelection,
466
477
  /** Workspace-wide new-bot defaults (config newBots), applied to every
467
478
  * new bot's selection whichever path created it. */
468
- completeNewBotSelection = (selection) => selection) {
479
+ completeNewBotSelection = (selection) => selection,
480
+ /** The driver an engine instance runs, for the level a thread keeps when
481
+ * its engine changes (modelSwitchNeedsAsk). Unknown counts as another
482
+ * driver, so Full and Custom fall back to Ask. */
483
+ driverKindOf = () => undefined) {
469
484
  this.defaultSelection = defaultSelection;
470
485
  this.completeNewBotSelection = completeNewBotSelection;
486
+ this.driverKindOf = driverKindOf;
471
487
  mkdirSync(DATA_DIR, { recursive: true });
472
488
  for (const file of [BOTS_FILE, GROUPS_FILE])
473
489
  tightenRegistryFile(file);
@@ -696,11 +712,17 @@ export class Store {
696
712
  }
697
713
  if (groupsMigrated)
698
714
  this.saveGroups();
715
+ // Older builds copied a bot's model onto every thread it made, so a
716
+ // thread on exactly its bot's model is a copy, not a person's pick: once
717
+ // per data directory it is cleared and the thread follows its bot. A
718
+ // different model can't be told apart from a pick and stays. Only over a
719
+ // bot list this load could read, so an unreadable file is not skipped.
720
+ const clearModelCopies = !existsSync(THREAD_MODEL_COPIES_CLEARED) && (botsLoaded || !existsSync(BOTS_FILE));
699
721
  // bots saved before tasks existed have one endless thread; adopt it as
700
722
  // their first task so nothing is lost and nothing special-cases it
701
723
  for (const b of this.bots) {
702
- // Folders are organizational only. Preserve existing thread model
703
- // snapshots while discarding the unshipped folder-default setting.
724
+ // Folders are organizational only. Preserve threads' own models while
725
+ // discarding the unshipped folder-default setting.
704
726
  if (b.projects?.some((project) => "modelSelection" in project)) {
705
727
  b.projects = b.projects.map(({ id, name, emoji }) => ({ id, name, ...(isProjectEmoji(emoji) ? { emoji } : {}) }));
706
728
  botsMigrated = true;
@@ -730,8 +752,8 @@ export class Store {
730
752
  botsMigrated = true;
731
753
  }
732
754
  for (const task of b.tasks) {
733
- if (task.modelSelection === undefined) {
734
- task.modelSelection = structuredClone(b.modelSelection);
755
+ if (clearModelCopies && task.modelSelection !== undefined && sameModelSelection(task.modelSelection, b.modelSelection)) {
756
+ delete task.modelSelection;
735
757
  botsMigrated = true;
736
758
  }
737
759
  if (!task.resumeCursors) {
@@ -774,6 +796,8 @@ export class Store {
774
796
  }
775
797
  if (botsMigrated)
776
798
  this.saveBots();
799
+ if (clearModelCopies)
800
+ writeFileAtomic(THREAD_MODEL_COPIES_CLEARED, "Threads follow their bot's model; the copies older builds made are cleared.\n", { mode: 0o600 });
777
801
  // Search reads SQLite directly, so migrate every known legacy transcript
778
802
  // at startup rather than waiting until the user happens to open it. Only
779
803
  // pending JSON files are touched; already-migrated threads stay lazy.
@@ -896,10 +920,13 @@ export class Store {
896
920
  if (botsDirty)
897
921
  this.saveBots();
898
922
  }
923
+ // Many saves repeat what is already on disk (the same resume cursor, an
924
+ // unchanged task field), so both registries skip a byte-identical write.
925
+ // Events are still emitted by the callers either way.
899
926
  saveBots(bots = this.bots, registerSections = true) {
900
927
  if (registerSections)
901
928
  this.rememberSections([...this.bots, ...bots].map((bot) => bot.section));
902
- writeFileAtomic(BOTS_FILE, JSON.stringify(bots.map(({ busy: _busy, activity: _activity, ...bot }) => ({
929
+ writeFileAtomicIfChanged(BOTS_FILE, JSON.stringify(bots.map(({ busy: _busy, activity: _activity, ...bot }) => ({
903
930
  ...bot,
904
931
  tasks: bot.tasks?.map(({ busy: _taskBusy, activity: _taskActivity, turnStartedAt: _taskTurnStarted, ...task }) => persistedPin(task)),
905
932
  })), null, 2), { mode: 0o600 });
@@ -907,7 +934,7 @@ export class Store {
907
934
  saveGroups(groups = this.groups, registerSections = true) {
908
935
  if (registerSections)
909
936
  this.rememberSections(groups.map((group) => group.section));
910
- writeFileAtomic(GROUPS_FILE, JSON.stringify(groups.map(({ busyBotId: _busyBotId, turnStartedAt: _turnStartedAt, ...g }) => ({
937
+ writeFileAtomicIfChanged(GROUPS_FILE, JSON.stringify(groups.map(({ busyBotId: _busyBotId, turnStartedAt: _turnStartedAt, ...g }) => ({
911
938
  ...g,
912
939
  ...(g.tasks ? { tasks: g.tasks.map((task) => persistedPin(task)) } : {}),
913
940
  })), null, 2), { mode: 0o600 });
@@ -1120,6 +1147,9 @@ export class Store {
1120
1147
  else if (!patch.busyBotId)
1121
1148
  delete group.turnStartedAt;
1122
1149
  }
1150
+ if (Object.prototype.hasOwnProperty.call(patch, "turnTimeoutMinutes") && patch.turnTimeoutMinutes == null) {
1151
+ delete group.turnTimeoutMinutes;
1152
+ }
1123
1153
  if (!group.dm && Object.prototype.hasOwnProperty.call(patch, "pinnedMessageId")) {
1124
1154
  const active = this.activeGroupTask(group.id);
1125
1155
  if (active)
@@ -1260,6 +1290,20 @@ export class Store {
1260
1290
  this.emit({ type: "group", groupId });
1261
1291
  return task;
1262
1292
  }
1293
+ /** Null removes the conversation's own ceiling so the next turn uses the
1294
+ * global group limit. A sibling conversation is left alone. */
1295
+ setGroupTaskTurnTimeout(groupId, threadId, minutes) {
1296
+ const task = this.groupTaskByThread(groupId, threadId);
1297
+ if (!task)
1298
+ return null;
1299
+ if (minutes == null)
1300
+ delete task.turnTimeoutMinutes;
1301
+ else
1302
+ task.turnTimeoutMinutes = minutes;
1303
+ this.saveGroups();
1304
+ this.emit({ type: "group", groupId });
1305
+ return task;
1306
+ }
1263
1307
  renameGroupTask(groupId, threadId, title) {
1264
1308
  const task = this.groupTaskByThread(groupId, threadId);
1265
1309
  if (!task)
@@ -1626,7 +1670,6 @@ export class Store {
1626
1670
  createdAt: bot.createdAt,
1627
1671
  updatedAt: bot.createdAt,
1628
1672
  resumeCursors: {},
1629
- modelSelection: structuredClone(bot.modelSelection),
1630
1673
  unread: false,
1631
1674
  activity: "idle",
1632
1675
  busy: false,
@@ -1658,7 +1701,8 @@ export class Store {
1658
1701
  return bot;
1659
1702
  }
1660
1703
  /** All setup fields and the Chief's receipt commit before publishing any
1661
- * mutation. Model defaults never rewrite saved thread selections. */
1704
+ * mutation. A new model moves the threads that follow the bot, never a
1705
+ * thread's own model. */
1662
1706
  applyTeamSetup(request) {
1663
1707
  const chief = this.bot(request.botId);
1664
1708
  if (!chief)
@@ -1687,7 +1731,7 @@ export class Store {
1687
1731
  // a restricted Chief never creates a bot everyone sees.
1688
1732
  ...(chief.visibility && chief.visibility !== "everyone" ? { visibility: structuredClone(chief.visibility) } : {}),
1689
1733
  tasks: [{ threadId: operation.threadId, title: UNTITLED_THREAD, createdAt, updatedAt: createdAt, resumeCursors: {},
1690
- modelSelection: structuredClone(modelSelection), approvalMode: "ask", autoApprove: false,
1734
+ approvalMode: "ask", autoApprove: false,
1691
1735
  unread: false, activity: "idle", busy: false }],
1692
1736
  };
1693
1737
  // "" is the private-workspace spelling on proposal; the record
@@ -1702,13 +1746,7 @@ export class Store {
1702
1746
  const previous = nextBots[at];
1703
1747
  next = { ...previous, ...operation.fields };
1704
1748
  if (operation.fields.modelSelection)
1705
- next.tasks = previous.tasks?.map((task) => ({
1706
- ...task,
1707
- modelSelection: structuredClone(task.modelSelection ?? previous.modelSelection),
1708
- approvalMode: approvalModeFor(this.projectBotForTask(previous.id, task.threadId)),
1709
- autoApprove: task.autoApprove ?? previous.autoApprove,
1710
- alwaysAllow: structuredClone(task.alwaysAllow ?? previous.alwaysAllow ?? []),
1711
- }));
1749
+ next.tasks = this.withDefaultModel(previous, operation.fields.modelSelection);
1712
1750
  nextBots[at] = next;
1713
1751
  }
1714
1752
  if (operation.fields.chiefOfStaff === false)
@@ -1746,23 +1784,38 @@ export class Store {
1746
1784
  this.emit({ type: "bot", botId: chief.id });
1747
1785
  return result;
1748
1786
  }
1749
- /** One reviewed default-model change (propose_model): task stamping is
1750
- * identical to applyTeamSetup's update branch — saved per-thread
1751
- * selections are never rewritten, and a thread with no selection of its
1752
- * own is pinned to the previous default so it does not silently follow
1753
- * the new one. */
1787
+ /** Moving this thread from one model to another takes its level back to
1788
+ * Ask where the new engine would have to confirm it (modelSwitchNeedsAsk),
1789
+ * as on every engine switch. Read before anything changes. */
1790
+ switchNeedsAsk(botId, threadId, from, to) {
1791
+ if (from.instanceId === to.instanceId)
1792
+ return false;
1793
+ const thread = this.projectBotForTask(botId, threadId);
1794
+ return Boolean(thread) && modelSwitchNeedsAsk(approvalModeFor(thread), this.driverKindOf(from.instanceId), this.driverKindOf(to.instanceId));
1795
+ }
1796
+ /** A bot's threads as a change of its model to `next` leaves them. Each
1797
+ * keeps the level it runs at now (older threads may still inherit the
1798
+ * bot's, which may change in the same write); one that follows the bot
1799
+ * moves with it, back to Ask where the new engine would have to confirm
1800
+ * its level; a thread's own model stays. */
1801
+ withDefaultModel(bot, next) {
1802
+ return bot.tasks?.map((task) => ({
1803
+ ...task,
1804
+ approvalMode: approvalModeFor(this.projectBotForTask(bot.id, task.threadId)),
1805
+ autoApprove: task.autoApprove ?? bot.autoApprove,
1806
+ alwaysAllow: structuredClone(task.alwaysAllow ?? bot.alwaysAllow ?? []),
1807
+ ...(task.modelSelection === undefined && this.switchNeedsAsk(bot.id, task.threadId, bot.modelSelection, next) ? structuredClone(ASK_NOW) : {}),
1808
+ }));
1809
+ }
1810
+ /** One reviewed default-model change (propose_model), with
1811
+ * applyTeamSetup's task stamping (withDefaultModel): every thread that
1812
+ * follows the bot moves to the new model, and a thread's own model, which a
1813
+ * person picked there, stays. */
1754
1814
  applyModelDefault(id, modelSelection) {
1755
1815
  const previous = this.bot(id);
1756
1816
  if (!previous)
1757
1817
  return null;
1758
- const next = { ...previous, modelSelection: structuredClone(modelSelection) };
1759
- next.tasks = previous.tasks?.map((task) => ({
1760
- ...task,
1761
- modelSelection: structuredClone(task.modelSelection ?? previous.modelSelection),
1762
- approvalMode: approvalModeFor(this.projectBotForTask(previous.id, task.threadId)),
1763
- autoApprove: task.autoApprove ?? previous.autoApprove,
1764
- alwaysAllow: structuredClone(task.alwaysAllow ?? previous.alwaysAllow ?? []),
1765
- }));
1818
+ const next = { ...previous, modelSelection: structuredClone(modelSelection), tasks: this.withDefaultModel(previous, modelSelection) };
1766
1819
  this.saveBots(this.bots.map((candidate) => candidate.id === id ? next : candidate));
1767
1820
  this.bots = this.bots.map((candidate) => candidate.id === id ? next : candidate);
1768
1821
  this.emit({ type: "bot", botId: id });
@@ -1860,9 +1913,16 @@ export class Store {
1860
1913
  patch = { ...patch };
1861
1914
  delete patch.toolScope;
1862
1915
  }
1916
+ // A new model moves the threads that follow the bot; one whose level the
1917
+ // new engine would have to confirm goes back to Ask. That is a
1918
+ // revocation, so it holds in memory like the others below.
1919
+ const toAsk = patch.modelSelection ? (bot.tasks ?? []).filter((task) => task.modelSelection === undefined &&
1920
+ this.switchNeedsAsk(id, task.threadId, bot.modelSelection, patch.modelSelection)) : [];
1863
1921
  // Runtime revocations must become effective in memory even when disk is
1864
1922
  // unavailable. Profile edits use the separate atomic path below.
1865
1923
  Object.assign(bot, patch);
1924
+ for (const task of toAsk)
1925
+ Object.assign(task, structuredClone(ASK_NOW));
1866
1926
  const task = this.activeTask(id);
1867
1927
  if (task) {
1868
1928
  for (const key of ["resumeCursors", "rewound", "pinnedMessageId", "unread"]) {
@@ -2586,37 +2646,61 @@ export class Store {
2586
2646
  this.emit({ type: "bot", botId });
2587
2647
  return cleared;
2588
2648
  }
2589
- /** Model/provider changes are one configuration transaction: never publish
2590
- * a new provider before its confirmed approval downgrade, or change the
2591
- * default while leaving the selected thread behind after a write failure. */
2649
+ /** A person picks a model in one thread, and with updateBotDefault makes it
2650
+ * the bot's model too. A thread on its bot's model follows the bot: it
2651
+ * keeps no model of its own. Model/provider changes are one configuration
2652
+ * transaction: never publish a new provider before its confirmed approval
2653
+ * downgrade, or change the default while leaving the selected thread
2654
+ * behind after a write failure. */
2592
2655
  switchTaskModel(botId, threadId, selection, updateBotDefault, resetApprovalToAsk, taskPatch = {}) {
2593
2656
  const bot = this.bot(botId);
2594
2657
  const task = this.taskByThread(botId, threadId);
2595
2658
  if (!bot || !task)
2596
2659
  return null;
2597
- const patch = { modelSelection: structuredClone(selection),
2598
- ...(resetApprovalToAsk ? { approvalMode: "ask", autoApprove: false, alwaysAllow: [] } : {}) };
2599
- const nextTask = persistedPin({ ...task, ...taskPatch, ...patch,
2660
+ const approval = resetApprovalToAsk ? structuredClone(ASK_NOW) : {};
2661
+ const own = sameModelSelection(selection, updateBotDefault ? selection : bot.modelSelection) ? undefined : structuredClone(selection);
2662
+ const nextTask = persistedPin({ ...task, ...taskPatch, modelSelection: own, ...approval,
2600
2663
  ...(typeof taskPatch.title === "string" ? { title: taskPatch.title.trim().slice(0, 80) || UNTITLED_THREAD } : {}) });
2601
- // Older threads may still inherit settings. Freeze their effective
2602
- // values before updating the default so "other threads unchanged" also
2603
- // holds for workspaces created before per-thread approval settings.
2604
- const nextTasks = bot.tasks.map((candidate) => candidate === task ? nextTask : !updateBotDefault ? candidate : {
2605
- ...candidate,
2606
- modelSelection: structuredClone(candidate.modelSelection ?? bot.modelSelection),
2607
- approvalMode: approvalModeFor(this.projectBotForTask(botId, candidate.threadId)),
2608
- autoApprove: candidate.autoApprove ?? bot.autoApprove,
2609
- alwaysAllow: structuredClone(candidate.alwaysAllow ?? bot.alwaysAllow ?? []),
2610
- });
2611
- const next = { ...bot, ...(updateBotDefault ? patch : {}),
2612
- tasks: nextTasks };
2613
- this.saveBots(this.bots.map((candidate) => candidate === bot ? next : candidate));
2664
+ // A new bot model moves the threads that follow it (withDefaultModel);
2665
+ // every other thread keeps its model and the level it runs at now.
2666
+ const others = updateBotDefault ? this.withDefaultModel(bot, selection) : bot.tasks;
2667
+ const nextTasks = bot.tasks.map((candidate, index) => candidate === task ? nextTask : others[index]);
2668
+ const botPatch = updateBotDefault ? { modelSelection: structuredClone(selection), ...approval } : {};
2669
+ this.saveBots(this.bots.map((candidate) => candidate === bot ? { ...bot, ...botPatch, tasks: nextTasks } : candidate));
2614
2670
  bot.tasks.forEach((candidate, index) => Object.assign(candidate, nextTasks[index]));
2615
- if (updateBotDefault)
2616
- Object.assign(bot, patch);
2671
+ Object.assign(bot, botPatch);
2617
2672
  this.emit({ type: "bot", botId });
2618
2673
  return task;
2619
2674
  }
2675
+ /** These threads follow their bot's model again: each one's own model is
2676
+ * cleared ("Switch them too", "Use the bot's model", or a model that can't
2677
+ * run here). A level the bot's engine would have to confirm goes back to
2678
+ * Ask, as on every engine switch, and `reset` adds what a changed model
2679
+ * needs (a hosted workspace's fresh session). One write. Returns the
2680
+ * threads that changed; a listed thread that already follows stays as it is. */
2681
+ followBotModel(botId, threadIds, reset = () => ({})) {
2682
+ const bot = this.bot(botId);
2683
+ if (!bot?.tasks)
2684
+ return [];
2685
+ const listed = new Set(threadIds);
2686
+ const patches = bot.tasks.map((task) => !listed.has(task.threadId) || task.modelSelection === undefined ? null : {
2687
+ ...reset(task.modelSelection, bot.modelSelection),
2688
+ modelSelection: undefined,
2689
+ ...(this.switchNeedsAsk(botId, task.threadId, task.modelSelection, bot.modelSelection) ? structuredClone(ASK_NOW) : {}),
2690
+ });
2691
+ const moved = bot.tasks.filter((_, index) => patches[index]).map((task) => task.threadId);
2692
+ if (!moved.length)
2693
+ return [];
2694
+ const nextTasks = bot.tasks.map((task, index) => patches[index] ? { ...task, ...patches[index] } : task);
2695
+ this.saveBots(this.bots.map((candidate) => candidate === bot ? { ...bot, tasks: nextTasks } : candidate));
2696
+ bot.tasks.forEach((task, index) => { if (patches[index])
2697
+ Object.assign(task, patches[index]); });
2698
+ const active = bot.tasks.find((task) => task.threadId === bot.threadId);
2699
+ if (active)
2700
+ this.mirrorActiveTask(bot, active);
2701
+ this.emit({ type: "bot", botId });
2702
+ return moved;
2703
+ }
2620
2704
  /** One durable write: never leave only part of a bot's threads updated. */
2621
2705
  setAllThreadApprovalMode(botId, mode) {
2622
2706
  const bot = this.bot(botId);
@@ -2656,9 +2740,9 @@ export class Store {
2656
2740
  bot.pinnedMessageId = task.pinnedMessageId;
2657
2741
  }
2658
2742
  /** A fresh context on the same bot: new thread, new session, same
2659
- * persona/tools/computer. Becomes the active task. It runs on the bot's
2660
- * default model unless the caller hands it one (a thread opened from
2661
- * another of this bot's threads keeps that thread's model). */
2743
+ * persona/tools/computer. Becomes the active task. It follows the bot's
2744
+ * model unless the caller hands it another one: a thread opened from
2745
+ * another of this bot's threads keeps the model a person picked there. */
2662
2746
  createTask(botId, title, activate = true, projectId, openedBy, approvalMode, modelSelection) {
2663
2747
  const bot = this.bot(botId);
2664
2748
  if (!bot)
@@ -2674,7 +2758,7 @@ export class Store {
2674
2758
  ...(projectId ? { projectId } : {}),
2675
2759
  ...(openedBy ? { openedBy: structuredClone(openedBy) } : {}),
2676
2760
  resumeCursors: {},
2677
- modelSelection: structuredClone(modelSelection ?? bot.modelSelection),
2761
+ ...(modelSelection && !sameModelSelection(modelSelection, bot.modelSelection) ? { modelSelection: structuredClone(modelSelection) } : {}),
2678
2762
  approvalMode: approvalMode ?? approvalModeFor(bot),
2679
2763
  autoApprove: approvalMode ? false : Boolean(bot.autoApprove),
2680
2764
  alwaysAllow: approvalMode ? [] : [...(bot.alwaysAllow ?? [])],
@@ -2780,10 +2864,14 @@ export class Store {
2780
2864
  this.emit({ type: "bot", botId });
2781
2865
  return bot;
2782
2866
  }
2867
+ /** A first run: no bot yet, so seedIfEmpty makes one. */
2868
+ needsSeed() {
2869
+ return this.bots.length === 0;
2870
+ }
2783
2871
  /** First-run seed: one bot so the app never opens empty — it gets a
2784
2872
  * random friendly name like every other bot. */
2785
2873
  seedIfEmpty() {
2786
- if (this.bots.length)
2874
+ if (!this.needsSeed())
2787
2875
  return;
2788
2876
  this.createBot();
2789
2877
  }
@@ -123,7 +123,7 @@ export function customMcpPrompt(names) {
123
123
  }
124
124
  export const CREDENTIAL_PROMPT = " If a supported API key is missing for the service actually needed, use request_credential to create a secure credential request. Before requesting a computer-provider key, inspect the configured targets with select_computer; an existing self-hosted VPS does not need Boat credentials. Do not request a different provider's key merely because a task mentions cloud. A freshly QR-paired mobile app or the desktop app can show the secure entry card. Never claim it opened unless the request succeeded, and never ask the user to paste credentials into chat.";
125
125
  export const THREADS_PROMPT = " A thread is one conversation with its own history and its own run; a bot can have several running at once, and the person sees them as rows under that bot. Use start_thread to open one on yourself for separate work, or on a teammate to hand them a job that should run on its own. Use list_threads to see how the ones you opened are going. When you mention a thread to the person, write its title as #Title so it links. Do not use a ticket comment, a note, or a room post as a stand-in for a thread.";
126
- const PROPOSAL_RESULT_PROMPT = " Follow the tool result: with granted Full Access it may report applied immediately; then continue the requested work without asking for another confirmation. If it reports a pending review, end the turn and wait for the in-app decision. Never claim success before an applied result, and report failures honestly. Full Access does not grant another bot broader permissions.";
126
+ const PROPOSAL_RESULT_PROMPT = " Follow the tool result: a change to your own routines, skills, profile or model applies immediately and the person sees it with an Undo; then continue the requested work without asking for another confirmation. A change for another bot may wait for the person. If it reports a pending review, end the turn and wait for the in-app decision. Never claim success before an applied result, and report failures honestly. Full Access does not grant another bot broader permissions.";
127
127
  export const ROUTINE_PROMPT = " If the user explicitly asks to list or review, schedule, run, or change routines, use list_routines and propose_routine or propose_routine_action. Keep run_on omitted or maus to use the bot's current model and configured computer, including its VPS. A routine's box (legacy cloud) destination runs on the bot's cloud computer, not the configured VPS; choose it only when the user explicitly wants the cloud computer. Convert calendar requests such as the first or last day of each month or the second Monday to a five-field cron expression with an explicit IANA timezone; use interval for elapsed every-N-minutes work. Never replace a calendar rule with daily AI date checking or an approximate weekly schedule; clarify ambiguous or unsupported requests." + PROPOSAL_RESULT_PROMPT;
128
128
  export const ROUTINE_EXECUTION_PROMPT = " Execute this routine now: use available peer tools for required handoffs rather than merely announcing that you will wait; after an accepted delegation, end this turn for automatic resumption, and report a concrete blocker if no handoff is possible.";
129
129
  export const LEARN_PROMPT = " If the user sends /learn or asks you to save a reusable procedure from this work, use skills_list and skill_manage. Create new skills; update an existing learned skill only when the user explicitly asks to revise that exact name. Include source provenance." + PROPOSAL_RESULT_PROMPT;
@@ -78,6 +78,9 @@ export function createTeamBackup(store, routines, name) {
78
78
  ? { botId: task.closedBy.botId, name: task.closedBy.name, at: task.closedBy.at }
79
79
  : undefined,
80
80
  pinned: task.pinned === true ? true : undefined,
81
+ turnTimeoutMinutes: "turnTimeoutMinutes" in task && typeof task.turnTimeoutMinutes === "number"
82
+ ? task.turnTimeoutMinutes
83
+ : undefined,
81
84
  activeLeafId: store.activeLeaf(task.threadId),
82
85
  messages: store.messagesFor(task.threadId).map((message) => ({
83
86
  id: message.id, role: message.role, text: messageText(message), at: message.at,
@@ -97,7 +100,10 @@ export function createTeamBackup(store, routines, name) {
97
100
  ? { kind: "mentions" }
98
101
  : group.defaultResponder.kind === "auto" && group.defaultResponder.fallbackBotId && !memberIds.includes(group.defaultResponder.fallbackBotId)
99
102
  ? { kind: "auto" } : group.defaultResponder,
100
- activeTask: group.threadId, tasks: history(group),
103
+ activeTask: group.threadId,
104
+ tasks: history(group).map((task, index) => group.dm && index === 0 && typeof group.turnTimeoutMinutes === "number"
105
+ ? { ...task, turnTimeoutMinutes: group.turnTimeoutMinutes }
106
+ : task),
101
107
  };
102
108
  });
103
109
  const validRoutines = routines.filter((routine) => {
@@ -197,8 +203,9 @@ export function importTeamBackup(store, routines, input, selection, options = {}
197
203
  const bot = store.bot(botIds.get(source.key));
198
204
  const tasks = source.tasks.map((task, i) => {
199
205
  const record = {
206
+ // Each imported thread follows the imported bot's model.
200
207
  threadId: i === 0 ? bot.threadId : newId(), title: task.title, createdAt: task.createdAt, resumeCursors: {},
201
- modelSelection: structuredClone(bot.modelSelection), activity: "idle", busy: false, unread: false,
208
+ activity: "idle", busy: false, unread: false,
202
209
  ...(task.titleFromFirstMessage ? { titleFromFirstMessage: true } : {}),
203
210
  };
204
211
  // Same rule as a message's `from`: the opener is remapped to its
@@ -229,9 +236,16 @@ export function importTeamBackup(store, routines, input, selection, options = {}
229
236
  groups.push(group);
230
237
  groupIds.set(source.key, group.id);
231
238
  const responder = source.defaultResponder;
232
- store.patchGroup(group.id, { bulletin: source.bulletin, setupCompletedAt: Date.now(), defaultResponder: responder.kind === "member" ? { kind: "member", botId: botIds.get(responder.botId) }
239
+ store.patchGroup(group.id, {
240
+ bulletin: source.bulletin,
241
+ setupCompletedAt: Date.now(),
242
+ ...(source.dm && source.tasks[0]?.turnTimeoutMinutes != null
243
+ ? { turnTimeoutMinutes: source.tasks[0].turnTimeoutMinutes }
244
+ : {}),
245
+ defaultResponder: responder.kind === "member" ? { kind: "member", botId: botIds.get(responder.botId) }
233
246
  : responder.kind === "auto" ? { kind: "auto", ...(responder.fallbackBotId ? { fallbackBotId: botIds.get(responder.fallbackBotId) } : {}) }
234
- : responder });
247
+ : responder
248
+ });
235
249
  // Use the existing task APIs for rooms; direct-message rooms have one.
236
250
  const threads = source.tasks.map((task, i) => {
237
251
  if (i === 0) {
@@ -248,6 +262,7 @@ export function importTeamBackup(store, routines, input, selection, options = {}
248
262
  updatedAt: task.messages.reduce((max, message) => Math.max(max, message.at), task.createdAt),
249
263
  ...(task.pinned === true ? { pinned: true } : {}),
250
264
  ...(task.titleFromFirstMessage ? { titleFromFirstMessage: true } : {}),
265
+ ...(task.turnTimeoutMinutes != null ? { turnTimeoutMinutes: task.turnTimeoutMinutes } : {}),
251
266
  }));
252
267
  store.switchGroupTask(group.id, threads[source.tasks.findIndex((task) => task.key === source.activeTask)]);
253
268
  }
@@ -0,0 +1,25 @@
1
+ import { readyToRun, signedOut } from "./default-model-selection.js";
2
+ import { sameModelSelection } from "../shared/thread-model.js";
3
+ function canRunOn(engine, selection, context) {
4
+ return Boolean(engine?.enabled) && engine.snapshot.state === "available" && context.refusal?.(engine) === undefined &&
5
+ context.allows?.(selection) !== false;
6
+ }
7
+ /** Why this thread's own model gives way to its bot's, or null when the turn
8
+ * runs on it as picked:
9
+ * - "unavailable": its engine is gone, disabled, unavailable or not allowed
10
+ * (by the organisation, or a hosted workspace's assigned models);
11
+ * - "signed-out": its engine is signed out and the bot's model is on another
12
+ * engine that can run. On the same engine the turn runs as picked, so the
13
+ * engine's own sign-in prompt shows; a fallback never hides it. */
14
+ export function threadModelFallback(own, botModel, engine, context = {}) {
15
+ if (sameModelSelection(own, botModel))
16
+ return null;
17
+ const mine = engine(own.instanceId);
18
+ if (!canRunOn(mine, own, context))
19
+ return "unavailable";
20
+ if (!signedOut(mine))
21
+ return null;
22
+ // On the same engine the bot's model is signed out too, so it never stands in.
23
+ const theirs = engine(botModel.instanceId);
24
+ return canRunOn(theirs, botModel, context) && readyToRun(theirs, context) ? "signed-out" : null;
25
+ }
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  /**
5
5
  * Automatic continuity: when a turn ends without a final answer and the work
6
- * is resumable (budget cap or tool errors), persist a structured handoff and
6
+ * is resumable (a budget cap), persist a structured handoff and
7
7
  * let the harness start a `Continue:` thread on the same bot — transparent to
8
8
  * the bot, which just sees a normal new thread seeded with the handoff.
9
9
  *
@@ -82,23 +82,21 @@ export function writeTurnHandoff(input) {
82
82
  return null;
83
83
  }
84
84
  }
85
- /** Provider config errors are not resumable: looping on them burns budget and
86
- * masks the real fix (change the model or the tool set). Everything else that
87
- * stops a turn without a final answer is a candidate for auto-continuation. */
88
- const NOT_RESUMABLE = /not a multimodal model|unknown model|does not support|tool arguments do not match/i;
85
+ /** The harness's own budget stops, in its own words: the chat driver's
86
+ * "Stopped after 64 steps without a final answer…" and the older model-call
87
+ * cap. Matched from the start of the failure, so a provider's error text can
88
+ * never read as one: "upstream HTTP 429: Rate limit reached" is not a spent
89
+ * budget. */
90
+ const CAP_STOP = /^(?:stopped after \d+ [a-z ]+ without a final answer|model-call limit reached)/i;
89
91
  /** Classify a terminal turn as resumable. Returns the reason for the
90
92
  * continuation (`cap` = budget exhausted), or null when the turn must not be
91
- * auto-continued. A tool error is never continued: it includes a person's or
92
- * the approval policy's denial, and a new unattended thread would retry what
93
- * was refused. */
93
+ * auto-continued. Only a cap is continued. A provider error (a rate limit, a
94
+ * quota, a rejected key, a model that cannot take the request) fails the same
95
+ * way in a new thread: continuing would go straight back to the provider that
96
+ * just refused. A tool error includes a person's or the approval policy's
97
+ * denial, and a new unattended thread would retry what was refused. */
94
98
  export function classifyContinuable(_stopReason, failure) {
95
- if (!failure)
96
- return null;
97
- if (NOT_RESUMABLE.test(failure))
98
- return null;
99
- if (/limit reached|stopped after \d+ steps|without a final answer/i.test(failure))
100
- return "cap";
101
- return null;
99
+ return failure && CAP_STOP.test(failure) ? "cap" : null;
102
100
  }
103
101
  const CONTINUE_TITLE = /^Continue: /;
104
102
  const MAX_PER_BOT_PER_HOUR = 5;
@@ -131,7 +129,10 @@ export function makeCapContinuationSubscriber(deps) {
131
129
  if (CONTINUE_TITLE.test(deadTask.title ?? ""))
132
130
  return;
133
131
  const approvalMode = deadTask.approvalMode === "ask" || deadTask.approvalMode === "full" ? deadTask.approvalMode : undefined;
134
- const contTask = deps.store.createTask(bot.id, `Continue: ${(deadTask.title ?? "task").slice(0, 90)}`, true, deadTask.projectId, undefined, approvalMode, deadTask.modelSelection);
132
+ const contTask = deps.store.createTask(
133
+ // Not activated: the person's open thread stays theirs, and their next
134
+ // message goes where they were, not into the continuation.
135
+ bot.id, `Continue: ${(deadTask.title ?? "task").slice(0, 90)}`, false, deadTask.projectId, undefined, approvalMode, deadTask.modelSelection);
135
136
  if (!contTask)
136
137
  return;
137
138
  rec.count += 1;
@@ -84,11 +84,16 @@ export function createWebhookIngressHandler(manager, claimRequest) {
84
84
  let release;
85
85
  try {
86
86
  release = claimRequest?.();
87
- const pathSecret = match[2] ? decodeURIComponent(match[2]) : "";
88
- const secret = pathSecret || bearerSecret(req);
87
+ let secret = "";
88
+ try {
89
+ secret = (match[2] ? decodeURIComponent(match[2]) : "") || bearerSecret(req);
90
+ }
91
+ catch {
92
+ // A malformed escape is not a secret this server issued.
93
+ }
89
94
  // Reject bad capability URLs before buffering or parsing attacker input.
90
95
  if (!manager.authorize(match[1], secret)) {
91
- manager.recordRejected(match[1], 401, "Invalid webhook URL or secret", {
96
+ manager.recordUnauthorized(match[1], {
92
97
  contentType: header(req, "content-type"),
93
98
  eventName: eventName(req),
94
99
  deliveryId: deliveryId(req),