openmausbot 0.1.85 → 0.1.87

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 (262) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/assets/abnfDiagram-O67JEVCF-BTiuL79F.js +1 -0
  62. package/dist/assets/arc-C88Blc2v.js +1 -0
  63. package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
  64. package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
  65. package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
  66. package/dist/assets/channel-taCNRlUU.js +1 -0
  67. package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
  68. package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
  69. package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
  70. package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
  71. package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
  72. package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
  73. package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
  74. package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
  75. package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
  76. package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
  77. package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
  78. package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
  79. package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
  80. package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
  81. package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
  82. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  83. package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
  84. package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
  85. package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
  86. package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
  87. package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
  88. package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
  89. package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
  90. package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
  91. package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
  92. package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
  93. package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
  94. package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
  95. package/dist/assets/graph-D8UmR1vK.js +1 -0
  96. package/dist/assets/index-BwdMAQ6p.css +1 -0
  97. package/dist/assets/index-ByJennr7.js +581 -0
  98. package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
  99. package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
  100. package/dist/assets/init-Gi6I4Gst.js +1 -0
  101. package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
  102. package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
  103. package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
  104. package/dist/assets/linear-DBJGPmjT.js +1 -0
  105. package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
  106. package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
  107. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  108. package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
  109. package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
  110. package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
  111. package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
  112. package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
  113. package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
  114. package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
  115. package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
  116. package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
  117. package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
  118. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  119. package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
  120. package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
  121. package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
  122. package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
  123. package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
  124. package/dist/index.html +2 -2
  125. package/dist-server/browser-proxy.js +2 -1
  126. package/dist-server/container-mcp.js +1047 -173
  127. package/dist-server/drivers/agents-proxy.js +16121 -1504
  128. package/dist-server/drivers/phone-proxy.js +33 -2
  129. package/dist-server/enterprise/LICENSE +32 -0
  130. package/dist-server/enterprise/server/index.js +5 -5
  131. package/dist-server/index.js +16134 -7446
  132. package/dist-server/local-computer-proxy.js +3 -3
  133. package/dist-server/local-computer.js +53 -17
  134. package/dist-server/mcp-server.js +3 -1
  135. package/dist-server/openmausbot.js +9353 -6124
  136. package/dist-server/pair-cli.js +9353 -6124
  137. package/dist-server/permission-proxy.js +29 -9
  138. package/dist-server/server/admin-activity.js +410 -0
  139. package/dist-server/server/attachments.js +55 -0
  140. package/dist-server/server/auto-approve.js +4 -3
  141. package/dist-server/server/auto-vm-claims.js +1 -0
  142. package/dist-server/server/bot-package.js +18 -292
  143. package/dist-server/server/bot-profile.js +5 -17
  144. package/dist-server/server/bot-visibility.js +462 -0
  145. package/dist-server/server/box-create-idempotency.js +3 -3
  146. package/dist-server/server/box.js +59 -56
  147. package/dist-server/server/browser-proxy.js +3 -2
  148. package/dist-server/server/browser-runtime.js +38 -2
  149. package/dist-server/server/channel-queue.js +2 -0
  150. package/dist-server/server/cli-prompts.js +24 -2
  151. package/dist-server/server/cli.js +81 -12
  152. package/dist-server/server/command-allowlist.js +154 -0
  153. package/dist-server/server/composio.js +49 -11
  154. package/dist-server/server/computer-wait.js +22 -9
  155. package/dist-server/server/config.js +133 -17
  156. package/dist-server/server/connector-verdict.js +141 -0
  157. package/dist-server/server/container-computer.js +1 -1
  158. package/dist-server/server/decision-log.js +209 -48
  159. package/dist-server/server/default-model-selection.js +29 -3
  160. package/dist-server/server/delegations.js +97 -44
  161. package/dist-server/server/drivers/acp/core.js +163 -33
  162. package/dist-server/server/drivers/acp/grok.js +4 -3
  163. package/dist-server/server/drivers/agents-call.js +1162 -0
  164. package/dist-server/server/drivers/agents-catalog.js +689 -0
  165. package/dist-server/server/drivers/agents-client.js +28 -0
  166. package/dist-server/server/drivers/agents-proxy.js +18 -1722
  167. package/dist-server/server/drivers/boxagent.js +174 -36
  168. package/dist-server/server/drivers/builtIn.js +2 -0
  169. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  170. package/dist-server/server/drivers/chat-images.js +54 -0
  171. package/dist-server/server/drivers/chat-mcp-tools.js +55 -10
  172. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  173. package/dist-server/server/drivers/claude.js +28 -8
  174. package/dist-server/server/drivers/codex-instructions.js +48 -6
  175. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  176. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  177. package/dist-server/server/drivers/codex.js +121 -34
  178. package/dist-server/server/drivers/grok.js +2 -1
  179. package/dist-server/server/drivers/mistral.js +115 -0
  180. package/dist-server/server/drivers/openai-chat.js +155 -32
  181. package/dist-server/server/drivers/openai-compat.js +16 -8
  182. package/dist-server/server/drivers/permission-command.js +35 -0
  183. package/dist-server/server/drivers/phone-proxy.js +47 -2
  184. package/dist-server/server/drivers/pi.js +75 -4
  185. package/dist-server/server/drivers/prompt-split.js +87 -0
  186. package/dist-server/server/enterprise.js +97 -16
  187. package/dist-server/server/external-runtime.js +83 -0
  188. package/dist-server/server/fleet.js +1 -1
  189. package/dist-server/server/group-thread-usage.js +103 -0
  190. package/dist-server/server/guarded-requests.js +60 -0
  191. package/dist-server/server/harness/http.js +61 -0
  192. package/dist-server/server/harness/registry.js +1 -0
  193. package/dist-server/server/hosted-models.js +151 -0
  194. package/dist-server/server/hosted-slack.js +11 -0
  195. package/dist-server/server/index.js +3091 -658
  196. package/dist-server/server/local-computer.js +72 -23
  197. package/dist-server/server/managed-desktop.js +79 -14
  198. package/dist-server/server/managed-policy.js +153 -0
  199. package/dist-server/server/mcp-bridge.js +6 -6
  200. package/dist-server/server/message-db.js +28 -0
  201. package/dist-server/server/model-context-window.js +7 -0
  202. package/dist-server/server/model-prices.js +121 -0
  203. package/dist-server/server/new-bot-defaults.js +115 -0
  204. package/dist-server/server/notify.js +10 -1
  205. package/dist-server/server/org-library.js +1051 -0
  206. package/dist-server/server/package-export.js +627 -39
  207. package/dist-server/server/package-import.js +458 -0
  208. package/dist-server/server/package-parts.js +87 -0
  209. package/dist-server/server/peer-approval.js +16 -7
  210. package/dist-server/server/peer-roster.js +23 -2
  211. package/dist-server/server/presets.js +492 -0
  212. package/dist-server/server/provider-key-check.js +3 -2
  213. package/dist-server/server/published-teams.js +80 -0
  214. package/dist-server/server/recent-work.js +15 -0
  215. package/dist-server/server/request-auth.js +96 -0
  216. package/dist-server/server/room-handoffs.js +44 -14
  217. package/dist-server/server/routes/bot-presets.js +25 -0
  218. package/dist-server/server/routes/hosted-slack.js +20 -0
  219. package/dist-server/server/routes/table.js +14 -0
  220. package/dist-server/server/routine-requests.js +6 -3
  221. package/dist-server/server/routines.js +63 -1
  222. package/dist-server/server/section-context.js +10 -0
  223. package/dist-server/server/skills.js +34 -61
  224. package/dist-server/server/spend.js +111 -16
  225. package/dist-server/server/steer-queue.js +9 -3
  226. package/dist-server/server/store.js +550 -35
  227. package/dist-server/server/surface.js +10 -4
  228. package/dist-server/server/system-prompt.js +12 -4
  229. package/dist-server/server/team-backup.js +16 -3
  230. package/dist-server/server/team-computers.js +10 -0
  231. package/dist-server/server/team-library.js +6 -2
  232. package/dist-server/server/team-manifest.js +4 -3
  233. package/dist-server/server/team-setup-requests.js +16 -5
  234. package/dist-server/server/thread-events.js +17 -1
  235. package/dist-server/server/thread-starters.js +53 -0
  236. package/dist-server/server/turn-context.js +2 -0
  237. package/dist-server/server/turn-watchdog.js +12 -3
  238. package/dist-server/server/usage-ledger.js +43 -9
  239. package/dist-server/server/vps-computer.js +139 -51
  240. package/dist-server/server/vps-container-mcp.js +7 -0
  241. package/dist-server/server/vps-ssh.js +36 -7
  242. package/dist-server/server/workspace-backup-policy.js +21 -2
  243. package/dist-server/server/workspace-backup.js +7 -4
  244. package/dist-server/server/workspace.js +1 -1
  245. package/dist-server/shared/approval-mode.js +1 -1
  246. package/dist-server/shared/ask-question.js +188 -12
  247. package/dist-server/shared/bot-profile.js +15 -0
  248. package/dist-server/shared/command-allowlist.js +1 -0
  249. package/dist-server/shared/credential-request.js +1 -1
  250. package/dist-server/shared/mention-boundary.js +15 -0
  251. package/dist-server/shared/package-format.js +967 -0
  252. package/dist-server/shared/routines.js +7 -1
  253. package/dist-server/shared/skill-md.js +64 -0
  254. package/dist-server/shared/team-backup.js +9 -0
  255. package/dist-server/shared/wire.js +4 -0
  256. package/dist-server/src/lib/composer-attachments.js +16 -0
  257. package/dist-server/vps-container-mcp.js +1147 -179
  258. package/enterprise/README.md +33 -12
  259. package/enterprise/server/index.js +5 -5
  260. package/package.json +1 -1
  261. package/dist/assets/index-Ba9G44HI.js +0 -310
  262. package/dist/assets/index-CkRNp7wX.css +0 -1
