openmausbot 0.1.86 → 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 (166) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-BTiuL79F.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-C88Blc2v.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-BZsJIZrb.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CVeMe9Ah.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-Cedk3Bhx.js} +1 -1
  7. package/dist/assets/channel-taCNRlUU.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-D-VRqzTB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-BChov01Y.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-C-TfwvHy.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-Db7CGj5Y.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-CNvIfqKT.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-C_bEp5tq.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Coi4BOls.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-CzylEuL3.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-m7p7ztaD.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BMy6o9x2.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-BDQ__0_J.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Djv4ghUb.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-BULrlGYH.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-D-jrT_hU.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-p2UhQmfL.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-DHvGWZ7q.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-ChhKDot-.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-B-T7A43R.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-CJm8ENNY.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-510tlza8.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-PkvRPHel.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-BVBVPoX3.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BgYMVm7L.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-BGqsnc_3.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-D_-UeReH.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-D0uooONx.js} +1 -1
  34. package/dist/assets/index-BwdMAQ6p.css +1 -0
  35. package/dist/assets/index-ByJennr7.js +581 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-zRsJERGJ.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-B9e1z77f.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-BB4vNwvD.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DpkosOTB.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-DbDP4kUu.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-DBJGPmjT.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-DWPRi-ue.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-nszBbJmx.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-CWykJWu_.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-g3Da-Vnv.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-Wzrlpzgh.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-moHTFMC0.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-CwfXdFH8.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-3ZROYvnq.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-E1TBebP8.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-DHKEU5pu.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BguEj09D.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-C7IDYnhd.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-DCW-F5PW.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-xBMFtWYV.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-B5nIkuH8.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-DYFAhdbr.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-DeXwX6Cz.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/container-mcp.js +1029 -164
  63. package/dist-server/drivers/agents-proxy.js +64 -10
  64. package/dist-server/drivers/phone-proxy.js +33 -2
  65. package/dist-server/index.js +14577 -9001
  66. package/dist-server/local-computer-proxy.js +3 -3
  67. package/dist-server/local-computer.js +53 -17
  68. package/dist-server/openmausbot.js +3981 -1165
  69. package/dist-server/pair-cli.js +3981 -1165
  70. package/dist-server/permission-proxy.js +29 -9
  71. package/dist-server/server/attachments.js +55 -0
  72. package/dist-server/server/auto-approve.js +4 -3
  73. package/dist-server/server/auto-vm-claims.js +1 -0
  74. package/dist-server/server/bot-package.js +18 -292
  75. package/dist-server/server/bot-profile.js +5 -17
  76. package/dist-server/server/box-create-idempotency.js +3 -3
  77. package/dist-server/server/box.js +57 -55
  78. package/dist-server/server/browser-proxy.js +3 -2
  79. package/dist-server/server/browser-runtime.js +37 -1
  80. package/dist-server/server/cli.js +2 -2
  81. package/dist-server/server/command-allowlist.js +154 -0
  82. package/dist-server/server/computer-wait.js +22 -9
  83. package/dist-server/server/config.js +59 -9
  84. package/dist-server/server/connector-verdict.js +141 -0
  85. package/dist-server/server/container-computer.js +1 -1
  86. package/dist-server/server/default-model-selection.js +9 -0
  87. package/dist-server/server/delegations.js +85 -34
  88. package/dist-server/server/drivers/acp/core.js +57 -15
  89. package/dist-server/server/drivers/agents-call.js +42 -3
  90. package/dist-server/server/drivers/agents-catalog.js +34 -4
  91. package/dist-server/server/drivers/boxagent.js +174 -36
  92. package/dist-server/server/drivers/builtIn.js +2 -0
  93. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  94. package/dist-server/server/drivers/chat-images.js +54 -0
  95. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  96. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  97. package/dist-server/server/drivers/claude.js +7 -2
  98. package/dist-server/server/drivers/codex-instructions.js +48 -6
  99. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  100. package/dist-server/server/drivers/codex.js +95 -30
  101. package/dist-server/server/drivers/mistral.js +115 -0
  102. package/dist-server/server/drivers/openai-chat.js +155 -32
  103. package/dist-server/server/drivers/openai-compat.js +1 -0
  104. package/dist-server/server/drivers/permission-command.js +35 -0
  105. package/dist-server/server/drivers/phone-proxy.js +47 -2
  106. package/dist-server/server/drivers/pi.js +75 -4
  107. package/dist-server/server/drivers/prompt-split.js +87 -0
  108. package/dist-server/server/fleet.js +1 -1
  109. package/dist-server/server/group-thread-usage.js +103 -0
  110. package/dist-server/server/harness/registry.js +1 -0
  111. package/dist-server/server/hosted-models.js +1 -1
  112. package/dist-server/server/index.js +1385 -322
  113. package/dist-server/server/local-computer.js +72 -23
  114. package/dist-server/server/managed-desktop.js +1 -1
  115. package/dist-server/server/mcp-bridge.js +6 -6
  116. package/dist-server/server/new-bot-defaults.js +115 -0
  117. package/dist-server/server/org-library.js +1051 -0
  118. package/dist-server/server/package-export.js +627 -39
  119. package/dist-server/server/package-import.js +458 -0
  120. package/dist-server/server/package-parts.js +87 -0
  121. package/dist-server/server/peer-approval.js +16 -7
  122. package/dist-server/server/peer-roster.js +10 -0
  123. package/dist-server/server/presets.js +492 -0
  124. package/dist-server/server/provider-key-check.js +3 -2
  125. package/dist-server/server/published-teams.js +80 -0
  126. package/dist-server/server/room-handoffs.js +19 -3
  127. package/dist-server/server/routes/bot-presets.js +25 -0
  128. package/dist-server/server/routine-requests.js +6 -3
  129. package/dist-server/server/routines.js +41 -1
  130. package/dist-server/server/section-context.js +10 -0
  131. package/dist-server/server/skills.js +34 -61
  132. package/dist-server/server/steer-queue.js +3 -2
  133. package/dist-server/server/store.js +259 -15
  134. package/dist-server/server/surface.js +10 -4
  135. package/dist-server/server/system-prompt.js +12 -4
  136. package/dist-server/server/team-backup.js +6 -2
  137. package/dist-server/server/team-computers.js +10 -0
  138. package/dist-server/server/team-library.js +6 -2
  139. package/dist-server/server/team-manifest.js +4 -3
  140. package/dist-server/server/team-setup-requests.js +16 -5
  141. package/dist-server/server/thread-events.js +17 -1
  142. package/dist-server/server/turn-context.js +2 -0
  143. package/dist-server/server/turn-watchdog.js +12 -3
  144. package/dist-server/server/usage-ledger.js +12 -1
  145. package/dist-server/server/vps-computer.js +139 -51
  146. package/dist-server/server/vps-container-mcp.js +7 -0
  147. package/dist-server/server/vps-ssh.js +36 -7
  148. package/dist-server/server/workspace-backup-policy.js +12 -0
  149. package/dist-server/server/workspace-backup.js +2 -2
  150. package/dist-server/server/workspace.js +1 -1
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/ask-question.js +188 -12
  153. package/dist-server/shared/bot-profile.js +15 -0
  154. package/dist-server/shared/command-allowlist.js +1 -0
  155. package/dist-server/shared/credential-request.js +1 -1
  156. package/dist-server/shared/package-format.js +967 -0
  157. package/dist-server/shared/skill-md.js +64 -0
  158. package/dist-server/shared/team-backup.js +7 -0
  159. package/dist-server/shared/wire.js +4 -0
  160. package/dist-server/vps-container-mcp.js +1129 -170
  161. package/enterprise/README.md +1 -1
  162. package/package.json +1 -1
  163. package/dist/assets/channel-BXFkgus9.js +0 -1
  164. package/dist/assets/index-BGgqlWPJ.css +0 -1
  165. package/dist/assets/index-BtHo8ZKl.js +0 -577
  166. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
