openmausbot 0.1.88 → 0.1.90

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 (147) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{abnfDiagram-O67JEVCF-qd9VD3GV.js → abnfDiagram-O67JEVCF-DUyXd0kL.js} +1 -1
  3. package/dist/assets/{arc-3RwkUjRe.js → arc-q73qM-At.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CFKSJS7a.js → architectureDiagram-NJMV4G6O-D33O3Wur.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CG1rEfWD.js → blockDiagram-BEXU5L5S-DLsJeoXC.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-CBW9N664.js → c4Diagram-YGBWAQC7-DnaljZnu.js} +1 -1
  7. package/dist/assets/channel-BCuyHjFV.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-2mRfTksB.js → chunk-2Q5K7J3B-Da0h2i79.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-Cv1dpErj.js → chunk-3FUC2YCW-CfzHi54f.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-B7IACJp-.js → chunk-5VM5RSS4-LjE4d9Jf.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-An0hZtYa.js → chunk-GTNCS2PH-DY-UGmvc.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-BcJB2nEm.js → chunk-GWA4HPMP-BqeGhyhP.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-CV_W7TN7.js → chunk-JWPE2WC7-BYa9EBAg.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-Bcs_kwnA.js → chunk-NETBCI7D-vTrDtWyb.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-lrx_DxFP.js → chunk-WEXAMYUT-ic5tXadM.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-DabHlLGM.js → chunk-XXDRQBXY-DvRCY-c6.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BnHj6P7d.js → classDiagram-v2-NBCMYWYE-DVbmgz_L.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-CfLp6UDi.js → cose-bilkent-JH36ORCC-vdUs2n57.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-Dm4dbVA1.js → cynefin-OW5HDTMX-BvLGlV3U.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-B9mH-PID.js → cynefinDiagram-VND7K2PF-C4GcJQDt.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DDc_NVQJ.js → dagre-6A5THRUB-BMSaHpt_.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-lwr8Ip8-.js → diagram-22UHCM2B-D5b9UBhC.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-BLLe6dmI.js → diagram-3UASUU5V-CIKLlB7Y.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-6eUIq4lz.js → diagram-ATOU4E4O-BzXb-7k7.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-O-pRN7VQ.js → diagram-CDSNMT55-HsTvJ7uT.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BietvGAI.js → diagram-MLGK6HIB-Db9ySNbQ.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-Bd2Cl62X.js → diagram-MPIPVDR6-D6BKbpqY.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-B6iDy69W.js → ebnfDiagram-ZINNZB2B-Bq067L0t.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-DQVHfKe0.js → elk-276RUBZZ-DhaD7fNJ.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-BWdVp2tQ.js → erDiagram-OPXOYQCR-fwIhfz36.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-WLE6rKB4.js → flowDiagram-KWPJA3E3-DygIyu0V.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-BbjS-4gn.js → ganttDiagram-FUAMR5RP-DPYfSO96.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dpy2QSGq.js → gitGraphDiagram-X574FWY7-DhkHeCle.js} +1 -1
  34. package/dist/assets/index-CRvo-DS2.css +1 -0
  35. package/dist/assets/index-CWYGlA63.js +588 -0
  36. package/dist/assets/{index-CIaHn5NS.js → index-G1PHKGyh.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-D_jKWlKe.js → infoDiagram-VRGFBTTK-DstBLxME.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-DLJohSuK.js → ishikawaDiagram-OU5B5YK6-CrIsGgch.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-DPOFj0qN.js → journeyDiagram-ZHPQQLJL-CYypGDbB.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BI7v-xpK.js → kanban-definition-PNTS6WVX-DGjPt8E_.js} +1 -1
  41. package/dist/assets/{linear-C2pWPSxO.js → linear-Dwc3Hrru.js} +1 -1
  42. package/dist/assets/{mermaid.core-CR-j5WQU.js → mermaid.core-Dc4OePBL.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-lz9GvXRh.js → mindmap-definition-NLK3R4M7-DV-vepj-.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-Dl4_ia61.js → pegDiagram-GJSIUBJH-lx5Kj-n5.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DWuT3n88.js → pieDiagram-5QR66LMP-Bq7Vmm15.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-DXWe9Jx7.js → quadrantDiagram-O4NWA36T-DeqeqOBj.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-COY1IBVY.js → railroadDiagram-XR7U4H2S-CXwRQzSC.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DnyVuhsQ.js → requirementDiagram-PLB6GJNP-8tAeoB88.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-BSc4NdGK.js → sankeyDiagram-IPEJSGJF-BG5zjGWU.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-kqEU2EGq.js → sequenceDiagram-PO4LG4MO-DKF3iLcw.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BvB4bkWD.js → sizeCapture-INFHLROL-B-y1qUgT.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BIrqNv7y.js → stateDiagram-v2-GCMORJYK-MkzPgxAc.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-CFnalQUE.js → swimlanes-2SLR337P-DnbaOEwH.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-CsSWJnSQ.js → timeline-definition-EJHVYXUP-Bb4xG_Bt.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-DkAmJ1EE.js → usecaseDiagram-POWQR4AR-JgkR9pFf.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C3vd9drL.js → vennDiagram-UO4OBE2U-CIPIrAYe.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-j26CM-pi.js → wardleyDiagram-VNRHLVJA-BjCLuUvJ.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-BYu1Q_at.js → xychartDiagram-PMCCYNJV-DKgiaYxT.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/cloud-home-start.js +14795 -0
  62. package/dist-server/container-mcp.js +60 -4
  63. package/dist-server/drivers/agents-proxy.js +20 -10
  64. package/dist-server/index.js +48924 -46153
  65. package/dist-server/openmausbot.js +544 -253
  66. package/dist-server/pair-cli.js +544 -253
  67. package/dist-server/permission-proxy.js +1 -1
  68. package/dist-server/server/admission.js +46 -0
  69. package/dist-server/server/automatic-recovery.js +15 -0
  70. package/dist-server/server/{box-create-idempotency.js → boat-create-idempotency.js} +32 -31
  71. package/dist-server/server/{box-delete-journal.js → boat-delete-journal.js} +26 -25
  72. package/dist-server/server/{box.js → boat.js} +330 -328
  73. package/dist-server/server/bot-profile.js +7 -1
  74. package/dist-server/server/channel-queue.js +58 -18
  75. package/dist-server/server/checkpoints.js +98 -52
  76. package/dist-server/server/claim-idle.js +33 -0
  77. package/dist-server/server/cli-api-setup.js +0 -1
  78. package/dist-server/server/cloud-backend.js +5 -5
  79. package/dist-server/server/cloud-home-start.js +103 -0
  80. package/dist-server/server/cloud-home.js +218 -0
  81. package/dist-server/server/cloud-overflow.js +167 -0
  82. package/dist-server/server/composio.js +5 -0
  83. package/dist-server/server/computer-observation.js +2 -2
  84. package/dist-server/server/config.js +124 -6
  85. package/dist-server/server/container-computer.js +23 -0
  86. package/dist-server/server/contracts.js +10 -0
  87. package/dist-server/server/control-client.js +2 -2
  88. package/dist-server/server/delegations.js +11 -3
  89. package/dist-server/server/drivers/acp/core.js +129 -21
  90. package/dist-server/server/drivers/acp/qwen.js +3 -0
  91. package/dist-server/server/drivers/agents-call.js +5 -1
  92. package/dist-server/server/drivers/agents-catalog.js +9 -3
  93. package/dist-server/server/drivers/{boxagent.js → boatagent.js} +14 -13
  94. package/dist-server/server/drivers/builtIn.js +2 -2
  95. package/dist-server/server/drivers/{chat-box-tools.js → chat-boat-tools.js} +3 -3
  96. package/dist-server/server/drivers/chat-mcp-tools.js +2 -2
  97. package/dist-server/server/drivers/claude.js +177 -13
  98. package/dist-server/server/drivers/native.js +1 -1
  99. package/dist-server/server/drivers/openai-chat.js +19 -4
  100. package/dist-server/server/drivers/pi.js +3 -3
  101. package/dist-server/server/harness-capabilities.js +18 -3
  102. package/dist-server/server/hosted-models.js +6 -1
  103. package/dist-server/server/index.js +1163 -530
  104. package/dist-server/server/local-vm-inventory.js +11 -1
  105. package/dist-server/server/local-vm-seat-pool.js +141 -0
  106. package/dist-server/server/managed-policy.js +4 -1
  107. package/dist-server/server/memory-capture.js +194 -0
  108. package/dist-server/server/memory-entries.js +76 -0
  109. package/dist-server/server/memory-organize.js +102 -0
  110. package/dist-server/server/memory-tidy.js +144 -0
  111. package/dist-server/server/memory-topics.js +169 -0
  112. package/dist-server/server/memory-upkeep.js +452 -0
  113. package/dist-server/server/message-db.js +49 -5
  114. package/dist-server/server/permission-proxy.js +3 -3
  115. package/dist-server/server/profile-learned.js +98 -0
  116. package/dist-server/server/profile-versions.js +45 -11
  117. package/dist-server/server/recall.js +172 -0
  118. package/dist-server/server/redact.js +1 -1
  119. package/dist-server/server/room-cwd.js +1 -1
  120. package/dist-server/server/room-handoffs.js +61 -6
  121. package/dist-server/server/routes/bot-memory.js +215 -0
  122. package/dist-server/server/routine-requests.js +27 -16
  123. package/dist-server/server/routines.js +10 -6
  124. package/dist-server/server/steer-queue.js +41 -10
  125. package/dist-server/server/store.js +25 -1
  126. package/dist-server/server/surface.js +1 -1
  127. package/dist-server/server/system-prompt.js +16 -5
  128. package/dist-server/server/team-computers.js +2 -2
  129. package/dist-server/server/tightening-requests.js +34 -4
  130. package/dist-server/server/tool-summary.js +2 -2
  131. package/dist-server/server/tts/elevenlabs.js +1 -1
  132. package/dist-server/server/turn-resources.js +87 -11
  133. package/dist-server/server/vps-computer.js +1 -1
  134. package/dist-server/server/webhooks.js +21 -3
  135. package/dist-server/server/workspace.js +57 -22
  136. package/dist-server/shared/ask-question.js +7 -5
  137. package/dist-server/shared/bot-avatar.js +19 -0
  138. package/dist-server/shared/credential-request.js +3 -3
  139. package/dist-server/shared/tool-surface.js +1 -1
  140. package/dist-server/src/lib/composer-attachments.js +26 -0
  141. package/dist-server/vps-container-mcp.js +60 -4
  142. package/dist-server/workspace-backup.worker.js +60 -4
  143. package/package.json +1 -1
  144. package/dist/assets/channel-CnRnO3tF.js +0 -1
  145. package/dist/assets/index-BIMdIbaw.css +0 -1
  146. package/dist/assets/index-CHnODCr2.js +0 -583
  147. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +0 -8
@@ -13,6 +13,7 @@ import { isModelVariant } from "./contracts.js";
13
13
  import { PROVIDER_ICON_PRESETS, providerIconError } from "../shared/provider-icon.js";
14
14
  import { isRemoteMcpServer, parseStoredMcpServer } from "./mcp-registry.js";
15
15
  import { parseJson, schemaIssue } from "./schema.js";
16
+ import { CLOUD_SEAT_IDLE_STOP_MS } from "./cloud-overflow.js";
16
17
  const optionalText = z.string().optional();
17
18
  const SSH_ALIAS = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
18
19
  const LEGACY_BROWSER_PROFILE_ID = /^[A-Za-z0-9_-]{1,40}$/;
@@ -87,7 +88,8 @@ const roomConfigSchema = z.object({
87
88
  .min(MIN_ROOM_TURN_TIMEOUT_MINUTES)
88
89
  .max(MAX_ROOM_TURN_TIMEOUT_MINUTES),
89
90
  /** Room handoff tree lifetime. Active execution pauses this clock; the
90
- * hard cap is wall-clock and bounds trees that never stop executing. */
91
+ * hard cap is the maximum stall window, measured wall-clock from the
92
+ * tree's last durable progress. */
91
93
  handoffLifetimeMinutes: z.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
92
94
  handoffMinRunwayMinutes: z.number().int().min(1).max(MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
93
95
  handoffHardCapMinutes: z.number().int().min(1).max(7 * MAX_ROOM_TURN_TIMEOUT_MINUTES).optional(),
@@ -95,8 +97,14 @@ const roomConfigSchema = z.object({
95
97
  (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) &&
96
98
  (rooms.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) <=
97
99
  (rooms.handoffHardCapMinutes ?? DEFAULT_ROOM_HANDOFF_HARD_CAP_MINUTES), { message: "rooms handoff bounds must satisfy handoffMinRunwayMinutes <= handoffLifetimeMinutes <= handoffHardCapMinutes" });
100
+ /** Isolation for bot desktops. Migration note (issue #1654): switching
101
+ * modes changes lease keys and vm-home directories, so desktops cold-start
102
+ * under the new mode — a pool seat lives in vm-homes/pool-N — while the old
103
+ * mode's workspaces stay on disk until removed. maxInstances caps per-bot
104
+ * desktops in per-bot mode, or the pool's seat count in pool mode; it is
105
+ * not a total across modes. Default stays "shared". */
98
106
  const localVmConfigSchema = z.object({
99
- mode: z.enum(["shared", "per-bot"]).optional(),
107
+ mode: z.enum(["shared", "per-bot", "pool"]).optional(),
100
108
  maxInstances: z
101
109
  .number()
102
110
  .int()
@@ -247,6 +255,10 @@ const featureConfigSchema = z.object({
247
255
  skillAuthoring: z.boolean().optional(),
248
256
  /** Show each tool run in the transcript. Off unless explicitly enabled. */
249
257
  showToolCalls: z.boolean().optional(),
258
+ /** Run a routine inside the conversation it reports to, instead of a hidden
259
+ * thread. Off unless explicitly enabled. The run still starts from its own
260
+ * instructions, but the messages stay in that chat. */
261
+ routinesInConversation: z.boolean().optional(),
250
262
  /** Experimental built-in browser. Off until explicitly enabled; each bot
251
263
  * also has its own switch. */
252
264
  browser: z.boolean().optional(),
@@ -260,8 +272,23 @@ const featureConfigSchema = z.object({
260
272
  claudeUserMcp: z.boolean().optional(),
261
273
  /** LLM-generated titles for new bot threads. Off until explicitly
262
274
  * enabled; a one-shot that fails or answers junk leaves the first-message
263
- * snippet in place — see llmThreadTitlesEnabled. */
275
+ * snippet in place — see llmThreadTitlesEnabled. */
264
276
  llmThreadTitles: z.boolean().optional(),
277
+ /** Before each turn, passages from the bot's own memory files and earlier
278
+ * conversations that share words with the message ride into the turn.
279
+ * Read-only; on unless explicitly switched off — see autoRecallEnabled. */
280
+ autoRecall: z.boolean().optional(),
281
+ /** Idle release for computer claims (#1653): a desktop seat that stays
282
+ * screen-quiet for 90 seconds is released to waiting turns while its
283
+ * holder's turn still lives; the previous holder re-claims directly
284
+ * for 10 minutes and yields to an occupied seat. Off until baked; see
285
+ * computerClaimIdleReleaseEnabled for how to enable it by hand. */
286
+ computerClaimIdleRelease: z.boolean().optional(),
287
+ /** Consented cloud overflow for local computer waits (#1655): a wait
288
+ * may offer a per-second-billed cloud seat, but only through an explicit
289
+ * consent card or a configured allowlist — never a silent Auto default.
290
+ * Off until baked; see cloudOverflowEnabled for how to enable it by hand. */
291
+ cloudOverflow: z.boolean().optional(),
265
292
  });
266
293
  /** First-run progress. Kept in the workspace config rather than a browser so
267
294
  * it survives cleared site data and is shared by every paired client. Hint
@@ -295,6 +322,10 @@ const defaultModelSelectionSchema = z.object({
295
322
  effort: z.enum(EFFORT_LEVELS).optional(),
296
323
  variant: z.string().refine(isModelVariant, "invalid model variant").optional(),
297
324
  }).refine((selection) => selection.variant === undefined || selection.effort === undefined, "choose either a model variant or an effort level");
325
+ const automaticRecoverySchema = z.object({
326
+ enabled: z.boolean(),
327
+ backup: defaultModelSelectionSchema.optional(),
328
+ }).strict().refine((recovery) => !recovery.enabled || recovery.backup !== undefined, { message: "Choose a backup model before enabling automatic recovery", path: ["backup"] });
298
329
  const threadsConfigSchema = z.object({
299
330
  maxConcurrentPerBot: z.number().int().min(1).max(MAX_CONCURRENT_BOT_THREADS),
300
331
  /** Cap each per-thread events/ and native/ NDJSON log at this many
@@ -327,6 +358,7 @@ const appConfigSchema = z.object({
327
358
  * addresses or `@domain` entries; admins get every scope, members chat only. */
328
359
  signIn: z.object({ admins: z.array(z.string().max(320)).max(500).optional(), members: z.array(z.string().max(320)).max(5000).optional() }).optional(),
329
360
  defaultModelSelection: defaultModelSelectionSchema.optional(),
361
+ automaticRecovery: automaticRecoverySchema.optional(),
330
362
  newBotDefaults: newBotDefaultsSchema.optional(),
331
363
  newBots: newBotsConfigSchema.optional(),
332
364
  /** CLI-only launch preferences. Never enable remote access implicitly. */
@@ -371,6 +403,8 @@ const appConfigSchema = z.object({
371
403
  /** Project key used for Sessions, catalog and agent tools. userId/sessionId
372
404
  * are non-secret local identifiers used to reuse one Composio Session. */
373
405
  composio: z.object({ apiKey: optionalText, userId: optionalText, sessionId: optionalText }).optional(),
406
+ /** Historical config section name "box" (the persisted config.json key); the
407
+ * provider is Boat now and the key is kept for compatibility. */
374
408
  box: z.object({ token: optionalText }).optional(),
375
409
  vps: vpsConfigSchema.optional(),
376
410
  /** Optional OpenCode key; persisted write-only and passed only to its child. */
@@ -424,10 +458,26 @@ const appConfigSchema = z.object({
424
458
  compactAt: z.number().positive().max(10_000_000).optional(),
425
459
  autoCompact: z.boolean().optional(),
426
460
  }).optional(),
461
+ /** Memory upkeep timing, for bots with Memory upkeep switched on. */
462
+ memory: z.object({
463
+ /** Quiet time after a turn before its facts are captured (ms). */
464
+ captureQuietMs: z.number().int().min(1_000).max(24 * 60 * 60_000).optional(),
465
+ /** Local hour (0-23) after which the nightly tidy-up runs. */
466
+ tidyHour: z.number().int().min(0).max(23).optional(),
467
+ }).strict().optional(),
427
468
  threads: threadsConfigSchema.optional(),
428
469
  /** The authorization decision log (server/decision-log.ts): days of month
429
470
  * files kept, at least; OMB_DECISION_RETENTION_DAYS wins when set. */
430
471
  decisions: z.object({ retentionDays: z.number().int().min(1).max(3650).optional() }).strict().optional(),
472
+ /** #1655 cloud-overflow settings. perSecondCostUsd is the operator's own
473
+ * verified rate: with no price configured the feature stays inert rather
474
+ * than show an invented one. allowlistedThreads carries standing consent
475
+ * for exact thread ids. */
476
+ cloudOverflow: z.object({
477
+ perSecondCostUsd: z.number().positive().max(10).optional(),
478
+ idleStopMs: z.number().int().positive().max(24 * 60 * 60_000).optional(),
479
+ allowlistedThreads: z.array(z.string().min(1)).max(1000).optional(),
480
+ }).strict().optional(),
431
481
  localVm: localVmConfigSchema.optional(),
432
482
  features: featureConfigSchema.optional(),
433
483
  onboarding: onboardingConfigSchema.optional(),
@@ -536,8 +586,9 @@ export function roomTurnTimeoutMinutes(cfg) {
536
586
  return cfg.rooms?.turnTimeoutMinutes ?? DEFAULT_ROOM_TURN_TIMEOUT_MINUTES;
537
587
  }
538
588
  /** Room handoff tree budgets in milliseconds. The tree lifetime pauses
539
- * while a node is actively executing; the hard cap is wall-clock and bounds
540
- * trees that never stop. Read when the server starts. */
589
+ * while a node is actively executing; the hard cap is the maximum stall
590
+ * window, measured wall-clock from the tree's last durable progress. Read
591
+ * when the server starts. */
541
592
  export function roomHandoffLimits(cfg) {
542
593
  return {
543
594
  lifetimeMs: (cfg.rooms?.handoffLifetimeMinutes ?? DEFAULT_ROOM_HANDOFF_LIFETIME_MINUTES) * 60_000,
@@ -573,9 +624,26 @@ export function localVmMaxInstances(cfg) {
573
624
  export function skillAuthoringEnabled(cfg) {
574
625
  return cfg.features?.skillAuthoring !== false;
575
626
  }
627
+ /** Automatic recall is read-only, so it is on unless switched off. */
628
+ export function autoRecallEnabled(cfg) {
629
+ return cfg.features?.autoRecall !== false;
630
+ }
631
+ export const DEFAULT_CAPTURE_QUIET_MS = 2 * 60_000;
632
+ export const DEFAULT_TIDY_HOUR = 3;
633
+ export function captureQuietMs(cfg) {
634
+ return cfg.memory?.captureQuietMs ?? DEFAULT_CAPTURE_QUIET_MS;
635
+ }
636
+ export function tidyHour(cfg) {
637
+ return cfg.memory?.tidyHour ?? DEFAULT_TIDY_HOUR;
638
+ }
576
639
  export function showToolCallsEnabled(cfg) {
577
640
  return cfg.features?.showToolCalls === true;
578
641
  }
642
+ /** A routine's turns are posted in the conversation that receives its card.
643
+ * Off by default, so scheduled work stays in a hidden thread. */
644
+ export function routinesInConversationEnabled(cfg) {
645
+ return cfg.features?.routinesInConversation === true;
646
+ }
579
647
  /** Workspace-level gate for the experimental built-in browser. A bot's own
580
648
  * switch sits under it, so either can withhold the browser. */
581
649
  export function builtInBrowserEnabled(cfg) {
@@ -609,6 +677,43 @@ export function claudeUserMcpEnabled(cfg) {
609
677
  export function llmThreadTitlesEnabled(cfg) {
610
678
  return cfg.features?.llmThreadTitles === true;
611
679
  }
680
+ /** Idle release for computer claims (#1653): a whole-turn desktop hold
681
+ * ends after a screen-quiet window (default 90 seconds, screen-poller
682
+ * frames excluded) instead of at turn settle, and the previous holder
683
+ * re-claims directly inside a reclaim window (default 10 minutes) while
684
+ * yielding to a seat another turn already holds. Off unless an explicit
685
+ * `true` — bake it as a maintainer-only flag first, exactly like
686
+ * sharedComputers: enable by hand in ~/.openmausbot/config.json
687
+ * (`{"features": {"computerClaimIdleRelease": true}}`) and restart. */
688
+ export function computerClaimIdleReleaseEnabled(cfg) {
689
+ return cfg.features?.computerClaimIdleRelease === true;
690
+ }
691
+ /** Consented cloud overflow for local computer waits (#1655): while a turn
692
+ * waits for a local desktop, it may offer overflow to a per-second-billed
693
+ * cloud seat — never silently: the card names the cost, and only an
694
+ * explicit per-conversation consent or a configured allowlist thread may
695
+ * start the machine, which then hard-stops after an idle window. Off
696
+ * unless an explicit `true`, like computerClaimIdleRelease: enable by
697
+ * hand in ~/.openmausbot/config.json
698
+ * (`{"features": {"cloudOverflow": true}, "cloudOverflow": {"perSecondCostUsd": 0.0004}}`)
699
+ * and restart. */
700
+ export function cloudOverflowEnabled(cfg) {
701
+ return cfg.features?.cloudOverflow === true;
702
+ }
703
+ /** The per-second rate shown on the card, or null when the operator has
704
+ * not set one: the offer fails closed rather than guess a price. */
705
+ export function cloudOverflowPerSecondCostUsd(cfg) {
706
+ const cost = cfg.cloudOverflow?.perSecondCostUsd;
707
+ return typeof cost === "number" && Number.isFinite(cost) && cost > 0 ? cost : null;
708
+ }
709
+ /** How long a cloud seat may stay screen-quiet before it is stopped. */
710
+ export function cloudOverflowIdleStopMs(cfg) {
711
+ return cfg.cloudOverflow?.idleStopMs ?? CLOUD_SEAT_IDLE_STOP_MS;
712
+ }
713
+ /** Threads with standing consent, by exact thread id. */
714
+ export function cloudOverflowAllowlistedThreads(cfg) {
715
+ return new Set(cfg.cloudOverflow?.allowlistedThreads ?? []);
716
+ }
612
717
  /** Config sections no provider driver reads. A write that touches only
613
718
  * these must not rebuild the fleet: rebuilding disposes every engine child
614
719
  * and reloads it, seconds of work that would also interrupt in-flight
@@ -622,9 +727,12 @@ export const FLEET_NEUTRAL_KEYS = new Set([
622
727
  "vps",
623
728
  "rooms",
624
729
  "threads",
730
+ "automaticRecovery",
625
731
  "context",
732
+ "memory",
626
733
  "localVm",
627
734
  "features",
735
+ "cloudOverflow",
628
736
  "browserProfiles",
629
737
  "onboarding",
630
738
  ]);
@@ -746,6 +854,7 @@ export function loadConfig() {
746
854
  cfg.composio = { ...cfg.composio };
747
855
  if (process.env.COMPOSIO_API_KEY !== undefined)
748
856
  cfg.composio.apiKey = process.env.COMPOSIO_API_KEY;
857
+ // BOX_TOKEN keeps its historical name; the provider is Boat.
749
858
  cfg.box = { ...cfg.box };
750
859
  if (process.env.BOX_TOKEN !== undefined)
751
860
  cfg.box.token = process.env.BOX_TOKEN;
@@ -755,6 +864,11 @@ export function loadConfig() {
755
864
  cfg.tts = { ...cfg.tts };
756
865
  if (process.env.OMB_TTS_KEY !== undefined)
757
866
  cfg.tts.key = process.env.OMB_TTS_KEY;
867
+ // A preset ElevenLabs voice (Cloud Pro sets one) is only a default: a voice or
868
+ // another speech provider the person picked in Settings always wins.
869
+ const presetVoice = process.env.OMB_TTS_DEFAULT_VOICE?.trim();
870
+ if (presetVoice && !cfg.tts.voice?.trim() && (cfg.tts.provider ?? "elevenlabs") === "elevenlabs")
871
+ cfg.tts.voice = presetVoice;
758
872
  if (process.env.OMB_FISH_AUDIO_API_KEY !== undefined)
759
873
  cfg.tts.fishKey = process.env.OMB_FISH_AUDIO_API_KEY;
760
874
  cfg.imageGen = { ...cfg.imageGen };
@@ -923,7 +1037,7 @@ export function saveConfig(patch, options = {}) {
923
1037
  const storedProfiles = storedBrowserProfilesSchema.safeParse(disk.browserProfiles);
924
1038
  if (storedProfiles.success)
925
1039
  disk.browserProfiles = storedProfiles.data;
926
- 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"]) {
1040
+ for (const key of ["xai", "anthropic", "mistral", "openaiCompat", "composio", "box", "opencodeGo", "tts", "imageGen", "profile", "rooms", "threads", "context", "memory", "localVm", "features", "cloudOverflow", "budgets", "billing", "decisions", "onboarding", "browserEngine", "newBots"]) {
927
1041
  const section = checkedPatch[key];
928
1042
  if (!section)
929
1043
  continue;
@@ -953,6 +1067,9 @@ export function saveConfig(patch, options = {}) {
953
1067
  disk.customDomain = checkedPatch.customDomain;
954
1068
  if (checkedPatch.signIn !== undefined)
955
1069
  disk.signIn = checkedPatch.signIn;
1070
+ // Replace the section so clearing a backup cannot revive the old selection.
1071
+ if (checkedPatch.automaticRecovery !== undefined)
1072
+ disk.automaticRecovery = checkedPatch.automaticRecovery;
956
1073
  // A selection is replaced as one value, so changing engines also clears
957
1074
  // an effort level omitted from the new selection.
958
1075
  if (checkedPatch.defaultModelSelection !== undefined) {
@@ -1129,6 +1246,7 @@ function injectedEnvironment(cfg, driver) {
1129
1246
  environment.set("OPENAI_COMPAT_API_KEY", cfg.openaiCompat.key);
1130
1247
  if (driver === "openai-compat" && cfg.openaiCompat?.url)
1131
1248
  environment.set("OPENAI_COMPAT_URL", cfg.openaiCompat.url);
1249
+ // driverKind "boxAgent" and env BOX_TOKEN keep their historical names.
1132
1250
  if (driver === "boxAgent" && cfg.box?.token)
1133
1251
  environment.set("BOX_TOKEN", cfg.box.token);
1134
1252
  if (driver === "opencodeGo" && cfg.opencodeGo?.apiKey)
@@ -7,6 +7,7 @@
7
7
  // typing, screenshots, accessibility, or window discovery.
8
8
  import { execFile } from "node:child_process";
9
9
  import { createHash, randomBytes } from "node:crypto";
10
+ import { lstatSync } from "node:fs";
10
11
  import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
11
12
  import { tmpdir } from "node:os";
12
13
  import { join, resolve } from "node:path";
@@ -68,6 +69,28 @@ export function perBotLocalVmTarget(botId) {
68
69
  label: digest,
69
70
  };
70
71
  }
72
+ /** A pool-mode seat (issue #1654): one of N desktops shared by every
73
+ * conversation, addressed by seat index. Identities stay disjoint from the
74
+ * shared singleton and from per-bot digests, so discovery, labels, and lease
75
+ * lanes never confuse the three modes. */
76
+ export function poolLocalVmTarget(seat) {
77
+ return {
78
+ key: `pool:${seat}`,
79
+ containerName: `${CONTAINER}-p${seat}`,
80
+ workspaceDir: join(DATA_DIR, "vm-homes", `pool-${seat}`),
81
+ viewerPort: null,
82
+ label: `pool-${seat}`,
83
+ };
84
+ }
85
+ /** Only provisioning creates this durable directory; idle removal keeps it. */
86
+ export function localVmWorkspaceExists(target) {
87
+ try {
88
+ return lstatSync(target.workspaceDir, { throwIfNoEntry: false })?.isDirectory() === true;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
71
94
  const LINUX_WHEELS = {
72
95
  x86_64: {
73
96
  url: "https://files.pythonhosted.org/packages/fa/d7/a43008a328a40c85e7bc706fc20235b9abedc75e28b413817655153157ff/cua_driver-0.20.0-py3-none-manylinux_2_31_x86_64.whl",
@@ -12,6 +12,16 @@ export class ProviderError extends Error {
12
12
  this.code = code;
13
13
  }
14
14
  }
15
+ /** The driver proved this attempt never reached a prompt or client-side action,
16
+ * and its owned process has stopped. The harness may safely recover it. */
17
+ export class TurnNotStartedError extends Error {
18
+ turnId;
19
+ constructor(turnId, message) {
20
+ super(message);
21
+ this.name = "TurnNotStartedError";
22
+ this.turnId = turnId;
23
+ }
24
+ }
15
25
  /** Variants are opaque provider IDs, not the cross-engine effort enum. */
16
26
  export function isModelVariant(value) {
17
27
  return typeof value === "string" && value.length > 0 && value.length <= 256 &&
@@ -1,8 +1,8 @@
1
1
  // The proxy-side half of computer control. The harness keeps the record of
2
2
  // who is driving (server/computer-control.ts); the per-turn computer
3
3
  // processes consult it through this client before acting, because the
4
- // action paths themselves never traverse the harness — a box click goes
5
- // straight to the box's REST API, and a Local VM / VPS click rides a
4
+ // action paths themselves never traverse the harness — a boat click goes
5
+ // straight to the boat's REST API, and a Local VM / VPS click rides a
6
6
  // transparent stdio bridge into Cua Driver.
7
7
  //
8
8
  // Failure posture: CLOSED once configured. A missing/expired turn capability
@@ -259,8 +259,10 @@ export function pendingDelegationSnapshot() {
259
259
  })));
260
260
  }
261
261
  /** How many handoffs one turn may queue. Small on purpose: this is the only
262
- * thing standing between a confused bot and a fan-out of real turns. */
263
- const MAX_QUEUED_PER_THREAD = 4;
262
+ * thing standing between a confused bot and a fan-out of real turns. Six,
263
+ * not four: a lead running a weekly check-in over a five-member team is the
264
+ * ordinary case, and hitting the cap there silently dropped one teammate. */
265
+ const MAX_QUEUED_PER_THREAD = 6;
264
266
  /** Validate and enqueue a delegation. Pushes a "Delegated to @B: reason"
265
267
  * chip to the source thread so the user can see what was queued. */
266
268
  export function queueDelegation(bus, from, item, maxDepth, sourceThreadId = from.threadId) {
@@ -838,7 +840,13 @@ export function buildDelegationFailurePrompt(targetName, reason) {
838
840
  "Take over: tell the user what failed in plain terms, then decide the next step — retry with a narrower task, do the work yourself, or propose an alternative. Do not re-delegate the exact same task unchanged.",
839
841
  ].join("\n\n");
840
842
  }
841
- export const DELEGATION_WAKE_MAX_PER_WINDOW = 3;
843
+ // Must not be smaller than MAX_QUEUED_PER_THREAD: every delegate reply is
844
+ // one wake, so a fan-out that the queue cap allows has to be resumable
845
+ // within the window. Three failed a five-member weekly check-in whose
846
+ // replies all landed inside four minutes ("Delegation follow-up limit
847
+ // reached"), while the same routine passed a week earlier only because the
848
+ // replies happened to spread over twenty.
849
+ export const DELEGATION_WAKE_MAX_PER_WINDOW = 6;
842
850
  export const DELEGATION_WAKE_WINDOW_MS = 5 * 60 * 1000;
843
851
  /** Bounded auto-wake budget per source thread. A delegation completion
844
852
  * wakes the source; if that source re-delegates and the new completion