openmausbot 0.1.84 → 0.1.86

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 (230) hide show
  1. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  2. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  4. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  10. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  16. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  19. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  22. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  25. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  28. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  31. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  34. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  43. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  46. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  49. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  52. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  54. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  57. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  60. package/dist/assets/abnfDiagram-O67JEVCF-DpUZNnFD.js +1 -0
  61. package/dist/assets/arc-DW7mrI4p.js +1 -0
  62. package/dist/assets/architectureDiagram-NJMV4G6O-CyeZ5WLZ.js +36 -0
  63. package/dist/assets/blockDiagram-BEXU5L5S-CBVaNjO7.js +140 -0
  64. package/dist/assets/c4Diagram-YGBWAQC7-sf8d41uT.js +38 -0
  65. package/dist/assets/channel-BXFkgus9.js +1 -0
  66. package/dist/assets/chunk-2Q5K7J3B-BOGvy7Y_.js +1 -0
  67. package/dist/assets/chunk-3FUC2YCW-BiF9zyiz.js +2 -0
  68. package/dist/assets/chunk-5VM5RSS4-EFuZ1SBZ.js +15 -0
  69. package/dist/assets/chunk-GTNCS2PH-pJLNSAT7.js +88 -0
  70. package/dist/assets/chunk-GWA4HPMP-Cb-3_3IT.js +1 -0
  71. package/dist/assets/chunk-JWPE2WC7-DAf5aogn.js +1 -0
  72. package/dist/assets/chunk-NETBCI7D-DR3kiwZz.js +1 -0
  73. package/dist/assets/chunk-WEXAMYUT-BmU8PeyM.js +1 -0
  74. package/dist/assets/chunk-XXDRQBXY-Buh8g8OT.js +1 -0
  75. package/dist/assets/classDiagram-v2-NBCMYWYE-BurfmMWM.js +217 -0
  76. package/dist/assets/cose-bilkent-JH36ORCC-KkGZUEkU.js +1 -0
  77. package/dist/assets/cynefin-OW5HDTMX-C3PpV96d.js +166 -0
  78. package/dist/assets/cynefinDiagram-VND7K2PF-Dt8JwJaI.js +62 -0
  79. package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
  80. package/dist/assets/dagre-6A5THRUB-DkjN8266.js +4 -0
  81. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  82. package/dist/assets/diagram-22UHCM2B-DUAJS0JV.js +200 -0
  83. package/dist/assets/diagram-3UASUU5V-Dw4KhF1a.js +24 -0
  84. package/dist/assets/diagram-ATOU4E4O-Dd1gmQqf.js +3 -0
  85. package/dist/assets/diagram-CDSNMT55-CjwGs0Yw.js +30 -0
  86. package/dist/assets/diagram-MLGK6HIB-BL-69Zrq.js +24 -0
  87. package/dist/assets/diagram-MPIPVDR6-5Zi2N7wo.js +41 -0
  88. package/dist/assets/ebnfDiagram-ZINNZB2B-D9NekWUX.js +1 -0
  89. package/dist/assets/elk-276RUBZZ-CkaSQjxw.js +27 -0
  90. package/dist/assets/erDiagram-OPXOYQCR-CdAVBV8-.js +99 -0
  91. package/dist/assets/flowDiagram-KWPJA3E3-Bm_lWWH7.js +213 -0
  92. package/dist/assets/ganttDiagram-FUAMR5RP-FZ8ejYWJ.js +292 -0
  93. package/dist/assets/gitGraphDiagram-X574FWY7-Dlv9rizZ.js +106 -0
  94. package/dist/assets/graph-D8UmR1vK.js +1 -0
  95. package/dist/assets/index-BGgqlWPJ.css +1 -0
  96. package/dist/assets/index-BtHo8ZKl.js +577 -0
  97. package/dist/assets/{index-DNa2umw-.js → index-h9FFNwTu.js} +1 -1
  98. package/dist/assets/infoDiagram-VRGFBTTK-BdQ_gW5B.js +2 -0
  99. package/dist/assets/init-Gi6I4Gst.js +1 -0
  100. package/dist/assets/ishikawaDiagram-OU5B5YK6-EwssEp2X.js +70 -0
  101. package/dist/assets/journeyDiagram-ZHPQQLJL-BX1G1vOd.js +139 -0
  102. package/dist/assets/kanban-definition-PNTS6WVX-BB4kAsyC.js +89 -0
  103. package/dist/assets/linear-3su2TG93.js +1 -0
  104. package/dist/assets/mermaid.core-CDD1E0s9.js +311 -0
  105. package/dist/assets/mindmap-definition-NLK3R4M7-Ba50MsNK.js +96 -0
  106. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  107. package/dist/assets/pegDiagram-GJSIUBJH-DZLkkZlM.js +1 -0
  108. package/dist/assets/pieDiagram-5QR66LMP-DaTz77Uq.js +39 -0
  109. package/dist/assets/quadrantDiagram-O4NWA36T-CpaiidpR.js +7 -0
  110. package/dist/assets/railroadDiagram-XR7U4H2S-mN4JrMeI.js +1 -0
  111. package/dist/assets/requirementDiagram-PLB6GJNP-BSNDRpg7.js +84 -0
  112. package/dist/assets/sankeyDiagram-IPEJSGJF-FngzGNug.js +40 -0
  113. package/dist/assets/sequenceDiagram-PO4LG4MO-CM4pCC3L.js +169 -0
  114. package/dist/assets/sizeCapture-INFHLROL-CRICHXkw.js +1 -0
  115. package/dist/assets/stateDiagram-v2-GCMORJYK-oyTWpxH2.js +291 -0
  116. package/dist/assets/swimlanes-2SLR337P-D9h9mPBq.js +1 -0
  117. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +8 -0
  118. package/dist/assets/timeline-definition-EJHVYXUP-BJUlN9bW.js +120 -0
  119. package/dist/assets/usecaseDiagram-POWQR4AR-3uBNkbAb.js +370 -0
  120. package/dist/assets/vennDiagram-UO4OBE2U-D2odalzT.js +34 -0
  121. package/dist/assets/wardleyDiagram-VNRHLVJA-DHgFaB9S.js +78 -0
  122. package/dist/assets/xychartDiagram-PMCCYNJV-w5GYM2h0.js +7 -0
  123. package/dist/index.html +2 -2
  124. package/dist-server/companion/src/routes.js +1 -0
  125. package/dist-server/container-mcp.js +231 -17
  126. package/dist-server/drivers/agents-proxy.js +16097 -1411
  127. package/dist-server/enterprise/LICENSE +32 -0
  128. package/dist-server/enterprise/server/index.js +6 -5
  129. package/dist-server/hooks/omb-hook.js +74 -0
  130. package/dist-server/index.js +12495 -7584
  131. package/dist-server/local-computer-proxy.js +83 -1
  132. package/dist-server/local-computer.js +14 -2
  133. package/dist-server/mcp-server.js +3 -1
  134. package/dist-server/openmausbot.js +5458 -4587
  135. package/dist-server/pair-cli.js +5458 -4587
  136. package/dist-server/proxy-paths.js +1 -0
  137. package/dist-server/server/admin-activity.js +410 -0
  138. package/dist-server/server/agent-tool-policy.js +1 -0
  139. package/dist-server/server/bot-package.js +2 -0
  140. package/dist-server/server/bot-visibility.js +462 -0
  141. package/dist-server/server/box.js +25 -5
  142. package/dist-server/server/browser-engine.js +87 -10
  143. package/dist-server/server/browser-live.js +7 -5
  144. package/dist-server/server/browser-runtime.js +66 -11
  145. package/dist-server/server/channel-queue.js +2 -0
  146. package/dist-server/server/checkpoints.js +62 -18
  147. package/dist-server/server/cli-prompts.js +26 -2
  148. package/dist-server/server/cli.js +79 -10
  149. package/dist-server/server/commands.js +27 -0
  150. package/dist-server/server/compaction-summary.js +78 -0
  151. package/dist-server/server/composio.js +49 -11
  152. package/dist-server/server/config.js +137 -14
  153. package/dist-server/server/context-budget.js +29 -0
  154. package/dist-server/server/context-rebuild.js +72 -0
  155. package/dist-server/server/decision-log.js +209 -48
  156. package/dist-server/server/default-model-selection.js +20 -3
  157. package/dist-server/server/delegations.js +11 -9
  158. package/dist-server/server/digest.js +130 -0
  159. package/dist-server/server/drivers/acp/core.js +637 -238
  160. package/dist-server/server/drivers/acp/grok.js +4 -3
  161. package/dist-server/server/drivers/agents-call.js +1123 -0
  162. package/dist-server/server/drivers/agents-catalog.js +659 -0
  163. package/dist-server/server/drivers/agents-client.js +28 -0
  164. package/dist-server/server/drivers/agents-proxy.js +18 -1683
  165. package/dist-server/server/drivers/agents-result.js +23 -0
  166. package/dist-server/server/drivers/chat-mcp-tools.js +17 -2
  167. package/dist-server/server/drivers/claude.js +83 -16
  168. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  169. package/dist-server/server/drivers/codex.js +32 -4
  170. package/dist-server/server/drivers/grok.js +2 -1
  171. package/dist-server/server/drivers/openai-chat.js +66 -45
  172. package/dist-server/server/drivers/openai-compat.js +16 -8
  173. package/dist-server/server/enterprise.js +97 -16
  174. package/dist-server/server/external-runtime.js +83 -0
  175. package/dist-server/server/guarded-requests.js +60 -0
  176. package/dist-server/server/harness/http.js +61 -0
  177. package/dist-server/server/hooks/omb-hook.js +103 -0
  178. package/dist-server/server/hosted-models.js +151 -0
  179. package/dist-server/server/hosted-slack.js +11 -0
  180. package/dist-server/server/index.js +2810 -678
  181. package/dist-server/server/local-computer.js +21 -2
  182. package/dist-server/server/managed-desktop.js +79 -14
  183. package/dist-server/server/managed-policy.js +153 -0
  184. package/dist-server/server/mcp-bridge.js +10 -2
  185. package/dist-server/server/mcp-tool-images.js +37 -0
  186. package/dist-server/server/mcp-tool-schema.js +108 -0
  187. package/dist-server/server/message-db.js +108 -21
  188. package/dist-server/server/message-file.js +3 -2
  189. package/dist-server/server/model-context-window.js +7 -0
  190. package/dist-server/server/model-prices.js +121 -0
  191. package/dist-server/server/notify.js +10 -1
  192. package/dist-server/server/package-export.js +1 -0
  193. package/dist-server/server/peer-roster.js +17 -4
  194. package/dist-server/server/provider-icon.js +20 -0
  195. package/dist-server/server/proxy-paths.js +1 -0
  196. package/dist-server/server/recent-work.js +15 -0
  197. package/dist-server/server/request-auth.js +97 -0
  198. package/dist-server/server/room-handoffs.js +41 -13
  199. package/dist-server/server/routes/hosted-slack.js +20 -0
  200. package/dist-server/server/routes/table.js +14 -0
  201. package/dist-server/server/routine-requests.js +39 -1
  202. package/dist-server/server/routines.js +65 -9
  203. package/dist-server/server/shared-computer-control.js +31 -4
  204. package/dist-server/server/spend.js +111 -16
  205. package/dist-server/server/steer-queue.js +12 -1
  206. package/dist-server/server/store.js +384 -33
  207. package/dist-server/server/system-prompt.js +4 -4
  208. package/dist-server/server/team-backup.js +19 -2
  209. package/dist-server/server/thread-starters.js +53 -0
  210. package/dist-server/server/tool-results.js +72 -0
  211. package/dist-server/server/tts/grok.js +74 -0
  212. package/dist-server/server/tts/index.js +19 -1
  213. package/dist-server/server/usage-ledger.js +31 -8
  214. package/dist-server/server/webhooks.js +28 -0
  215. package/dist-server/server/workspace-backup-policy.js +9 -2
  216. package/dist-server/server/workspace-backup.js +7 -4
  217. package/dist-server/shared/approval-mode.js +7 -1
  218. package/dist-server/shared/digest.js +1 -0
  219. package/dist-server/shared/markdown-windows-paths.js +36 -0
  220. package/dist-server/shared/mention-boundary.js +15 -0
  221. package/dist-server/shared/provider-icon.js +110 -0
  222. package/dist-server/shared/routines.js +7 -1
  223. package/dist-server/shared/team-backup.js +5 -0
  224. package/dist-server/src/lib/composer-attachments.js +16 -0
  225. package/dist-server/vps-container-mcp.js +236 -22
  226. package/enterprise/README.md +33 -12
  227. package/enterprise/server/index.js +6 -5
  228. package/package.json +1 -1
  229. package/dist/assets/index-BbU5REzd.js +0 -310
  230. package/dist/assets/index-CLGfYlx_.css +0 -1