@@ -7,8 +7,17 @@
7
7
  // that, name the holder as a bot running a thread, and keep the same words
8
8
  // on every surface (chat chip, room chip, the gate's refusal) so a person
9
9
  // who has read it once recognises it everywhere.
10
- /** Whole minutes, or seconds under one minute, for the give-up notice. */
11
- const minutes = (ms) => ms >= 60_000 ? `${Math.round(ms / 60_000)} minutes` : `${Math.round(ms / 1000)} seconds`;
10
+ /** Whole minutes, or seconds under one minute, singular at exactly one. */
11
+ const minutes = (ms) => {
12
+ const value = ms >= 60_000 ? Math.round(ms / 60_000) : Math.round(ms / 1000);
13
+ const unit = ms >= 60_000 ? "minute" : "second";
14
+ return `${value} ${unit}${value === 1 ? "" : "s"}`;
15
+ };
16
+ /** How long a wait lasted, for the resolution line: waits under a second
17
+ * are honest about being over in a blink instead of claiming "0 seconds". */
18
+ export function computerWaitDuration(ms) {
19
+ return ms < 1_000 ? "under a second" : minutes(ms);
20
+ }
12
21
  const holderPhrase = (holder) => holder.task ? `${holder.name} is running ${holder.task}` : `${holder.name} is using it`;