@@ -7,6 +7,7 @@ import { join } from "node:path";
7
7
  import { z } from "zod";
8
8
  import { normalizeImageGenerationUrl } from "../shared/image-generation.js";
9
9
  import { writeFileAtomic } from "./atomic.js";
10
+ import { newBotDefaultsSchema } from "./new-bot-defaults.js";
10
11
  import { EFFORT_LEVELS } from "../shared/wire.js";
11
12
  import { isModelVariant } from "./contracts.js";
12
13
  import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-icon.js";
@@ -289,11 +290,36 @@ const instanceConfigSchema = z.object({
289
290
  });
290
291
  const instanceConfigMapSchema = z.record(z.string(), instanceConfigSchema);
291
292
  const defaultModelSelectionSchema = z.object({
292
- instanceId: z.string().trim().min(1),
293
- model: z.string().trim().min(1),
293
+ instanceId: z.string().trim().min(1).max(200),
294
+ model: z.string().trim().min(1).max(500),
294
295
  effort: z.enum(EFFORT_LEVELS).optional(),
295
296
  variant: z.string().refine(isModelVariant, "invalid model variant").optional(),
296
297
  }).refine((selection) => selection.variant === undefined || selection.effort === undefined, "choose either a model variant or an effort level");
298
+ const threadsConfigSchema = z.object({
299
+ maxConcurrentPerBot: z.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
300
+ /** Cap each per-thread events/ and native/ NDJSON log at this many
301
+ * bytes; absent (the default) keeps today's unbounded growth (#1280). */
302
+ eventLogMaxBytes: z.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
303
+ /** Days a closed or archived thread's event logs survive (#1280).
304
+ * Absent keeps them forever. */
305
+ eventLogRetentionDays: z.number().int().min(1).max(3650).optional(),
306
+ }).strict();
307
+ /** Workspace-wide defaults every new bot starts with (Store.createBot). */
308
+ const newBotsConfigSchema = z.object({
309
+ /** Effort for a new bot whose model selection names none. */
310
+ effort: z.enum(EFFORT_LEVELS).optional(),
311
+ }).strict();
312
+ /** PATCH newBots: null clears a default back to absent (no level is sent). */
313
+ const newBotsPatchSchema = z.object({
314
+ effort: newBotsConfigSchema.shape.effort.nullable(),
315
+ }).strict();
316
+ /** PATCH threads: every knob is independently patchable, and null clears an
317
+ * event-log knob back to its absent (off) default. */
318
+ const threadsPatchSchema = threadsConfigSchema.extend({
319
+ maxConcurrentPerBot: threadsConfigSchema.shape.maxConcurrentPerBot.optional(),
320
+ eventLogMaxBytes: threadsConfigSchema.shape.eventLogMaxBytes.nullable(),
321
+ eventLogRetentionDays: threadsConfigSchema.shape.eventLogRetentionDays.nullable(),
322
+ });
297
323
  const appConfigSchema = z.object({
298
324
  /** Verified by the dedicated domain endpoint, never a generic config patch. */
299
325
  customDomain: z.string().optional(),
@@ -301,12 +327,15 @@ const appConfigSchema = z.object({
301
327
  * addresses or `@domain` entries; admins get every scope, members chat only. */
302
328
  signIn: z.object({ admins: z.array(z.string().max(320)).max(500).optional(), members: z.array(z.string().max(320)).max(5000).optional() }).optional(),
303
329
  defaultModelSelection: defaultModelSelectionSchema.optional(),
330
+ newBotDefaults: newBotDefaultsSchema.optional(),
331
+ newBots: newBotsConfigSchema.optional(),
304
332
  /** CLI-only launch preferences. Never enable remote access implicitly. */
305
333
  cliStartup: z.object({
306
334
  access: z.enum(["local", "tunnel", "tailscale", "public-url"]),
307
335
  publicUrl: z.string().url().optional(),
308
336
  phone: z.enum(["ios", "android"]).optional(),
309
337
  }).optional(),
338
+ mistral: z.object({ key: optionalText }).optional(),
310
339
  xai: z.object({ key: optionalText, url: optionalText }).optional(),
311
340
  /** Anthropic API key for Claude Code billed per token, handed only to
312
341
  * Claude instances; `url` only for a proxy or a test double. Never a
@@ -384,8 +413,8 @@ const appConfigSchema = z.object({
384
413
  }).optional(),
385
414
  customModel: z.string().trim().max(200).refine((value) => !["\r", "\n", "\0"].some((character) => value.includes(character)), "Use a model ID without control characters").optional(),
386
415
  }).optional(),
387
- /** Non-secret profile details shown in the sidebar. */
388
- profile: z.object({ name: optionalText, email: optionalText }).optional(),
416
+ /** Non-secret profile details; aboutMe is shared with every bot. */
417
+ profile: z.object({ name: optionalText, email: optionalText, aboutMe: z.string().max(24_000).optional() }).optional(),
389
418
  /** UI language override (BCP-47, lowercase). Empty/absent = follow the
390
419
  * system language. Unknown tags degrade to English in the renderer. */
391
420
  language: optionalText,
@@ -395,15 +424,10 @@ const appConfigSchema = z.object({
395
424
  compactAt: z.number().positive().max(10_000_000).optional(),
396
425
  autoCompact: z.boolean().optional(),
397
426
  }).optional(),
398
- threads: z.object({
399
- maxConcurrentPerBot: z.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
400
- /** Cap each per-thread events/ and native/ NDJSON log at this many
401
- * bytes; absent (the default) keeps today's unbounded growth (#1280). */
402
- eventLogMaxBytes: z.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
403
- /** Days a closed or archived thread's event logs survive (#1280).
404
- * Absent keeps them forever. */
405
- eventLogRetentionDays: z.number().int().min(1).max(3650).optional(),
406
- }).strict().optional(),
427
+ threads: threadsConfigSchema.optional(),
428
+ /** The authorization decision log (server/decision-log.ts): days of month
429
+ * files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
430
+ decisions: z.object({ retentionDays: z.number().int().min(1).max(3650).optional() }).strict().optional(),
407
431
  localVm: localVmConfigSchema.optional(),
408
432
  features: featureConfigSchema.optional(),
409
433
  onboarding: onboardingConfigSchema.optional(),
@@ -420,7 +444,8 @@ const appConfigSchema = z.object({
420
444
  const storedAppConfigSchema = appConfigSchema.extend({
421
445
  browserProfiles: storedBrowserProfilesSchema.optional(),
422
446
  });
423
- const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true });
447
+ const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true })
448
+ .extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
424
449
  const jsonObjectSchema = z.record(z.string(), z.json());
425
450
  /** Resolve a canonical profile record to its exact durable Electron
426
451
  * partition identity. Callers must never substitute the display/API id. */
@@ -520,6 +545,8 @@ export function roomHandoffLimits(cfg) {
520
545
  hardCapMs: (cfg.rooms?.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES) * 60_000,
521
546
  };
522
547
  }
548
+ /** Accepts a full config or a parsed patch: the concurrency limit may be
549
+ * read from either, and a patch may legitimately omit it. */
523
550
  export function maxConcurrentBotThreads(cfg) {
524
551
  return cfg.threads?.maxConcurrentPerBot ?? DEFAULT_MAX_CONCURRENT_BOT_THREADS;
525
552
  }
@@ -671,6 +698,9 @@ export function loadConfig() {
671
698
  // Anything that saves a credential mid-session must keep process.env in
672
699
  // step (syncCredentialEnv below), or the value injected at boot would
673
700
  // shadow the save until the next launch.
701
+ cfg.mistral = { ...cfg.mistral };
702
+ if (process.env.MISTRAL_API_KEY !== undefined)
703
+ cfg.mistral.key = process.env.MISTRAL_API_KEY;
674
704
  cfg.xai = { ...cfg.xai };
675
705
  if (process.env.XAI_API_KEY !== undefined)
676
706
  cfg.xai.key = process.env.XAI_API_KEY;
@@ -732,6 +762,7 @@ export function loadConfig() {
732
762
  export function syncCredentialEnv(patch) {
733
763
  const secrets = [
734
764
  [patch.xai?.key, "XAI_API_KEY"],
765
+ [patch.mistral?.key, "MISTRAL_API_KEY"],
735
766
  [patch.anthropic?.key, "OMB_ANTHROPIC_API_KEY"],
736
767
  [patch.openaiCompat?.key, "OPENAI_COMPAT_API_KEY"],
737
768
  [patch.composio?.apiKey, "COMPOSIO_API_KEY"],
@@ -774,8 +805,11 @@ export function syncCredentialEnv(patch) {
774
805
  * child these are someone else's keys riding along in `...process.env`. */
775
806
  export const WORKSPACE_CREDENTIAL_ENV = [
776
807
  "XAI_API_KEY",
808
+ "MISTRAL_API_KEY",
777
809
  "OMB_ANTHROPIC_API_KEY",
778
810
  "OMB_ANTHROPIC_API_URL",
811
+ "OMB_HOSTED_MODEL_TOKEN",
812
+ "OMB_HOSTED_MODELS",
779
813
  "OPENAI_COMPAT_API_KEY",
780
814
  "OPENAI_COMPAT_URL",
781
815
  "BOX_TOKEN",
@@ -792,10 +826,32 @@ export const WORKSPACE_CREDENTIAL_ENV = [
792
826
  "OMB_BROWSER_CONNECTION",
793
827
  "OMB_USER_DATA",
794
828
  ];
795
- /** Drop every workspace credential from a child-process env (in place). */
829
+ /** Secrets of whoever operates this server, not of the workspace: the license
830
+ * key, a fleet container's installation credential, and everything a hosting
831
+ * control plane injects under `OMB_CLOUD_` (the readiness token, the bootstrap
832
+ * document and its gateway token). Only this process reads them. The prefix
833
+ * ends in an underscore on purpose: `OMB_CLOUDFLARED_PATH` is not one of them.
834
+ * What an engine is meant to receive arrives under another name through its
835
+ * instance environment (the hosted model token as ANTHROPIC_API_KEY or
836
+ * OPENMAUSBOT_COMPANY_API_KEY), so nothing here is ever an engine's input. */
837
+ export const CONTROL_PLANE_ENV = ["OMB_LICENSE_KEY", "OMB_INSTALLATION_CREDENTIAL"];
838
+ export const CONTROL_PLANE_ENV_PREFIX = "OMB_CLOUD_";
839
+ /** Drop every control-plane secret from a child-process env (in place). No
840
+ * driver allowlist re-admits these. Names compare case-insensitively because
841
+ * Windows environments do. */
842
+ export function stripControlPlaneEnv(env) {
843
+ for (const key of Object.keys(env)) {
844
+ const name = key.toUpperCase();
845
+ if (name.startsWith(CONTROL_PLANE_ENV_PREFIX) || CONTROL_PLANE_ENV.includes(name))
846
+ delete env[key];
847
+ }
848
+ }
849
+ /** Drop every workspace credential, and every control-plane secret, from a
850
+ * child-process env (in place). */
796
851
  export function stripWorkspaceCredentialEnv(env) {
797
852
  for (const key of WORKSPACE_CREDENTIAL_ENV)
798
853
  delete env[key];
854
+ stripControlPlaneEnv(env);
799
855
  }
800
856
  /** Env names a provider CLI might read as its own billing identity. A spawned
801
857
  * engine keeps only what its driver explicitly allows: a foreign key riding
@@ -812,9 +868,18 @@ export const PROVIDER_CREDENTIAL_ENV = [
812
868
  "OPENAI_API_KEY",
813
869
  "OPENCODE_API_KEY",
814
870
  "XAI_API_KEY",
871
+ "MISTRAL_API_KEY",
815
872
  "CURSOR_API_KEY",
816
873
  "CURSOR_AUTH_TOKEN",
817
874
  ];
875
+ const configSaveListeners = new Set();
876
+ /** Told, synchronously, what each saveConfig wrote: the file before and
877
+ * after. The admin activity log (server/admin-activity.ts) records the
878
+ * change for whoever's request made it. A listener must not throw. */
879
+ export function onConfigSaved(listener) {
880
+ configSaveListeners.add(listener);
881
+ return () => { configSaveListeners.delete(listener); };
882
+ }
818
883
  /** Merge a partial config into ~/.openmausbot/config.json (secrets never
819
884
  * echoed back — callers report configured-or-not booleans only). */
820
885
  export function saveConfig(patch, options = {}) {
@@ -828,20 +893,33 @@ export function saveConfig(patch, options = {}) {
828
893
  catch {
829
894
  /* first write */
830
895
  }
831
- const checkedPatch = appConfigSchema.partial().parse(patch);
896
+ const checkedPatch = appConfigSchema.partial().extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() }).parse(patch);
897
+ const before = configSaveListeners.size ? structuredClone(disk) : disk;
832
898
  // A write is the durable migration point. Preserve every other raw key in
833
899
  // config.json, but never write #567's mixed-case or duplicate profile ids
834
900
  // back after we have successfully recognized the legacy list.
835
901
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
836
902
  if (storedProfiles.success)
837
903
  disk.browserProfiles = storedProfiles.data;
838
- for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "localVm", "features", "budgets", "billing", "onboarding", "browserEngine"]) {
904
+ for (const key of ["xai", "anthropic", "mistral", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "localVm", "features", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
839
905
  const section = checkedPatch[key];
840
906
  if (!section)
841
907
  continue;
842
908
  const current = jsonObjectSchema.safeParse(disk[key]);
843
909
  const merged = current.success ? { ...current.data } : {};
910
+ // parseStoredConfig requires threads.maxConcurrentPerBot, so creating
911
+ // the section with only an event-log knob must still persist a valid
912
+ // concurrency default.
913
+ if (key === "threads" && !current.success)
914
+ merged.maxConcurrentPerBot = DEFAULT_MAX_CONCURRENT_BOT_THREADS;
844
915
  Object.assign(merged, section);
916
+ // null is the patch's explicit "remove this key" marker (the threads
917
+ // event-log knobs and newBots.effort use it); a key the patch omits
918
+ // keeps its value.
919
+ for (const [sectionKey, sectionValue] of Object.entries(section)) {
920
+ if (sectionValue === null)
921
+ delete merged[sectionKey];
922
+ }
845
923
  disk[key] = merged;
846
924
  }
847
925
  if (checkedPatch.vps !== undefined)
@@ -857,6 +935,24 @@ export function saveConfig(patch, options = {}) {
857
935
  // an effort level omitted from the new selection.
858
936
  if (checkedPatch.defaultModelSelection !== undefined) {
859
937
  disk.defaultModelSelection = checkedPatch.defaultModelSelection;
938
+ if (disk.newBotDefaults) {
939
+ const previousDefaults = newBotDefaultsSchema.parse(disk.newBotDefaults);
940
+ disk.newBotDefaults = newBotDefaultsSchema.parse({
941
+ ...previousDefaults,
942
+ profile: { ...previousDefaults.profile, modelSelection: checkedPatch.defaultModelSelection },
943
+ });
944
+ }
945
+ }
946
+ // Replace the complete template so clearing a field, file or routine
947
+ // cannot revive an old value through the general section merge above.
948
+ if (checkedPatch.newBotDefaults !== undefined) {
949
+ disk.newBotDefaults = checkedPatch.newBotDefaults;
950
+ if (checkedPatch.newBotDefaults.profile.modelSelection) {
951
+ disk.defaultModelSelection = checkedPatch.newBotDefaults.profile.modelSelection;
952
+ }
953
+ else {
954
+ delete disk.defaultModelSelection;
955
+ }
860
956
  }
861
957
  if (checkedPatch.cliStartup !== undefined)
862
958
  disk.cliStartup = checkedPatch.cliStartup;
@@ -880,6 +976,14 @@ export function saveConfig(patch, options = {}) {
880
976
  if (routingConflict)
881
977
  throw Object.assign(new Error(routingConflict), { status: 409 });
882
978
  disk.browserProfiles = nextProfiles;
979
+ if (disk.newBotDefaults) {
980
+ const defaults = newBotDefaultsSchema.parse(disk.newBotDefaults);
981
+ const profileId = defaults.profile.browserProfile;
982
+ if (profileId && profileId !== "guest" && !nextProfiles.some(profile => profile.id === profileId)) {
983
+ delete defaults.profile.browserProfile;
984
+ disk.newBotDefaults = defaults;
985
+ }
986
+ }
883
987
  }
884
988
  if (checkedPatch.instances) {
885
989
  const currentInstances = jsonObjectSchema.safeParse(disk.instances);
@@ -920,6 +1024,14 @@ export function saveConfig(patch, options = {}) {
920
1024
  }
921
1025
  mkdirSync(DATA_DIR, { recursive: true });
922
1026
  writeFileAtomic(p, JSON.stringify(disk, null, 2), { mode: 0o600 });
1027
+ for (const listener of configSaveListeners) {
1028
+ try {
1029
+ listener(before, disk);
1030
+ }
1031
+ catch (error) {
1032
+ console.warn(`config: a save listener failed: ${error instanceof Error ? error.message : String(error)}`);
1033
+ }
1034
+ }
923
1035
  }
924
1036
  /** Set one instance's `config.cli` ("" clears the override back to the
925
1037
  * driver default). Creating the instance entry is fine — a config-less
@@ -980,6 +1092,8 @@ export function persistableInstanceConfigs(cfg) {
980
1092
  * environment of an unrelated child process. */
981
1093
  function injectedEnvironment(cfg, driver) {
982
1094
  const environment = new Map();
1095
+ if (driver === "mistral" && cfg.mistral?.key)
1096
+ environment.set("MISTRAL_API_KEY", cfg.mistral.key);
983
1097
  if (driver === "grok" && cfg.xai?.key)
984
1098
  environment.set("XAI_API_KEY", cfg.xai.key);
985
1099
  // The workspace Anthropic key reaches Claude Code as the variable it
@@ -1030,6 +1144,7 @@ export function instanceConfigs(cfg) {
1030
1144
  opencodeGo: { driver: "opencodeGo" },
1031
1145
  computer: { driver: "boxAgent" },
1032
1146
  openaiCompat: { driver: "openai-compat" },
1147
+ mistral: { driver: "mistral" },
1033
1148
  qwen: { driver: "qwenAgent" },
1034
1149
  hermes: { driver: "hermesAgent" },
1035
1150
  pi: { driver: "piAgent" },
@@ -1045,6 +1160,7 @@ export function instanceConfigs(cfg) {
1045
1160
  const PRODUCT_FLEET_ADDITIONS = {
1046
1161
  cursor: { driver: "cursorAgent" },
1047
1162
  openaiCompat: { driver: "openai-compat" },
1163
+ mistral: { driver: "mistral" },
1048
1164
  ...CUSTOM_ONLY,
1049
1165
  };
1050
1166
  const configured = cfg.instances && Object.keys(cfg.instances).length ? cfg.instances : null;
@@ -0,0 +1,141 @@
1
+ // The harness-side verdict for connected-app tool calls (issue #1736, part
2
+ // of the per-bot Composio tool grants umbrella #1734).
3
+ //
4
+ // Every Composio tool call crosses POST /api/internal/connectors/mcp: the
5
+ // provider CLIs talk to the stdio bridge, and the bridge forwards each
6
+ // JSON-RPC frame there. So the grants verdict lives on the harness side,
7
+ // where the loopback token already names the bot and the decision log is
8
+ // native — the bridge never sees a key it could have to enforce with.
9
+ //
10
+ // Three shapes reach this module:
11
+ //
12
+ // * a direct per-toolkit call — params.name is the tool itself, e.g.
13
+ // GMAIL_SEND_EMAIL;
14
+ // * the executor meta-tool — COMPOSIO_MULTI_EXECUTE_TOOL, whose
15
+ // arguments name one or many target tools;
16
+ // * everything else — connection cards, discovery, notifications —
17
+ // which passes through untouched (advertisement filtering is slice 3).
18
+ //
19
+ // Unrecognized argument shapes are denied by default: a call whose target
20
+ // tools cannot be read is a call whose tools cannot be checked, and the
21
+ // grants promise — "this bot may call exactly these tools" — must hold
22
+ // even against a model that garbles the protocol.
23
+ import { CONNECTOR_TOOL_NAME_PATTERN } from "../shared/wire.js";
24
+ /** The executor meta-tool: the only meta-tool whose arguments name target
25
+ * tools. Discovery and connection meta-tools keep their existing flows. */
26
+ export const COMPOSIO_MULTI_EXECUTE_TOOL = "COMPOSIO_MULTI_EXECUTE_TOOL";
27
+ /** The service a Composio tool name belongs to: the upper-snake prefix
28
+ * before the first underscore, lowercased (GMAIL_SEND_EMAIL to gmail). A
29
+ * name with no underscore names no service, so no grant can cover it. */
30
+ export function serviceSlugFor(tool) {
31
+ const underscore = tool.indexOf("_");
32
+ if (underscore <= 0)
33
+ return null;
34
+ return tool.slice(0, underscore).toLowerCase();
35
+ }
36
+ /** Classify one relayed JSON-RPC frame. Anything that is not a tools/call,
37
+ * or is a discovery/connection/platform meta-tool, passes through. */
38
+ export function connectorCallFromFrame(payload) {
39
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
40
+ return { kind: "passthrough" };
41
+ const frame = payload;
42
+ if (frame.method !== "tools/call")
43
+ return { kind: "passthrough" };
44
+ const params = frame.params;
45
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
46
+ return { kind: "unrecognized", invoked: "tools/call", reason: "the call carried no params object" };
47
+ }
48
+ const name = params.name;
49
+ if (typeof name !== "string" || !name) {
50
+ return { kind: "unrecognized", invoked: "tools/call", reason: "the call named no tool" };
51
+ }
52
+ if (name === COMPOSIO_MULTI_EXECUTE_TOOL) {
53
+ return multiExecuteCall(name, params.arguments);
54
+ }
55
+ // Platform meta-tools (search, schemas, remote workbench) are not
56
+ // connected-app tools; grants are keyed by service slug, and slice 3
57
+ // owns what the model is even shown.
58
+ if (name.startsWith("COMPOSIO_"))
59
+ return { kind: "passthrough" };
60
+ // Per-service connection cards keep their card flow in any spelling.
61
+ if (name.endsWith("_MANAGE_CONNECTIONS") || name.endsWith("_WAIT_FOR_CONNECTIONS")) {
62
+ return { kind: "passthrough" };
63
+ }
64
+ if (!CONNECTOR_TOOL_NAME_PATTERN.test(name)) {
65
+ return { kind: "unrecognized", invoked: name, reason: "the tool name is not a Composio tool name" };
66
+ }
67
+ return { kind: "tools", invoked: name, names: [name] };
68
+ }
69
+ /** Read the target tools out of COMPOSIO_MULTI_EXECUTE_TOOL arguments:
70
+ * the upstream schema is a 1-50 item tools array of { tool_slug,
71
+ * arguments }; a legacy single { tool_slug } object is accepted too.
72
+ * Anything else cannot be checked, so it is denied. */
73
+ function multiExecuteCall(invoked, args) {
74
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
75
+ return { kind: "unrecognized", invoked, reason: "the arguments were not an object" };
76
+ }
77
+ const tools = args.tools;
78
+ const singleSlug = args.tool_slug;
79
+ if (Array.isArray(tools)) {
80
+ if (tools.length === 0) {
81
+ return { kind: "unrecognized", invoked, reason: "the tools list was empty" };
82
+ }
83
+ const names = [];
84
+ for (const item of tools) {
85
+ const slug = item && typeof item === "object" && !Array.isArray(item)
86
+ ? item.tool_slug
87
+ : undefined;
88
+ if (typeof slug !== "string" || !slug) {
89
+ return { kind: "unrecognized", invoked, reason: "an entry in the tools list named no tool_slug" };
90
+ }
91
+ names.push(slug);
92
+ }
93
+ return { kind: "tools", invoked, names };
94
+ }
95
+ if (typeof singleSlug === "string" && singleSlug) {
96
+ return { kind: "tools", invoked, names: [singleSlug] };
97
+ }
98
+ return { kind: "unrecognized", invoked, reason: "the arguments named no tools to execute" };
99
+ }
100
+ /** Judge distinct target names against a bot's grants. grants undefined
101
+ * is the legacy all-tools bot and passes everything; an explicit record —
102
+ * including the empty one — allows only what it names. */
103
+ export function evaluateConnectorTools(names, grants) {
104
+ if (grants === undefined) {
105
+ return { allowed: true, legacy: true, denials: [], rule: "composio" };
106
+ }
107
+ const denials = [];
108
+ let rule = "";
109
+ for (const tool of new Set(names)) {
110
+ const service = serviceSlugFor(tool);
111
+ const grant = service === null ? undefined : grants[service];
112
+ if (grant && (grant.tools === "*" || grant.tools.includes(tool))) {
113
+ if (!rule)
114
+ rule = "connectorTools." + service;
115
+ continue;
116
+ }
117
+ denials.push({ tool, service, onGrantedService: Boolean(grant) });
118
+ }
119
+ return { allowed: denials.length === 0, legacy: false, denials, rule };
120
+ }
121
+ /** Safe to hand straight to the model: names the refused tool and says who
122
+ * can change it. It never lists what the bot could have called instead —
123
+ * a refusal that enumerates adjacent grants teaches the model to probe. */
124
+ export function connectorRefusalText(denials) {
125
+ const named = denials.map((denial) => '"' + denial.tool + '"').join(", ");
126
+ const one = denials.length === 1;
127
+ return [
128
+ named + " " + (one ? "is" : "are") + " not granted to this bot.",
129
+ "This call was not performed.",
130
+ "Ask the person to grant " + (one ? "it" : "these tools") + " in OpenMausBot if they want " + (one ? "it" : "them") + " run.",
131
+ ].join(" ");
132
+ }
133
+ /** The unrecognized-shape refusal: names the meta-tool and what could not
134
+ * be read, and still points at the person rather than at any tool list. */
135
+ export function connectorUnrecognizedText(invoked, reason) {
136
+ return [
137
+ "A " + invoked + " call arrived in a shape OpenMausBot could not read (" + reason + "), so it was not performed.",
138
+ "Send a well-formed call that names the tools to execute.",
139
+ "Ask the person to grant any tool this bot needs.",
140
+ ].join(" ");
141
+ }
@@ -310,7 +310,7 @@ function statusProblem(status) {
310
310
  if (status.security === "unsafe")
311
311
  return "The existing Local VM is missing safety limits; recreate it";
312
312
  if (status.persistence === "unsafe")
313
- return "The existing Local VM is missing its durable workspace; recreate it";
313
+ return "The existing Local VM is missing its durable folder; recreate it";
314
314
  if (status.container === "stopped")
315
315
  return "This desktop image cannot safely resume; recreate the Local VM";
316
316
  if (status.desktop_error)