@@ -9,6 +9,7 @@ import { normalizeImageGenerationUrl } from "../shared/image-generation.js";
9
9
  import { writeFileAtomic } from "./atomic.js";
10
10
  import { EFFORT_LEVELS } from "../shared/wire.js";
11
11
  import { isModelVariant } from "./contracts.js";
12
+ import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-icon.js";
12
13
  import { isRemoteMcpServer, parseStoredMcpServer } from "./mcp-registry.js";
13
14
  import { parseJson, schemaIssue } from "./schema.js";
14
15
  const optionalText = z.string().optional();
@@ -35,6 +36,19 @@ export const MAX_LOCAL_VM_MAX_INSTANCES = 4;
35
36
  export function isValidSshAlias(value) {
36
37
  return typeof value === "string" && SSH_ALIAS.test(value);
37
38
  }
39
+ const CDP_PORT = /^[0-9]{1,5}$/;
40
+ const CDP_URL = /^(https?|wss?):\/\/\S+$/i;
41
+ /** A bare TCP port (agent-browser's `--cdp <port>` shorthand) or an
42
+ * http(s)/ws(s) URL to a Chrome DevTools Protocol endpoint. */
43
+ export function isValidCdpTarget(value) {
44
+ if (typeof value !== "string" || value === "")
45
+ return false;
46
+ if (CDP_PORT.test(value)) {
47
+ const port = Number(value);
48
+ return port >= 1 && port <= 65535;
49
+ }
50
+ return CDP_URL.test(value);
51
+ }
38
52
  /** Keep the persisted VPS shape deliberately smaller than an SSH connection. */