13
22
  /** The chip a turn shows while it is queued behind another turn's desktop. */
14
23
  export function computerWaitingText(holder) {
@@ -16,14 +25,18 @@ export function computerWaitingText(holder) {
16
25
  return "Waiting for its turn on this computer. Starts automatically when it is free.";
17
26
  return `Waiting for its turn on this computer — ${holderPhrase(holder)}. Starts automatically when that finishes.`;
18
27
  }
19
- /** The same chip once the wait landed and this turn holds the desktop. */
20
- export function computerFreeText() {
21
- return "Computer free — continuing";
28
+ /** The resolution appended once the wait landed and this turn holds the
29
+ * desktop: how long it waited, and who held it. The waiting chip stays as
30
+ * written — this line is the history beside it, not its replacement. */
31
+ export function computerFreeAfterText(holder, waitedMs) {
32
+ const who = holder ? ` (${holder.task ? `${holder.name} · ${holder.task}` : holder.name} held it)` : "";
33
+ return `Computer free — continuing after waiting ${computerWaitDuration(waitedMs)}${who}`;
22
34
  }
23
- /** The same chip when the wait ended without the desktop: the turn was
24
- * stopped, or the claim was abandoned. */
25
- export function computerWaitEndedText() {
26
- return "Stopped waiting for the computer";
35
+ /** The resolution appended when the wait ended because the turn was stopped
36
+ * or its claim was cancelled. */
37
+ export function computerStoppedWaitingText(holder, waitedMs) {
38
+ const who = holder ? ` — ${holderPhrase(holder)}` : "";
39
+ return `Stopped waiting for the computer after ${computerWaitDuration(waitedMs)}${who}.`;
27
40
  }
28
41
  /** The error after the wait ceiling: still names who holds it, and says
29
42
  * what a person can do — stop that turn, or move this one. */
@@ -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,8 +290,8 @@ 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");
@@ -303,6 +304,15 @@ const threadsConfigSchema = z.object({
303
304
  * Absent keeps them forever. */
304
305
  eventLogRetentionDays: z.number().int().min(1).max(3650).optional(),
305
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();
306
316
  /** PATCH threads: every knob is independently patchable, and null clears an
307
317
  * event-log knob back to its absent (off) default. */
308
318
  const threadsPatchSchema = threadsConfigSchema.extend({
@@ -317,12 +327,15 @@ const appConfigSchema = z.object({
317
327
  * addresses or `@domain` entries; admins get every scope, members chat only. */
318
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(),
319
329
  defaultModelSelection: defaultModelSelectionSchema.optional(),
330
+ newBotDefaults: newBotDefaultsSchema.optional(),
331
+ newBots: newBotsConfigSchema.optional(),
320
332
  /** CLI-only launch preferences. Never enable remote access implicitly. */
321
333
  cliStartup: z.object({
322
334
  access: z.enum(["local", "tunnel", "tailscale", "public-url"]),
323
335
  publicUrl: z.string().url().optional(),
324
336
  phone: z.enum(["ios", "android"]).optional(),
325
337
  }).optional(),
338
+ mistral: z.object({ key: optionalText }).optional(),
326
339
  xai: z.object({ key: optionalText, url: optionalText }).optional(),
327
340
  /** Anthropic API key for Claude Code billed per token, handed only to
328
341
  * Claude instances; `url` only for a proxy or a test double. Never a
@@ -400,8 +413,8 @@ const appConfigSchema = z.object({
400
413
  }).optional(),
401
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(),
402
415
  }).optional(),
403
- /** Non-secret profile details shown in the sidebar. */
404
- 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(),
405
418
  /** UI language override (BCP-47, lowercase). Empty/absent = follow the
406
419
  * system language. Unknown tags degrade to English in the renderer. */
407
420
  language: optionalText,
@@ -432,7 +445,7 @@ const storedAppConfigSchema = appConfigSchema.extend({
432
445
  browserProfiles: storedBrowserProfilesSchema.optional(),
433
446
  });
434
447
  const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true })
435
- .extend({ threads: threadsPatchSchema.optional() });
448
+ .extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() });
436
449
  const jsonObjectSchema = z.record(z.string(), z.json());
437
450
  /** Resolve a canonical profile record to its exact durable Electron
438
451
  * partition identity. Callers must never substitute the display/API id. */
@@ -685,6 +698,9 @@ export function loadConfig() {
685
698
  // Anything that saves a credential mid-session must keep process.env in
686
699
  // step (syncCredentialEnv below), or the value injected at boot would
687
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;
688
704
  cfg.xai = { ...cfg.xai };
689
705
  if (process.env.XAI_API_KEY !== undefined)
690
706
  cfg.xai.key = process.env.XAI_API_KEY;
@@ -746,6 +762,7 @@ export function loadConfig() {
746
762
  export function syncCredentialEnv(patch) {
747
763
  const secrets = [
748
764
  [patch.xai?.key, "XAI_API_KEY"],
765
+ [patch.mistral?.key, "MISTRAL_API_KEY"],
749
766
  [patch.anthropic?.key, "OMB_ANTHROPIC_API_KEY"],
750
767
  [patch.openaiCompat?.key, "OPENAI_COMPAT_API_KEY"],
751
768
  [patch.composio?.apiKey, "COMPOSIO_API_KEY"],
@@ -788,6 +805,7 @@ export function syncCredentialEnv(patch) {
788
805
  * child these are someone else's keys riding along in `...process.env`. */
789
806
  export const WORKSPACE_CREDENTIAL_ENV = [
790
807
  "XAI_API_KEY",
808
+ "MISTRAL_API_KEY",
791
809
  "OMB_ANTHROPIC_API_KEY",
792
810
  "OMB_ANTHROPIC_API_URL",
793
811
  "OMB_HOSTED_MODEL_TOKEN",
@@ -850,6 +868,7 @@ export const PROVIDER_CREDENTIAL_ENV = [
850
868
  "OPENAI_API_KEY",
851
869
  "OPENCODE_API_KEY",
852
870
  "XAI_API_KEY",
871
+ "MISTRAL_API_KEY",
853
872
  "CURSOR_API_KEY",
854
873
  "CURSOR_AUTH_TOKEN",
855
874
  ];
@@ -874,7 +893,7 @@ export function saveConfig(patch, options = {}) {
874
893
  catch {
875
894
  /* first write */
876
895
  }
877
- const checkedPatch = appConfigSchema.partial().extend({ threads: threadsPatchSchema.optional() }).parse(patch);
896
+ const checkedPatch = appConfigSchema.partial().extend({ threads: threadsPatchSchema.optional(), newBots: newBotsPatchSchema.optional() }).parse(patch);
878
897
  const before = configSaveListeners.size ? structuredClone(disk) : disk;
879
898
  // A write is the durable migration point. Preserve every other raw key in
880
899
  // config.json, but never write #567's mixed-case or duplicate profile ids
@@ -882,7 +901,7 @@ export function saveConfig(patch, options = {}) {
882
901
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
883
902
  if (storedProfiles.success)
884
903
  disk.browserProfiles = storedProfiles.data;
885
- for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "localVm", "features", "budgets", "billing", "decisions", "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"]) {
886
905
  const section = checkedPatch[key];
887
906
  if (!section)
888
907
  continue;
@@ -894,8 +913,9 @@ export function saveConfig(patch, options = {}) {
894
913
  if (key === "threads" && !current.success)
895
914
  merged.maxConcurrentPerBot = DEFAULT_MAX_CONCURRENT_BOT_THREADS;
896
915
  Object.assign(merged, section);
897
- // null is the patch's explicit "remove this key" marker (today only the
898
- // threads event-log knobs use it); a key the patch omits keeps its value.
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.
899
919
  for (const [sectionKey, sectionValue] of Object.entries(section)) {
900
920
  if (sectionValue === null)
901
921
  delete merged[sectionKey];
@@ -915,6 +935,24 @@ export function saveConfig(patch, options = {}) {
915
935
  // an effort level omitted from the new selection.
916
936
  if (checkedPatch.defaultModelSelection !== undefined) {
917
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
+ }
918
956
  }
919
957
  if (checkedPatch.cliStartup !== undefined)
920
958
  disk.cliStartup = checkedPatch.cliStartup;
@@ -938,6 +976,14 @@ export function saveConfig(patch, options = {}) {
938
976
  if (routingConflict)
939
977
  throw Object.assign(new Error(routingConflict), { status: 409 });
940
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
+ }
941
987
  }
942
988
  if (checkedPatch.instances) {
943
989
  const currentInstances = jsonObjectSchema.safeParse(disk.instances);
@@ -1046,6 +1092,8 @@ export function persistableInstanceConfigs(cfg) {
1046
1092
  * environment of an unrelated child process. */
1047
1093
  function injectedEnvironment(cfg, driver) {
1048
1094
  const environment = new Map();
1095
+ if (driver === "mistral" && cfg.mistral?.key)
1096
+ environment.set("MISTRAL_API_KEY", cfg.mistral.key);
1049
1097
  if (driver === "grok" && cfg.xai?.key)
1050
1098
  environment.set("XAI_API_KEY", cfg.xai.key);
1051
1099
  // The workspace Anthropic key reaches Claude Code as the variable it
@@ -1096,6 +1144,7 @@ export function instanceConfigs(cfg) {
1096
1144
  opencodeGo: { driver: "opencodeGo" },
1097
1145
  computer: { driver: "boxAgent" },
1098
1146
  openaiCompat: { driver: "openai-compat" },
1147
+ mistral: { driver: "mistral" },
1099
1148
  qwen: { driver: "qwenAgent" },
1100
1149
  hermes: { driver: "hermesAgent" },
1101
1150
  pi: { driver: "piAgent" },
@@ -1111,6 +1160,7 @@ export function instanceConfigs(cfg) {
1111
1160
  const PRODUCT_FLEET_ADDITIONS = {
1112
1161
  cursor: { driver: "cursorAgent" },
1113
1162
  openaiCompat: { driver: "openai-compat" },
1163
+ mistral: { driver: "mistral" },
1114
1164
  ...CUSTOM_ONLY,
1115
1165
  };
1116
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)
@@ -37,3 +37,12 @@ export function selectDefaultModelSelection(instances, preferred, context = {})
37
37
  }
38
38
  return { instanceId: pick?.instanceId ?? "", model: pick?.models.default ?? "" };
39
39
  }
40
+ /** Complete a new bot's selection with the workspace's new-bot effort. An
41
+ * explicit effort or model variant is the caller's choice and wins; an engine
42
+ * that does not offer the level keeps sending none rather than failing turn 1. */
43
+ export function withNewBotEffort(selection, effort, offered) {
44
+ if (!effort || selection.effort !== undefined || selection.variant !== undefined || !offered?.includes(effort)) {
45
+ return selection;
46
+ }
47
+ return { ...selection, effort };
48
+ }