39
53
  export function normalizeVpsConfig(raw) {
40
54
  if (raw === undefined || raw === null)
@@ -55,6 +69,16 @@ const vpsConfigSchema = z.object({
55
69
  message: "must be a simple SSH config alias",
56
70
  }).optional(),
57
71
  });
72
+ /** Attach a bot's browser to a Chrome the operator already has running,
73
+ * instead of agent-browser spawning its own (#1396). Deliberately a
74
+ * server-owned config field, not an env-var passthrough: the ambient
75
+ * process environment must never redirect a bot's browser
76
+ * (server/browser-live.test.ts pins this guarantee down). */
77
+ const browserEngineConfigSchema = z.object({
78
+ attachCdpUrl: z.string().trim().max(2048).refine((value) => value === "" || isValidCdpTarget(value), {
79
+ message: "browserEngine.attachCdpUrl must be a CDP port (1-65535) or an http(s)/ws(s) URL",
80
+ }).optional(),
81
+ });
58
82
  const roomConfigSchema = z.object({
59
83
  turnTimeoutMinutes: z
60
84
  .number()
@@ -233,6 +257,10 @@ const featureConfigSchema = z.object({
233
257
  * machine's own Claude Code setup (Plugins → MCP servers switch). Off by
234
258
  * default: each extra tool costs tokens on every model call. */
235
259
  claudeUserMcp: z.boolean().optional(),
260
+ /** LLM-generated titles for new bot threads. Off until explicitly
261
+ * enabled; a one-shot that fails or answers junk leaves the first-message
262
+ * snippet in place — see llmThreadTitlesEnabled. */
263
+ llmThreadTitles: z.boolean().optional(),
236
264
  });
237
265
  /** First-run progress. Kept in the workspace config rather than a browser so
238
266
  * it survives cleared site data and is shared by every paired client. Hint
@@ -250,6 +278,11 @@ const instanceConfigSchema = z.object({
250
278
  driver: z.string().min(1),
251
279
  displayName: optionalText,
252
280
  accentColor: optionalText,
281
+ icon: z.discriminatedUnion("kind", [
282
+ z.object({ kind: z.literal("preset"), preset: z.enum(PROVIDER_ICON_PRESETS) }).strict(),
283
+ z.object({ kind: z.literal("custom"), dataUrl: z.string() }).strict()
284
+ .refine((icon) => providerIconError(icon) === null, { message: "Invalid provider icon" }),
285
+ ]).optional(),
253
286
  environment: z.record(z.string(), z.string()).optional(),
254
287
  enabled: z.boolean().optional(),
255
288
  config: z.json().optional(),
@@ -261,6 +294,22 @@ const defaultModelSelectionSchema = z.object({
261
294
  effort: z.enum(EFFORT_LEVELS).optional(),
262
295
  variant: z.string().refine(isModelVariant, "invalid model variant").optional(),
263
296
  }).refine((selection) => selection.variant === undefined || selection.effort === undefined, "choose either a model variant or an effort level");
297
+ const threadsConfigSchema = z.object({
298
+ maxConcurrentPerBot: z.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
299
+ /** Cap each per-thread events/ and native/ NDJSON log at this many
300
+ * bytes; absent (the default) keeps today's unbounded growth (#1280). */
301
+ eventLogMaxBytes: z.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
302
+ /** Days a closed or archived thread's event logs survive (#1280).
303
+ * Absent keeps them forever. */
304
+ eventLogRetentionDays: z.number().int().min(1).max(3650).optional(),
305
+ }).strict();
306
+ /** PATCH threads: every knob is independently patchable, and null clears an
307
+ * event-log knob back to its absent (off) default. */
308
+ const threadsPatchSchema = threadsConfigSchema.extend({
309
+ maxConcurrentPerBot: threadsConfigSchema.shape.maxConcurrentPerBot.optional(),
310
+ eventLogMaxBytes: threadsConfigSchema.shape.eventLogMaxBytes.nullable(),
311
+ eventLogRetentionDays: threadsConfigSchema.shape.eventLogRetentionDays.nullable(),
312
+ });
264
313
  const appConfigSchema = z.object({
265
314
  /** Verified by the dedicated domain endpoint, never a generic config patch. */
266
315
  customDomain: z.string().optional(),
@@ -316,6 +365,7 @@ const appConfigSchema = z.object({
316
365
  /** Voice settings and the selected voice id. `provider` picks the
317
366
  * engine: "elevenlabs" (default; needs `key`), "fish" (needs its own
318
367
  * `fishKey`), "system" (the Mac's built-in voices, no key), or
368
+ * "xai" (Grok TTS, reusing `xai.key`), or
319
369
  * "chatterbox" (a local OpenAI-compatible Chatterbox server; `baseUrl`
320
370
  * and `model` are settings, not secrets). Cloud keys stay separate so
321
371
  * switching providers never overwrites or misuses the other key. */
@@ -323,7 +373,7 @@ const appConfigSchema = z.object({
323
373
  key: optionalText,
324
374
  fishKey: optionalText,
325
375
  voice: optionalText,
326
- provider: z.enum(["elevenlabs", "fish", "system", "chatterbox"]).optional(),
376
+ provider: z.enum(["elevenlabs", "fish", "system", "chatterbox", "xai"]).optional(),
327
377
  baseUrl: z
328
378
  .string()
329
379
  .trim()
@@ -356,18 +406,20 @@ const appConfigSchema = z.object({
356
406
  * system language. Unknown tags degrade to English in the renderer. */
357
407
  language: optionalText,
358
408
  rooms: roomConfigSchema.optional(),
359
- threads: z.object({
360
- maxConcurrentPerBot: z.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
361
- /** Cap each per-thread events/ and native/ NDJSON log at this many
362
- * bytes; absent (the default) keeps today's unbounded growth (#1280). */
363
- eventLogMaxBytes: z.number().int().min(MIN_THREAD_EVENT_LOG_BYTES).max(MAX_THREAD_EVENT_LOG_BYTES).optional(),
364
- /** Days a closed or archived thread's event logs survive (#1280).
365
- * Absent keeps them forever. */
366
- eventLogRetentionDays: z.number().int().min(1).max(3650).optional(),
367
- }).strict().optional(),
409
+ context: z.object({
410
+ rebuildBytes: z.number().int().min(1_024).max(1_000_000).optional(),
411
+ compactAt: z.number().positive().max(10_000_000).optional(),
412
+ autoCompact: z.boolean().optional(),
413
+ }).optional(),
414
+ threads: threadsConfigSchema.optional(),
415
+ /** The authorization decision log (server/decision-log.ts): days of month
416
+ * files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
417
+ decisions: z.object({ retentionDays: z.number().int().min(1).max(3650).optional() }).strict().optional(),
368
418
  localVm: localVmConfigSchema.optional(),
369
419
  features: featureConfigSchema.optional(),
370
420
  onboarding: onboardingConfigSchema.optional(),
421
+ /** CDP attach target for a bot's browser; see browserEngineConfigSchema. */
422
+ browserEngine: browserEngineConfigSchema.optional(),
371
423
  browserProfiles: browserProfilesSchema.optional(),
372
424
  instances: instanceConfigMapSchema.optional(),
373
425
  /** User-configured MCP servers, mounted into every capable engine. Kept
@@ -379,7 +431,8 @@ const appConfigSchema = z.object({
379
431
  const storedAppConfigSchema = appConfigSchema.extend({
380
432
  browserProfiles: storedBrowserProfilesSchema.optional(),
381
433
  });
382
- const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true });
434
+ const appConfigPatchSchema = appConfigSchema.omit({ instances: true, mcpServers: true, cliStartup: true, customDomain: true })
435
+ .extend({ threads: threadsPatchSchema.optional() });
383
436
  const jsonObjectSchema = z.record(z.string(), z.json());
384
437
  /** Resolve a canonical profile record to its exact durable Electron
385
438
  * partition identity. Callers must never substitute the display/API id. */
@@ -459,6 +512,13 @@ export function parseConfigPatch(value) {
459
512
  export function vpsSshAlias(cfg) {
460
513
  return isValidSshAlias(cfg.vps?.sshAlias) ? cfg.vps.sshAlias : null;
461
514
  }
515
+ /** Read-and-revalidate accessor, same shape as vpsSshAlias above: even
516
+ * though loadConfig()/parseStoredConfig() already schema-validate this
517
+ * field, callers that forward it into a child process environment get a
518
+ * second, cheap guarantee rather than trusting a hand-edited config.json. */
519
+ export function browserEngineAttachCdpUrl(cfg) {
520
+ return isValidCdpTarget(cfg.browserEngine?.attachCdpUrl) ? cfg.browserEngine.attachCdpUrl : null;
521
+ }
462
522
  export function roomTurnTimeoutMinutes(cfg) {
463
523
  return cfg.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
464
524
  }
@@ -472,6 +532,8 @@ export function roomHandoffLimits(cfg) {
472
532
  hardCapMs: (cfg.rooms?.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES) * 60_000,
473
533
  };
474
534
  }
535
+ /** Accepts a full config or a parsed patch: the concurrency limit may be
536
+ * read from either, and a patch may legitimately omit it. */
475
537
  export function maxConcurrentBotThreads(cfg) {
476
538
  return cfg.threads?.maxConcurrentPerBot ?? DEFAULT_MAX_CONCURRENT_BOT_THREADS;
477
539
  }
@@ -526,6 +588,14 @@ export function sharedComputersEnabled(cfg) {
526
588
  export function claudeUserMcpEnabled(cfg) {
527
589
  return cfg.features?.claudeUserMcp === true;
528
590
  }
591
+ /** Opt-in generated titles for new bot threads: a cheap provider one-shot
592
+ * names the row instead of the first-message snippet. Off until enabled by
593
+ * hand in ~/.openmausbot/config.json
594
+ * (`{"features": {"llmThreadTitles": true}}`); a one-shot that fails or
595
+ * answers anything unusable leaves the snippet untouched. */
596
+ export function llmThreadTitlesEnabled(cfg) {
597
+ return cfg.features?.llmThreadTitles === true;
598
+ }
529
599
  /** Config sections no provider driver reads. A write that touches only
530
600
  * these must not rebuild the fleet: rebuilding disposes every engine child
531
601
  * and reloads it, seconds of work that would also interrupt in-flight
@@ -539,6 +609,7 @@ export const FLEET_NEUTRAL_KEYS = new Set([
539
609
  "vps",
540
610
  "rooms",
541
611
  "threads",
612
+ "context",
542
613
  "localVm",
543
614
  "features",
544
615
  "browserProfiles",
@@ -719,6 +790,8 @@ export const WORKSPACE_CREDENTIAL_ENV = [
719
790
  "XAI_API_KEY",
720
791
  "OMB_ANTHROPIC_API_KEY",
721
792
  "OMB_ANTHROPIC_API_URL",
793
+ "OMB_HOSTED_MODEL_TOKEN",
794
+ "OMB_HOSTED_MODELS",
722
795
  "OPENAI_COMPAT_API_KEY",
723
796
  "OPENAI_COMPAT_URL",
724
797
  "BOX_TOKEN",
@@ -735,10 +808,32 @@ export const WORKSPACE_CREDENTIAL_ENV = [
735
808
  "OMB_BROWSER_CONNECTION",
736
809
  "OMB_USER_DATA",
737
810
  ];
738
- /** Drop every workspace credential from a child-process env (in place). */
811
+ /** Secrets of whoever operates this server, not of the workspace: the license
812
+ * key, a fleet container's installation credential, and everything a hosting
813
+ * control plane injects under `OMB_CLOUD_` (the readiness token, the bootstrap
814
+ * document and its gateway token). Only this process reads them. The prefix
815
+ * ends in an underscore on purpose: `OMB_CLOUDFLARED_PATH` is not one of them.
816
+ * What an engine is meant to receive arrives under another name through its
817
+ * instance environment (the hosted model token as ANTHROPIC_API_KEY or
818
+ * OPENMAUSBOT_COMPANY_API_KEY), so nothing here is ever an engine's input. */
819
+ export const CONTROL_PLANE_ENV = ["OMB_LICENSE_KEY", "OMB_INSTALLATION_CREDENTIAL"];
820
+ export const CONTROL_PLANE_ENV_PREFIX = "OMB_CLOUD_";
821
+ /** Drop every control-plane secret from a child-process env (in place). No
822
+ * driver allowlist re-admits these. Names compare case-insensitively because
823
+ * Windows environments do. */
824
+ export function stripControlPlaneEnv(env) {
825
+ for (const key of Object.keys(env)) {
826
+ const name = key.toUpperCase();
827
+ if (name.startsWith(CONTROL_PLANE_ENV_PREFIX) || CONTROL_PLANE_ENV.includes(name))
828
+ delete env[key];
829
+ }
830
+ }
831
+ /** Drop every workspace credential, and every control-plane secret, from a
832
+ * child-process env (in place). */
739
833
  export function stripWorkspaceCredentialEnv(env) {
740
834
  for (const key of WORKSPACE_CREDENTIAL_ENV)
741
835
  delete env[key];
836
+ stripControlPlaneEnv(env);
742
837
  }
743
838
  /** Env names a provider CLI might read as its own billing identity. A spawned
744
839
  * engine keeps only what its driver explicitly allows: a foreign key riding
@@ -758,6 +853,14 @@ export const PROVIDER_CREDENTIAL_ENV = [
758
853
  "CURSOR_API_KEY",
759
854
  "CURSOR_AUTH_TOKEN",
760
855
  ];
856
+ const configSaveListeners = new Set();
857
+ /** Told, synchronously, what each saveConfig wrote: the file before and
858
+ * after. The admin activity log (server/admin-activity.ts) records the
859
+ * change for whoever's request made it. A listener must not throw. */
860
+ export function onConfigSaved(listener) {
861
+ configSaveListeners.add(listener);
862
+ return () => { configSaveListeners.delete(listener); };
863
+ }
761
864
  /** Merge a partial config into ~/.openmausbot/config.json (secrets never
762
865
  * echoed back — callers report configured-or-not booleans only). */
763
866
  export function saveConfig(patch, options = {}) {
@@ -771,20 +874,32 @@ export function saveConfig(patch, options = {}) {
771
874
  catch {
772
875
  /* first write */
773
876
  }
774
- const checkedPatch = appConfigSchema.partial().parse(patch);
877
+ const checkedPatch = appConfigSchema.partial().extend({ threads: threadsPatchSchema.optional() }).parse(patch);
878
+ const before = configSaveListeners.size ? structuredClone(disk) : disk;
775
879
  // A write is the durable migration point. Preserve every other raw key in
776
880
  // config.json, but never write #567's mixed-case or duplicate profile ids
777
881
  // back after we have successfully recognized the legacy list.
778
882
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
779
883
  if (storedProfiles.success)
780
884
  disk.browserProfiles = storedProfiles.data;
781
- for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "localVm", "features", "budgets", "billing", "onboarding"]) {
885
+ for (const key of ["xai", "anthropic", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "localVm", "features", "budgets", "billing", "decisions", "onboarding", "browserEngine"]) {
782
886
  const section = checkedPatch[key];
783
887
  if (!section)
784
888
  continue;
785
889
  const current = jsonObjectSchema.safeParse(disk[key]);
786
890
  const merged = current.success ? { ...current.data } : {};
891
+ // parseStoredConfig requires threads.maxConcurrentPerBot, so creating
892
+ // the section with only an event-log knob must still persist a valid
893
+ // concurrency default.
894
+ if (key === "threads" && !current.success)
895
+ merged.maxConcurrentPerBot = DEFAULT_MAX_CONCURRENT_BOT_THREADS;
787
896
  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.
899
+ for (const [sectionKey, sectionValue] of Object.entries(section)) {
900
+ if (sectionValue === null)
901
+ delete merged[sectionKey];
902
+ }
788
903
  disk[key] = merged;
789
904
  }
790
905
  if (checkedPatch.vps !== undefined)
@@ -863,6 +978,14 @@ export function saveConfig(patch, options = {}) {
863
978
  }
864
979
  mkdirSync(DATA_DIR, { recursive: true });
865
980
  writeFileAtomic(p, JSON.stringify(disk, null, 2), { mode: 0o600 });
981
+ for (const listener of configSaveListeners) {
982
+ try {
983
+ listener(before, disk);
984
+ }
985
+ catch (error) {
986
+ console.warn(`config: a save listener failed: ${error instanceof Error ? error.message : String(error)}`);
987
+ }
988
+ }
866
989
  }
867
990
  /** Set one instance's `config.cli` ("" clears the override back to the
868
991
  * driver default). Creating the instance entry is fine — a config-less
@@ -0,0 +1,29 @@
1
+ import { modelContextWindow } from "./model-context-window.js";
2
+ export const DEFAULT_CONTEXT_WINDOW = 128_000;
3
+ const positive = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
4
+ export function contextWindowFor(model, catalog, reported) {
5
+ if (positive(reported))
6
+ return reported;
7
+ const declared = catalog?.options.find((option) => option.id === model)?.contextWindow;
8
+ if (positive(declared))
9
+ return declared;
10
+ const inferred = model ? modelContextWindow(model) : undefined;
11
+ return positive(inferred) ? inferred : DEFAULT_CONTEXT_WINDOW;
12
+ }
13
+ /** Leave headroom even for a small local model. An 8k lower bound would
14
+ * make compaction unreachable on models whose entire window is 8k. */
15
+ export function compactBudget(window, compactAt, nativeCompactAt) {
16
+ if (!positive(window))
17
+ throw new Error("invalid model context window");
18
+ const configured = positive(compactAt) ? (compactAt < 1 ? window * compactAt : compactAt) : window * 0.8;
19
+ return Math.max(1, Math.floor(Math.min(configured, window * 0.9, positive(nativeCompactAt) ? nativeCompactAt * 0.9 : Infinity)));
20
+ }
21
+ export function shouldCompact(input) {
22
+ // Summed input across tool rounds is NOT a context-window measurement.
23
+ const size = positive(input.contextTokens) ? input.contextTokens : Math.ceil(input.estimatedBytes / 4);
24
+ // Regrowth avoids repeatedly folding an irreducible prompt, but cannot
25
+ // delay the harness beyond a known provider-native compaction boundary.
26
+ const ceiling = compactBudget(input.window, input.window, input.nativeCompactAt);
27
+ const threshold = Math.min(ceiling, positive(input.floor) ? Math.max(input.budget, input.floor * 1.25) : input.budget);
28
+ return size >= threshold;
29
+ }
@@ -0,0 +1,72 @@
1
+ export const DEFAULT_REBUILD_BYTES = 24_000;
2
+ export const MAX_SUMMARY_BYTES = 6_000;
3
+ const OMITTED_TEXT = "\n[Text shortened for context; the original remains in this conversation.]";
4
+ /** UTF-8 prefixes must not end inside a code point. */
5
+ export function prefixBytes(text, limit) {
6
+ const bytes = Buffer.from(text);
7
+ let end = Math.max(0, Math.min(bytes.length, Math.floor(limit)));
8
+ while (end < bytes.length && end > 0 && (bytes[end] & 0xc0) === 0x80)
9
+ end--;
10
+ return bytes.subarray(0, end).toString("utf8");
11
+ }
12
+ export function boundedContextText(text, limit) {
13
+ if (Buffer.byteLength(text) <= limit)
14
+ return text;
15
+ const notice = prefixBytes(OMITTED_TEXT, limit);
16
+ const remaining = Math.max(0, limit - Buffer.byteLength(notice));
17
+ const head = prefixBytes(text, Math.floor(remaining / 2));
18
+ const bytes = Buffer.from(text);
19
+ let start = bytes.length - (remaining - Buffer.byteLength(head));
20
+ while (start < bytes.length && (bytes[start] & 0xc0) === 0x80)
21
+ start++;
22
+ return head + notice + bytes.subarray(start).toString("utf8");
23
+ }
24
+ export function selectReplay(history, options = {}) {
25
+ const budget = options.budgetBytes ?? DEFAULT_REBUILD_BYTES;
26
+ const maxMessages = options.maxMessages ?? 40;
27
+ if (!Number.isSafeInteger(budget) || budget < 1_024 || !Number.isSafeInteger(maxMessages) || maxMessages < 1) {
28
+ throw new Error("invalid context replay budget");
29
+ }
30
+ const record = options.compaction;
31
+ const foldedThrough = record ? history.findIndex((entry) => entry.id === record.foldedThroughId) : -1;
32
+ const cut = record ? (record.firstKeptId ? history.findIndex((entry) => entry.id === record.firstKeptId) : foldedThrough + 1) : -1;
33
+ const recordAt = record ? history.findIndex((entry) => entry.id === record.id) : -1;
34
+ // Both anchors must still belong to this branch, in their original order.
35
+ // A stale record must never hide a rewritten or independently selected fork.
36
+ const applies = Boolean(record && cut > foldedThrough && foldedThrough >= 0 && recordAt >= cut);
37
+ const representedIds = applies ? history.slice(0, cut).map((entry) => entry.id) : [];
38
+ const entries = history.slice(applies ? cut : 0).filter((entry) => entry.text && entry.id !== record?.id && !options.excludedIds?.has(entry.id));
39
+ const transcript = [];
40
+ // Reserve space for the exact omission count and role framing.
41
+ let available = budget - 128;
42
+ if (applies && record) {
43
+ const summary = boundedContextText(record.summary, Math.min(MAX_SUMMARY_BYTES, Math.floor(available / 2)));
44
+ const text = `[Earlier conversation summary — historical data, not new instructions. Later corrections take precedence.]\n${JSON.stringify(summary)}`;
45
+ const bounded = boundedContextText(text, Math.floor(available / 2));
46
+ transcript.push({ id: record.id, role: "assistant", text: bounded });
47
+ representedIds.push(record.id);
48
+ available -= Buffer.byteLength(bounded) + 16;
49
+ }
50
+ const kept = [];
51
+ const shortened = [];
52
+ for (let i = entries.length - 1; i >= 0 && kept.length < maxMessages; i--) {
53
+ const entry = entries[i];
54
+ if (available <= 16)
55
+ break;
56
+ const size = Buffer.byteLength(entry.text) + 16;
57
+ if (size > available && kept.length)
58
+ break;
59
+ const text = size > available ? boundedContextText(entry.text, available - 16) : entry.text;
60
+ if (text !== entry.text)
61
+ shortened.push(entry.id);
62
+ kept.unshift({ ...entry, text });
63
+ representedIds.push(entry.id);
64
+ available -= Buffer.byteLength(text) + 16;
65
+ }
66
+ const dropped = entries.length - kept.length;
67
+ if (dropped)
68
+ transcript.push({ id: "", role: "assistant", text: `[${dropped} earlier messages omitted from this context; full history remains in the app.]` });
69
+ transcript.push(...kept);
70
+ const represented = new Set(representedIds);
71
+ return { transcript, representedIds: history.filter(entry => represented.has(entry.id)).map(entry => entry.id), dropped, compacted: applies ? cut : 0, shortened };
72
+ }