openmausbot 0.1.96 → 0.1.98

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 (185) hide show
  1. package/dist/assets/ActivityPanel-H6D3SD39.js +1 -0
  2. package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
  3. package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
  4. package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
  5. package/dist/assets/DesktopViewer-utONms1p.js +5 -0
  6. package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
  7. package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
  8. package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
  9. package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
  10. package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
  11. package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
  12. package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
  13. package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
  14. package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
  15. package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
  16. package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
  17. package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
  18. package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
  19. package/dist/assets/{abnfDiagram-O67JEVCF-Dkbd2hcj.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
  20. package/dist/assets/activity-BxOkayvD.js +1 -0
  21. package/dist/assets/{arc-Duj5Coy2.js → arc-CJNA94c6.js} +1 -1
  22. package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
  23. package/dist/assets/{blockDiagram-BEXU5L5S-CWDlTpXe.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
  24. package/dist/assets/brain-CFZ7PEXc.js +1 -0
  25. package/dist/assets/{c4Diagram-YGBWAQC7-B0t15PDc.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
  26. package/dist/assets/channel-WhFyujhZ.js +1 -0
  27. package/dist/assets/{chunk-2Q5K7J3B-BJSCL-HV.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
  28. package/dist/assets/{chunk-3FUC2YCW-D3_ILccd.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
  29. package/dist/assets/{chunk-5VM5RSS4-D3dmwT_S.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
  30. package/dist/assets/{chunk-GTNCS2PH-Br73HFPD.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
  31. package/dist/assets/{chunk-GWA4HPMP-uSsjxubC.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
  32. package/dist/assets/{chunk-JWPE2WC7-C40VWFyr.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
  33. package/dist/assets/{chunk-NETBCI7D-D6WNNYwU.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
  34. package/dist/assets/{chunk-WEXAMYUT-DOEhq64P.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
  35. package/dist/assets/{chunk-XXDRQBXY-BqJEi6Pi.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
  36. package/dist/assets/{classDiagram-v2-NBCMYWYE-ByU_GnxT.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
  37. package/dist/assets/computer-control-NLov352p.js +1 -0
  38. package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
  39. package/dist/assets/{cynefin-OW5HDTMX-CZWO7aYj.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
  40. package/dist/assets/{cynefinDiagram-VND7K2PF-C70-w0la.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
  41. package/dist/assets/{dagre-6A5THRUB-CnhjhExB.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
  42. package/dist/assets/{diagram-22UHCM2B-bnjWCNb4.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
  43. package/dist/assets/{diagram-3UASUU5V-A4uTWoFV.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
  44. package/dist/assets/{diagram-ATOU4E4O-Zi6m3NUl.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
  45. package/dist/assets/{diagram-CDSNMT55-DtCcmd5k.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
  46. package/dist/assets/{diagram-MLGK6HIB-DofuETS3.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
  47. package/dist/assets/{diagram-MPIPVDR6-Dw2T9PuU.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
  48. package/dist/assets/{ebnfDiagram-ZINNZB2B-DbrwPiem.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
  49. package/dist/assets/{elk-276RUBZZ-BvCt1dCb.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
  50. package/dist/assets/{erDiagram-OPXOYQCR-Y-QKqPnU.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
  51. package/dist/assets/{flowDiagram-KWPJA3E3-CDkL1B5w.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
  52. package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
  53. package/dist/assets/{gitGraphDiagram-X574FWY7-nL9iFTCv.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
  54. package/dist/assets/image-plus-BbVjGHin.js +1 -0
  55. package/dist/assets/index-BrPW5HDO.css +1 -0
  56. package/dist/assets/index-C3NqfdVU.js +585 -0
  57. package/dist/assets/{index-DreDnjOu.js → index-CJTp9JEp.js} +1 -1
  58. package/dist/assets/{infoDiagram-VRGFBTTK-C7aHjzsf.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
  59. package/dist/assets/{ishikawaDiagram-OU5B5YK6-KUZsNBTh.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
  60. package/dist/assets/{journeyDiagram-ZHPQQLJL-CGUuodFl.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
  61. package/dist/assets/{kanban-definition-PNTS6WVX-Cy6r868Z.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
  62. package/dist/assets/{linear-Bl669148.js → linear-BVVKbEMy.js} +1 -1
  63. package/dist/assets/{mermaid.core-86Xqz5Mr.js → mermaid.core-JJdoKyKB.js} +5 -5
  64. package/dist/assets/{mindmap-definition-NLK3R4M7-G4DzHLrd.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
  65. package/dist/assets/module-DCUsy2BQ.js +3 -0
  66. package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
  67. package/dist/assets/{pegDiagram-GJSIUBJH-K4eruzY0.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
  68. package/dist/assets/{pieDiagram-5QR66LMP-DYdR9PPq.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
  69. package/dist/assets/{quadrantDiagram-O4NWA36T-BCAh0nEy.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
  70. package/dist/assets/{railroadDiagram-XR7U4H2S-p0dGRc4g.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
  71. package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
  72. package/dist/assets/{requirementDiagram-PLB6GJNP-tLeBG0aL.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
  73. package/dist/assets/{sankeyDiagram-IPEJSGJF-QRuN16rV.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
  74. package/dist/assets/{sequenceDiagram-PO4LG4MO-ueT1nVoa.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
  75. package/dist/assets/{sizeCapture-INFHLROL-wRS2zWg2.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
  76. package/dist/assets/{stateDiagram-v2-GCMORJYK-CYzA_hE1.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
  77. package/dist/assets/{swimlanes-2SLR337P-kDnv6LMc.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
  78. package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
  79. package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
  80. package/dist/assets/{timeline-definition-EJHVYXUP-D1y2UbbH.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
  81. package/dist/assets/{usecaseDiagram-POWQR4AR-IqB4v9HX.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
  82. package/dist/assets/user-DcZBnAuv.js +1 -0
  83. package/dist/assets/{vennDiagram-UO4OBE2U-BEi7KYVF.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
  84. package/dist/assets/{wardleyDiagram-VNRHLVJA-Dkybo-Yf.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
  85. package/dist/assets/{xychartDiagram-PMCCYNJV-BAPFJLpg.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
  86. package/dist/index.html +2 -2
  87. package/dist-server/cloud-home-start.js +20 -9
  88. package/dist-server/container-mcp.js +8 -0
  89. package/dist-server/desktop-entry.mjs +54 -0
  90. package/dist-server/drivers/agents-proxy.js +26 -9
  91. package/dist-server/index.js +3734 -2360
  92. package/dist-server/openmausbot.js +1664 -1553
  93. package/dist-server/pair-cli.js +1664 -1553
  94. package/dist-server/permission-proxy.js +2 -4
  95. package/dist-server/server/atomic.js +70 -2
  96. package/dist-server/server/auto-approve.js +1 -1
  97. package/dist-server/server/auto-vm-claims.js +13 -1
  98. package/dist-server/server/automatic-recovery.js +1 -2
  99. package/dist-server/server/boat.js +68 -84
  100. package/dist-server/server/bot-overview.js +1 -1
  101. package/dist-server/server/browser-engine.js +38 -1
  102. package/dist-server/server/browser-live.js +112 -25
  103. package/dist-server/server/browser-runtime.js +31 -1
  104. package/dist-server/server/checkpoints.js +275 -44
  105. package/dist-server/server/cli-setup.js +3 -3
  106. package/dist-server/server/cloud-computer-tools.js +11 -4
  107. package/dist-server/server/cloud-home-start.js +4 -4
  108. package/dist-server/server/cloud-home.js +3 -9
  109. package/dist-server/server/cloud-owner.js +1 -1
  110. package/dist-server/server/computer-engine-removal.js +70 -0
  111. package/dist-server/server/computer-selection.js +18 -0
  112. package/dist-server/server/config.js +29 -12
  113. package/dist-server/server/connector-card-text.js +15 -0
  114. package/dist-server/server/connector-scope-hint.js +83 -0
  115. package/dist-server/server/default-model-selection.js +23 -14
  116. package/dist-server/server/delegations.js +11 -5
  117. package/dist-server/server/delta-context.js +6 -1
  118. package/dist-server/server/drivers/acp/core.js +11 -1
  119. package/dist-server/server/drivers/acp/grok.js +47 -5
  120. package/dist-server/server/drivers/acp/opencode-go.js +16 -21
  121. package/dist-server/server/drivers/agents-call.js +1 -1
  122. package/dist-server/server/drivers/agents-catalog.js +27 -7
  123. package/dist-server/server/drivers/agents-proxy.js +1 -0
  124. package/dist-server/server/drivers/builtIn.js +0 -2
  125. package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
  126. package/dist-server/server/drivers/claude-login-auth.js +1 -1
  127. package/dist-server/server/drivers/claude.js +84 -41
  128. package/dist-server/server/drivers/codex-device-auth.js +27 -319
  129. package/dist-server/server/drivers/codex.js +2 -2
  130. package/dist-server/server/drivers/device-auth.js +342 -0
  131. package/dist-server/server/drivers/openai-chat.js +110 -10
  132. package/dist-server/server/drivers/pi.js +184 -49
  133. package/dist-server/server/fleet-agent.js +18 -5
  134. package/dist-server/server/harness/http.js +5 -2
  135. package/dist-server/server/harness/registry.js +19 -0
  136. package/dist-server/server/harness-capabilities.js +7 -4
  137. package/dist-server/server/hosted-models.js +3 -1
  138. package/dist-server/server/index.js +823 -333
  139. package/dist-server/server/key-rejections.js +57 -0
  140. package/dist-server/server/live-call.js +11 -6
  141. package/dist-server/server/managed-policy.js +1 -1
  142. package/dist-server/server/message-file.js +7 -4
  143. package/dist-server/server/model-catalog/presets.js +8 -0
  144. package/dist-server/server/peer-roster.js +10 -2
  145. package/dist-server/server/permission-proxy.js +4 -0
  146. package/dist-server/server/provider-key-check.js +5 -1
  147. package/dist-server/server/request-auth.js +1 -1
  148. package/dist-server/server/room-turn-end.js +15 -0
  149. package/dist-server/server/room-turn-timeout.js +28 -0
  150. package/dist-server/server/routes/live.js +3 -3
  151. package/dist-server/server/routes/thread-models.js +36 -0
  152. package/dist-server/server/routine-requests.js +1 -1
  153. package/dist-server/server/routines.js +3 -3
  154. package/dist-server/server/screen-frame-gate.js +1 -3
  155. package/dist-server/server/skills.js +3 -2
  156. package/dist-server/server/store.js +273 -61
  157. package/dist-server/server/surface.js +29 -49
  158. package/dist-server/server/system-prompt.js +10 -10
  159. package/dist-server/server/team-backup.js +19 -4
  160. package/dist-server/server/thread-model.js +25 -0
  161. package/dist-server/server/turn-continuation.js +168 -0
  162. package/dist-server/server/webhook-ingress.js +8 -3
  163. package/dist-server/server/webhooks.js +40 -5
  164. package/dist-server/server/workspace.js +1 -1
  165. package/dist-server/shared/ask-question.js +0 -70
  166. package/dist-server/shared/cloud-computer.js +4 -6
  167. package/dist-server/shared/credential-request.js +1 -1
  168. package/dist-server/shared/device-sign-in.js +57 -0
  169. package/dist-server/shared/failed-turn.js +1 -4
  170. package/dist-server/shared/place-view.js +409 -0
  171. package/dist-server/shared/team-backup.js +2 -0
  172. package/dist-server/shared/thread-model.js +13 -0
  173. package/dist-server/shared/tool-scope-support.js +1 -2
  174. package/dist-server/vps-container-mcp.js +8 -0
  175. package/dist-server/workspace-backup.worker.js +41 -22
  176. package/package.json +1 -1
  177. package/dist/assets/DesktopViewer-DsnA23KM.js +0 -5
  178. package/dist/assets/architectureDiagram-NJMV4G6O-uYUsDawP.js +0 -36
  179. package/dist/assets/channel-BZxwC-Gw.js +0 -1
  180. package/dist/assets/cose-bilkent-JH36ORCC-D-Uglxqq.js +0 -1
  181. package/dist/assets/ganttDiagram-FUAMR5RP-CKV5D2F6.js +0 -292
  182. package/dist/assets/index-Du7o6Dsf.js +0 -610
  183. package/dist/assets/index-KxG4bWSr.css +0 -1
  184. package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +0 -8
  185. package/dist-server/server/drivers/boatagent.js +0 -443
@@ -14,7 +14,9 @@
14
14
  //
15
15
  // Failure policy: checkpointing is best-effort convenience, never load-bearing.
16
16
  // Any git failure disables the feature for that bot for the rest of the
17
- // session and logs — nothing here ever throws into the turn path.
17
+ // session and logs — nothing here ever throws into the turn path. A git call
18
+ // that hits the timeout disables only that bot+folder, so a slow folder costs
19
+ // one wait and one log line, not one wait per turn.
18
20
  //
19
21
  // Adapted from the checkpoint designs of Cline, Roo-Code, and Gemini CLI
20
22
  // (all Apache-2.0): the per-call GIT_DIR/GIT_WORK_TREE/GIT_CONFIG_* env
@@ -24,6 +26,7 @@
24
26
  import { execFile } from "node:child_process";
25
27
  import { createHash } from "node:crypto";
26
28
  import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
29
+ import { readdir } from "node:fs/promises";
27
30
  import { homedir } from "node:os";
28
31
  import { isAbsolute, join, parse, resolve } from "node:path";
29
32
  import { DATA_DIR } from "./config.js";
@@ -117,9 +120,20 @@ Thumbs.db
117
120
  `;
118
121
  // gpgsign off: the user's global config may demand signing, and a shadow
119
122
  // commit must never block on a passphrase prompt. Automatic GC stays off:
120
- // explicit GC runs inside the same serialization as each snapshot, after the
121
- // previous snapshot's objects are no longer needed.
123
+ // explicit GC runs in the same per-folder queue as snapshots and diffs, on
124
+ // the throttle below, after the objects it drops are no longer needed.
122
125
  const GITCONFIG = "[commit]\n\tgpgsign = false\n[core]\n\tautocrlf = false\n[gc]\n\tauto = 0\n";
126
+ /** Hard ceiling on one git call. A hung git (index lock, dead network
127
+ * filesystem, cloud placeholders being downloaded) would otherwise jam the
128
+ * per-repo queue for the whole session. */
129
+ const GIT_TIMEOUT_MS = 120_000;
130
+ // The snapshot is awaited before every turn's dispatch, so GC does not run on
131
+ // every snapshot: collect after this many HEAD-advancing snapshots of one
132
+ // folder, or sooner when loose objects (git's own gc.auto default) or new
133
+ // object data pile up. Counters are per process and reset on restart.
134
+ export const GC_EVERY_COMMITS = 25;
135
+ const GC_LOOSE_OBJECTS = 6_700;
136
+ const GC_NEW_DATA_KIB = 262_144;
123
137
  /** One failed git call disables checkpoints for that bot until restart —
124
138
  * a broken shadow repo must cost the user one log line, not a failed turn. */
125
139
  const disabledBots = new Set();
@@ -127,6 +141,30 @@ function disable(botId, message) {
127
141
  disabledBots.add(botId);
128
142
  console.warn(`workspace checkpoints disabled for bot ${botId} this session: ${message}`);
129
143
  }
144
+ /** A git call that hit GIT_TIMEOUT_MS (not a cancellation). The folder is
145
+ * too slow to snapshot — waiting that long again on every turn is worse than
146
+ * having no checkpoint, so only that bot+folder is switched off. */
147
+ class GitTimeoutError extends Error {
148
+ }
149
+ /** Bot+folder pairs (keyed by shadow dir) switched off after a timeout. */
150
+ const disabledFolders = new Set();
151
+ function disableFolder(shadow, botId, worktree, message) {
152
+ if (disabledFolders.has(shadow))
153
+ return;
154
+ disabledFolders.add(shadow);
155
+ console.warn(`workspace checkpoints disabled for bot ${botId} in ${worktree} this session: ${message}`);
156
+ }
157
+ /** The shadow dir doubles as the bot+folder key. Null when the folder cannot
158
+ * be resolved (the refusal check reports that case). */
159
+ function folderShadow(botId, cwd) {
160
+ try {
161
+ const worktree = realpathSync(resolve(cwd));
162
+ return { worktree, shadow: shadowDir(botId, worktree) };
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
130
168
  // probed once: either the system has a usable git or checkpoints stay off
131
169
  let gitProbe = null;
132
170
  function gitAvailable() {
@@ -198,16 +236,23 @@ function gitEnv(shadow, cwd) {
198
236
  env.GIT_COMMITTER_EMAIL = "checkpoint@openmausbot.local";
199
237
  return env;
200
238
  }
239
+ /** execFile reports its own timeout as a killed child; a cancellation via
240
+ * `signal` arrives as an AbortError instead and is not a timeout. */
241
+ function gitFailure(command, err, stderr, signal) {
242
+ if (err.killed && err.name !== "AbortError" && !signal?.aborted) {
243
+ return new GitTimeoutError(`git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} s`);
244
+ }
245
+ return new Error(`git ${command}: ${(stderr || err.message).trim().slice(0, 400)}`);
246
+ }
201
247
  /** Run one git command against the shadow repo. cwd is the WORK TREE — the
202
- * "." pathspec in add resolves relative to it. A hung git (index lock, dead
203
- * network filesystem) would otherwise jam the per-repo queue for the whole
204
- * session, so every call carries a hard timeout. */
248
+ * "." pathspec in add resolves relative to it. Every call carries the
249
+ * GIT_TIMEOUT_MS hard timeout. */
205
250
  function runGit(args, cwd, env, signal) {
206
251
  return new Promise((resolvePromise, rejectPromise) => {
207
252
  signal?.throwIfAborted();
208
- execFile("git", args, { cwd, env, signal, windowsHide: true, encoding: "utf8", timeout: 120_000, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
253
+ execFile("git", args, { cwd, env, signal, windowsHide: true, encoding: "utf8", timeout: GIT_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
209
254
  if (err)
210
- rejectPromise(new Error(`git ${args[0]}: ${(stderr || err.message).trim().slice(0, 400)}`));
255
+ rejectPromise(gitFailure(args[0], err, stderr, signal));
211
256
  else
212
257
  resolvePromise(stdout);
213
258
  });
@@ -220,13 +265,13 @@ function runGit(args, cwd, env, signal) {
220
265
  function hasStagedChanges(cwd, env, signal) {
221
266
  return new Promise((resolvePromise, rejectPromise) => {
222
267
  signal?.throwIfAborted();
223
- execFile("git", ["diff", "--cached", "--quiet", "--ignore-submodules=dirty"], { cwd, env, signal, windowsHide: true, encoding: "utf8", timeout: 120_000, maxBuffer: 16 * 1024 * 1024 }, (err, _stdout, stderr) => {
268
+ execFile("git", ["diff", "--cached", "--quiet", "--ignore-submodules=dirty"], { cwd, env, signal, windowsHide: true, encoding: "utf8", timeout: GIT_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 }, (err, _stdout, stderr) => {
224
269
  if (err === null)
225
270
  resolvePromise(false);
226
271
  else if (err.code === 1)
227
272
  resolvePromise(true);
228
273
  else
229
- rejectPromise(new Error(`git diff: ${(stderr || err.message).trim().slice(0, 400)}`));
274
+ rejectPromise(gitFailure("diff", err, stderr, signal));
230
275
  });
231
276
  });
232
277
  }
@@ -241,26 +286,30 @@ function serialize(key, fn) {
241
286
  chains.set(key, run.then(() => undefined, () => undefined));
242
287
  return run;
243
288
  }
244
- // A ref per running turn. Every snapshot parents only the empty base and the
245
- // cleanup after it prunes whatever HEAD no longer reaches — so when a bot's
246
- // threads work side by side in one folder, a sibling's snapshot would discard
247
- // the pre-turn commit a still-running turn diffs against at its end. A pin
248
- // keeps that commit reachable until release(). The pin's name is hashed so
249
- // any token is a legal ref name.
289
+ // A ref per running turn. Every snapshot parents only the empty base and a
290
+ // collection prunes whatever HEAD no longer reaches — so when a bot's threads
291
+ // work side by side in one folder, a collection after a sibling's snapshot
292
+ // would discard the pre-turn commit a still-running turn diffs against at its
293
+ // end. A pin keeps that commit reachable until release(). The pin's name is
294
+ // hashed so any token is a legal ref name.
250
295
  const LIVE_REFS = "refs/omb-live/";
251
296
  function liveRef(pin) {
252
297
  return LIVE_REFS + createHash("sha256").update(pin).digest("hex").slice(0, 16);
253
298
  }
254
299
  // Pins outlive the process that wrote them (a crash mid-turn), so the first
255
300
  // use of each shadow in this process drops every pin left behind: no turn of
256
- // ours can be live in a shadow this process has not touched yet.
301
+ // ours can be live in a shadow this process has not touched yet. Dropping one
302
+ // makes the next snapshot's collection due (see collectionDue).
257
303
  const sweptShadows = new Set();
304
+ const droppedPins = new Set();
258
305
  async function sweepLiveRefs(cwd, env, shadow, signal) {
259
306
  if (sweptShadows.has(shadow))
260
307
  return;
261
308
  const stale = (await runGit(["for-each-ref", "--format=%(refname)", LIVE_REFS], cwd, env, signal)).split("\n").filter(Boolean);
262
309
  for (const ref of stale)
263
310
  await runGit(["update-ref", "-d", ref], cwd, env, signal);
311
+ if (stale.length > 0)
312
+ droppedPins.add(shadow);
264
313
  sweptShadows.add(shadow);
265
314
  }
266
315
  /** Create the shadow repo on first use; self-heal its config files on every
@@ -309,32 +358,190 @@ async function ensureShadow(cwd, env, shadow, signal) {
309
358
  }
310
359
  await sweepLiveRefs(cwd, env, shadow, signal);
311
360
  }
361
+ /** Folder listings in flight at once (libuv's pool has four threads). */
362
+ const LISTING_CONCURRENCY = 4;
363
+ /** Case-insensitive file systems only (APFS/HFS+ as macOS formats them,
364
+ * NTFS): does a staged path, or one of its folders, now exist on disk under a
365
+ * different case? git init sets core.ignorecase there, so an incremental
366
+ * `add -A` matches `R0` to the staged `r0` and keeps the old spelling
367
+ * forever. Reads folder listings only — each folder holding a staged path
368
+ * once, asynchronously, so a big tree never stalls the event loop — never
369
+ * file contents. Names compare in NFC: git stores precomposed paths on macOS,
370
+ * readdir returns names as they were created. */
371
+ async function caseRenamed(cwd, paths) {
372
+ if (paths.length === 0)
373
+ return false;
374
+ const pending = new Set([""]);
375
+ for (const path of paths) {
376
+ for (let slash = path.indexOf("/"); slash > 0; slash = path.indexOf("/", slash + 1))
377
+ pending.add(path.slice(0, slash));
378
+ }
379
+ const listings = new Map();
380
+ const queue = [...pending];
381
+ const read = async () => {
382
+ for (let dir = queue.pop(); dir !== undefined; dir = queue.pop()) {
383
+ try {
384
+ const exact = new Set((await readdir(join(cwd, dir))).map((name) => name.normalize("NFC")));
385
+ listings.set(dir, { exact, folded: new Set([...exact].map((name) => name.toLowerCase())) });
386
+ }
387
+ catch {
388
+ listings.set(dir, null); // gone or unreadable: add -A handles deletions itself
389
+ }
390
+ }
391
+ };
392
+ await Promise.all(Array.from({ length: Math.min(LISTING_CONCURRENCY, queue.length) }, read));
393
+ const folders = new Map();
394
+ const renamed = (path, isFolder) => {
395
+ const known = isFolder ? folders.get(path) : undefined;
396
+ if (known !== undefined)
397
+ return known;
398
+ const slash = path.lastIndexOf("/");
399
+ const parent = slash < 0 ? "" : path.slice(0, slash);
400
+ const name = path.slice(slash + 1).normalize("NFC");
401
+ const names = listings.get(parent) ?? null;
402
+ let verdict;
403
+ if (names === null)
404
+ verdict = false;
405
+ else if (!names.exact.has(name))
406
+ verdict = names.folded.has(name.toLowerCase()); // else simply deleted
407
+ else
408
+ verdict = parent !== "" && renamed(parent, true);
409
+ if (isFolder)
410
+ folders.set(path, verdict);
411
+ return verdict;
412
+ };
413
+ return paths.some((path) => renamed(path, false));
414
+ }
415
+ /** Would an incremental `git add -A` leave the shadow index different from
416
+ * one rebuilt from empty? Only in four cases, all detected here: a tracked
417
+ * path is now ignored or inside a cache-tagged directory (add -A never drops
418
+ * tracked files), a folder holding tracked files became a nested repository
419
+ * (add -A keeps the files instead of a gitlink), a recorded nested
420
+ * repository lost its .git (add -A keeps the stale gitlink), or — on a
421
+ * case-insensitive file system — a tracked path was renamed by case only
422
+ * (add -A keeps the old spelling). */
423
+ async function indexNeedsRebuild(cwd, env, signal) {
424
+ // If the index cannot be inspected (say, a listing past maxBuffer on a huge
425
+ // folder), rebuild: that is always correct, just slower.
426
+ const list = (args) => runGit(args, cwd, env, signal).catch((e) => {
427
+ if (e instanceof GitTimeoutError || signal?.aborted)
428
+ throw e;
429
+ return null;
430
+ });
431
+ const ignored = await list(["ls-files", "--cached", "--ignored", "--exclude-standard", "-z"]);
432
+ if (ignored === null || ignored.length > 0)
433
+ return true;
434
+ const staged = await list(["ls-files", "--stage", "-z"]);
435
+ if (staged === null)
436
+ return true;
437
+ const paths = [];
438
+ const parents = new Set();
439
+ for (const entry of staged.split("\0")) {
440
+ const tab = entry.indexOf("\t");
441
+ if (tab < 0)
442
+ continue;
443
+ const path = entry.slice(tab + 1);
444
+ paths.push(path);
445
+ if (entry.startsWith("160000 ")) {
446
+ if (!existsSync(join(cwd, path, ".git")))
447
+ return true;
448
+ continue;
449
+ }
450
+ for (let slash = path.indexOf("/"); slash > 0; slash = path.indexOf("/", slash + 1))
451
+ parents.add(path.slice(0, slash));
452
+ }
453
+ for (const dir of parents)
454
+ if (existsSync(join(cwd, dir, ".git")))
455
+ return true;
456
+ return (process.platform === "darwin" || process.platform === "win32") && (await caseRenamed(cwd, paths));
457
+ }
458
+ /** Stage the work tree into the shadow index. The index persists between
459
+ * snapshots so `git add -A` only re-reads files whose stat data changed;
460
+ * rebuilding it from empty re-reads and re-hashes every file (and downloads
461
+ * every cloud placeholder). It is rebuilt only when indexNeedsRebuild says an
462
+ * incremental add would differ, so the staged content always matches a
463
+ * from-empty rebuild. Only the shadow index changes; no user index or
464
+ * work-tree file does. */
465
+ async function stageWorkTree(cwd, env, signal) {
466
+ if (await indexNeedsRebuild(cwd, env, signal))
467
+ await runGit(["read-tree", "--empty"], cwd, env, signal);
468
+ await runGit(["add", "-A", "--ignore-errors", "."], cwd, env, signal);
469
+ }
312
470
  /** Capture fully before advancing HEAD: null when the add was partial, so a
313
471
  * partial capture never replaces the last usable checkpoint or authorizes
314
472
  * removal of its objects. */
315
473
  async function commitAll(cwd, env, label, signal) {
316
474
  const previous = (await runGit(["rev-parse", "HEAD"], cwd, env, signal)).trim();
317
475
  try {
318
- // Rebuild only the shadow index so newly ignored/cache-tagged files stop
319
- // pinning old blobs; no user index or work-tree file is changed.
320
- await runGit(["read-tree", "--empty"], cwd, env, signal);
321
- await runGit(["add", "-A", "--ignore-errors", "."], cwd, env, signal);
476
+ await stageWorkTree(cwd, env, signal);
322
477
  }
323
- catch {
478
+ catch (e) {
479
+ // A timeout is not an unreadable file: the caller switches the folder off.
480
+ if (e instanceof GitTimeoutError)
481
+ throw e;
324
482
  return null;
325
483
  }
326
484
  const changed = await hasStagedChanges(cwd, env, signal);
327
485
  const base = (await runGit(["rev-list", "--max-parents=0", "HEAD"], cwd, env, signal)).trim();
328
486
  const count = (await runGit(["rev-list", "--count", "HEAD"], cwd, env, signal)).trim();
329
487
  if (!changed && Number(count) <= 2)
330
- return previous;
488
+ return { hash: previous, advanced: false, compacted: false };
331
489
  const tree = (await runGit(["write-tree"], cwd, env, signal)).trim();
332
490
  const message = changed ? label : (await runGit(["show", "-s", "--format=%B", "HEAD"], cwd, env, signal)).trim();
333
491
  // Parent only the empty marker, never the previous snapshot: keeping the
334
492
  // previous commit as an ancestor would retain every old tree indefinitely.
335
493
  const hash = (await runGit(["commit-tree", tree, "-p", base, "-m", message], cwd, env, signal)).trim();
336
494
  await runGit(["update-ref", "HEAD", hash, previous], cwd, env, signal);
337
- return hash;
495
+ return { hash, advanced: true, compacted: Number(count) > 2 };
496
+ }
497
+ /** HEAD-advancing snapshots per shadow since this process last collected. */
498
+ const commitsSinceGc = new Map();
499
+ /** Pack size (KiB) first counted after the last collection — the live
500
+ * snapshot. Packs also grow between collections: `git add` streams files over
501
+ * core.bigFileThreshold straight into new packs. */
502
+ const packBaselineKib = new Map();
503
+ /** A collection the throttle asked for, held until that turn's settled diff
504
+ * has been queued, or — when the turn settled without one — until the next
505
+ * snapshot, which runs it first (see snapshot and diffWorkingTree). */
506
+ const pendingCollections = new Map();
507
+ /** Queue the folder's pending collection behind whatever was just queued for
508
+ * it. Nothing awaits it. */
509
+ function releaseCollection(shadow) {
510
+ const collect = pendingCollections.get(shadow);
511
+ if (collect === undefined)
512
+ return;
513
+ pendingCollections.delete(shadow);
514
+ void serialize(shadow, collect);
515
+ }
516
+ /** Throttle for collectObsolete (see GC_EVERY_COMMITS). */
517
+ async function collectionDue(shadow, result, cwd, env, signal) {
518
+ // A compaction or a swept leftover pin frees a whole tree at once: reclaim it now.
519
+ const freed = droppedPins.delete(shadow) || result.compacted;
520
+ if (!result.advanced && !freed)
521
+ return false;
522
+ const commits = (commitsSinceGc.get(shadow) ?? 0) + (result.advanced ? 1 : 0);
523
+ commitsSinceGc.set(shadow, commits);
524
+ let due = freed || commits >= GC_EVERY_COMMITS;
525
+ if (!due) {
526
+ // Advisory only: a failed count must not cost the snapshot just taken.
527
+ const counted = await runGit(["count-objects", "-v"], cwd, env, signal).catch(() => "");
528
+ if (counted !== "") {
529
+ const field = (name) => Number(new RegExp(`^${name}: (\\d+)$`, "m").exec(counted)?.[1] ?? 0);
530
+ const pack = field("size-pack");
531
+ if (!packBaselineKib.has(shadow))
532
+ packBaselineKib.set(shadow, pack);
533
+ // Every loose object is new since the last collection (gc packs or
534
+ // prunes them all). Only pack growth counts, so a folder whose live
535
+ // snapshot alone passes the ceiling does not collect every turn.
536
+ const newData = field("size") + Math.max(0, pack - packBaselineKib.get(shadow));
537
+ due = field("count") >= GC_LOOSE_OBJECTS || newData >= GC_NEW_DATA_KIB;
538
+ }
539
+ }
540
+ if (due) {
541
+ commitsSinceGc.set(shadow, 0);
542
+ packBaselineKib.delete(shadow);
543
+ }
544
+ return due;
338
545
  }
339
546
  async function collectObsolete(cwd, env) {
340
547
  // No caller may still need an unpinned previous tree: HEAD stays reachable,
@@ -349,8 +556,8 @@ async function collectObsolete(cwd, env) {
349
556
  }
350
557
  }
351
558
  /** Snapshot the folder. Returns the checkpoint hash, or null when the
352
- * feature is off for this bot, git is missing, the folder is refused, or a
353
- * file could not be read.
559
+ * feature is off for this bot or folder, git is missing, the folder is
560
+ * refused, or a file could not be read.
354
561
  * With `pin`, the returned commit stays reachable through later snapshots of
355
562
  * the same folder until release(pin) — for a turn that will diff against it
356
563
  * when it ends. Never throws — this is called fire-and-forget on the turn path. */
@@ -363,30 +570,47 @@ export async function snapshot(botId, cwd, label, signal, opts) {
363
570
  return null;
364
571
  if (refusalReason(cwd) !== null)
365
572
  return null;
573
+ const folder = folderShadow(botId, cwd);
574
+ if (folder === null || disabledFolders.has(folder.shadow))
575
+ return null;
576
+ const { worktree, shadow } = folder;
366
577
  try {
367
- const worktree = realpathSync(resolve(cwd));
368
- const shadow = shadowDir(botId, worktree);
578
+ // Still pending from the previous snapshot: that turn settled without a
579
+ // diff. Collect ahead of this snapshot, not behind it, so this turn's own
580
+ // diff never queues behind GC and the object counts below see the
581
+ // collected repo (a stale count would make GC due again at once).
582
+ releaseCollection(shadow);
369
583
  return await serialize(shadow, async () => {
370
584
  const env = gitEnv(shadow, worktree);
371
585
  await ensureShadow(worktree, env, shadow, signal);
372
- const hash = await commitAll(worktree, env, label, signal);
373
- if (!hash)
586
+ const result = await commitAll(worktree, env, label, signal);
587
+ if (!result)
374
588
  return null;
375
589
  if (opts?.pin)
376
- await runGit(["update-ref", liveRef(opts.pin), hash], worktree, env, signal);
377
- await collectObsolete(worktree, env);
378
- return hash;
590
+ await runGit(["update-ref", liveRef(opts.pin), result.hash], worktree, env, signal);
591
+ // Never awaited by the turn waiting on this snapshot, and held for that
592
+ // turn's settled diff: diffWorkingTree queues it behind itself, so GC
593
+ // never eats into the digest's capture window.
594
+ if (await collectionDue(shadow, result, worktree, env, signal)) {
595
+ pendingCollections.set(shadow, () => collectObsolete(worktree, env));
596
+ }
597
+ return result.hash;
379
598
  });
380
599
  }
381
600
  catch (e) {
382
- if (!signal?.aborted)
383
- disable(botId, e instanceof Error ? e.message : String(e));
601
+ if (signal?.aborted)
602
+ return null;
603
+ const message = e instanceof Error ? e.message : String(e);
604
+ if (e instanceof GitTimeoutError)
605
+ disableFolder(shadow, botId, worktree, message);
606
+ else
607
+ disable(botId, message);
384
608
  return null;
385
609
  }
386
610
  }
387
- /** Let go of a pinned pre-turn commit: the next snapshot's cleanup may
388
- * reclaim it. Queued behind the shadow's pending operations, so a digest's
389
- * diff already in that queue still finds its commit. Best-effort like the
611
+ /** Let go of a pinned pre-turn commit: the next collection may reclaim it.
612
+ * Queued behind the shadow's pending operations, so a digest's diff already
613
+ * in that queue still finds its commit. Best-effort like the
390
614
  * rest of this module — a pin that cannot be dropped costs one log line and
391
615
  * is swept on the next start. */
392
616
  export async function release(botId, cwd, pin) {
@@ -411,14 +635,15 @@ export async function diffWorkingTree(botId, cwd, fromHash, signal) {
411
635
  return null;
412
636
  if (disabledBots.has(botId) || !(await gitAvailable()) || refusalReason(cwd) !== null)
413
637
  return null;
638
+ const folder = folderShadow(botId, cwd);
639
+ if (folder === null || disabledFolders.has(folder.shadow))
640
+ return null;
641
+ const { worktree, shadow } = folder;
414
642
  try {
415
- const worktree = realpathSync(resolve(cwd));
416
- const shadow = shadowDir(botId, worktree);
417
643
  const env = gitEnv(shadow, worktree);
418
- return await serialize(shadow, async () => {
644
+ const run = serialize(shadow, async () => {
419
645
  await ensureShadow(worktree, env, shadow, signal);
420
- await runGit(["read-tree", "--empty"], worktree, env, signal);
421
- await runGit(["add", "-A", "--ignore-errors", "."], worktree, env, signal);
646
+ await stageWorkTree(worktree, env, signal);
422
647
  const out = await runGit(["diff", "--cached", "--name-status", "--no-renames", "-z", fromHash], worktree, env, signal);
423
648
  const diff = { changed: [], added: [], deleted: [] };
424
649
  const fields = out.split("\0").filter((field) => field.length > 0);
@@ -434,8 +659,14 @@ export async function diffWorkingTree(botId, cwd, fromHash, signal) {
434
659
  }
435
660
  return diff;
436
661
  });
662
+ // The collection this turn's snapshot left pending runs behind this
663
+ // diff, never ahead of it.
664
+ releaseCollection(shadow);
665
+ return await run;
437
666
  }
438
- catch {
667
+ catch (e) {
668
+ if (e instanceof GitTimeoutError && !signal?.aborted)
669
+ disableFolder(shadow, botId, worktree, e.message);
439
670
  return null;
440
671
  }
441
672
  }
@@ -118,7 +118,7 @@ const NATIVE = [
118
118
  async function connectNative(choice, id, entry, io, deps) {
119
119
  const cli = typeof rawObject(entry.config).cli === "string" ? rawObject(entry.config).cli : choice.cli;
120
120
  io.log("Connecting your account…");
121
- io.log("Sign-in stays with your provider. Its account limits apply; OMB never asks for your password.");
121
+ io.log("Sign-in stays with your provider. Its account limits apply; OpenMausBot never asks for your password.");
122
122
  let state = await deps.inspect(id, entry);
123
123
  if (state.snapshot.state !== "available") {
124
124
  if (cli !== choice.cli)
@@ -143,7 +143,7 @@ async function connectNative(choice, id, entry, io, deps) {
143
143
  await deps.runCli(cli, args, entry.environment);
144
144
  state = await deps.inspect(id, entry);
145
145
  if (state.snapshot.state !== "available" || !state.snapshot.authenticated) {
146
- throw new Error("Sign-in was not confirmed. Your OMB settings are unchanged; complete provider sign-in and try again.");
146
+ throw new Error("Sign-in was not confirmed. Your OpenMausBot settings are unchanged; complete provider sign-in and try again.");
147
147
  }
148
148
  }
149
149
  else {
@@ -294,7 +294,7 @@ export async function runSetup(options, io = defaultSetupIo(), deps = dependenci
294
294
  catch (error) {
295
295
  if (!(error instanceof SetupCancelled))
296
296
  throw error;
297
- io.log("\nSetup cancelled. No OMB settings were changed. Provider sign-ins or installs already completed are kept.");
297
+ io.log("\nSetup cancelled. No OpenMausBot settings were changed. Provider sign-ins or installs already completed are kept.");
298
298
  return false;
299
299
  }
300
300
  finally {
@@ -15,6 +15,8 @@ export const CLOUD_COMPUTER_TOOLS = [
15
15
  tool("open_url", "Request opening an HTTP or HTTPS URL in the cloud desktop browser; inspect the screen to confirm it loaded.", { url: { type: "string", pattern: "^https?://", maxLength: 2000 } }, ["url"]),
16
16
  tool("exec", "Run a shell command on the assigned cloud computer.", { command: { type: "string", minLength: 1, maxLength: 4000 } }, ["command"]),
17
17
  ];
18
+ /** A call the gate let through while no Boat had landed for the turn. */
19
+ export const CLOUD_COMPUTER_NOT_READY = "The cloud computer is not ready. This call was not performed. Take a fresh screenshot in a moment.";
18
20
  const quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
19
21
  const buttons = { left: 1, middle: 2, right: 3 };
20
22
  const scrollButtons = { up: 4, down: 5, left: 6, right: 7 };
@@ -85,9 +87,11 @@ export async function runCloudComputerTool(cfg, boxId, name, args, signal) {
85
87
  }
86
88
  /** One JSON-RPC request from harness-mcp-proxy, answered with its MCP
87
89
  * `result`. The order is the safety argument: arguments are checked before
88
- * the control gate (which may claim the seat), a person's control refuses
89
- * the call before anything reaches the Boat, and the turn's capability must
90
- * still be live before and after the Boat acts. */
90
+ * the control gate (which may claim the seat, and for a cloud computer
91
+ * mounted before it existed, create or wake it), a person's control refuses
92
+ * the call before anything reaches the Boat, the Boat is read only once the
93
+ * gate has let the call through, and the turn's capability must still be
94
+ * live before and after the Boat acts. Listing the tools touches no Boat. */
91
95
  export async function cloudComputerRpc(request, deps) {
92
96
  if (request?.method === "tools/list") {
93
97
  deps.assertActive();
@@ -104,7 +108,10 @@ export async function cloudComputerRpc(request, deps) {
104
108
  deps.assertActive();
105
109
  if (control.held)
106
110
  return { isError: true, content: [{ type: "text", text: control.blockedReason || CONTROL_REFUSAL_PLAIN }] };
107
- const result = await runCloudComputerTool(deps.cfg, deps.boxId, params.name, args, deps.signal);
111
+ const boxId = deps.boxId();
112
+ if (!boxId)
113
+ return { isError: true, content: [{ type: "text", text: CLOUD_COMPUTER_NOT_READY }] };
114
+ const result = await runCloudComputerTool(deps.cfg, boxId, params.name, args, deps.signal);
108
115
  deps.assertActive();
109
116
  return result;
110
117
  }
@@ -1,4 +1,4 @@
1
- // Entry point of the OMB Cloud Pro home image (deploy/fly/Dockerfile).
1
+ // Entry point of the OMB Cloud Pro home image (the Dockerfile's cloud-home target).
2
2
  //
3
3
  // Starts as root, hands a fresh Fly volume (mounted root-owned at /data) to
4
4
  // the unprivileged `maus` user, and stays a small root supervisor of two
@@ -77,7 +77,7 @@ export function cloudHomeChildEnvironments(config, env, home) {
77
77
  /** Why the root supervisor must not run or trust `files`, or null: each
78
78
  * must be root's and not writable by anyone else (nor any folder above it),
79
79
  * and none may live on the volume `home`, which `maus` owns. The image makes its code
80
- * root's (deploy/fly/Dockerfile); a file `maus` could rewrite would run as
80
+ * root's (the Dockerfile's cloud-home target); a file `maus` could rewrite would run as
81
81
  * root at the next start, or be handed the secrets. */
82
82
  export function codeTrustProblem(files, home, stat = statSync) {
83
83
  const volume = posix.join(home, "/");
@@ -117,7 +117,7 @@ export function startCloudHome(env = process.env) {
117
117
  process.umask(0o077);
118
118
  const config = cloudHomeConfiguration(env);
119
119
  if (!config)
120
- throw new Error("This image runs an OMB Cloud home machine; set its boot contract (docs/cloud-pro.md).");
120
+ throw new Error("This image runs a My Cloud machine for OpenMausBot Cloud; set its boot contract (docs/cloud-pro.md).");
121
121
  // Logged here once: the server child never sees what they are about.
122
122
  for (const warning of config.warnings)
123
123
  console.warn(`cloud home: ${warning}`);
@@ -155,7 +155,7 @@ export function startCloudHome(env = process.env) {
155
155
  if (ids) {
156
156
  const problem = codeTrustProblem([process.execPath, fileURLToPath(import.meta.url), join(here, "index.js"), edgeBin, edgeConfig], home);
157
157
  if (problem)
158
- throw new Error(`This image's code is not safe to run as root: ${problem}. Rebuild it from deploy/fly/Dockerfile.`);
158
+ throw new Error(`This image's code is not safe to run as root: ${problem}. Rebuild it with docker build --target cloud-home.`);
159
159
  }
160
160
  const { server, edge, secrets, dropped } = cloudHomeChildEnvironments(config, env, home);
161
161
  // Names only, never values: what the server does not get from here.
@@ -109,17 +109,11 @@ export { cloudHomeOffersPlace } from "../shared/cloud-home.js";
109
109
  * place it offers. A turn's error shows 160 characters, so each fits. */
110
110
  export function cloudHomePlaceRefusal(place) {
111
111
  if (place === "local")
112
- return "This computer isn't a place on your OMB Cloud: its bots run in the cloud. Set Works on to Auto, Cloud or Browser, or lend your Mac under Settings → OMB Cloud.";
112
+ return "This computer isn't a place on My Cloud. Set Works on to Auto, Cloud computer or Browser, or lend your Mac under Settings → OpenMausBot Cloud.";
113
113
  if (place === "vm")
114
- return "Bots on your OMB Cloud can't use a Local VM: the cloud machine has no container runtime. Set Works on to Auto, Cloud or Browser.";
114
+ return "Bots on My Cloud can't use a Local VM. Set Works on to Auto, Cloud computer or Browser.";
115
115
  return undefined;
116
116
  }
117
- /** What a turn is told when Cloud is chosen but no Boat account is set up (no
118
- * key of the person's and no included Boat). A Cloud home has no Local VM to
119
- * suggest instead. */
120
- export function boatNotConfiguredMessage(cloudHome) {
121
- return `Cloud Boat is not configured — add a Boat API key or choose ${cloudHome ? "Browser" : "Local VM"}`;
122
- }
123
117
  /** The Cloud's setup checklist (docs/cloud-pro.md) has a "try something"
124
118
  * step that is done once a bot's turn finishes on the machine itself. The
125
119
  * server records when, once, in this Cloud's own onboarding record: that
@@ -198,7 +192,7 @@ export function createCloudPairing(options) {
198
192
  const ttl = Math.min(ttlSeconds ?? CLOUD_PAIRING_DEFAULT_TTL_S, browser ? CLOUD_BROWSER_SIGN_IN_MAX_TTL_S : CLOUD_PAIRING_MAX_TTL_S);
199
193
  const opened = sessions.openPairing({
200
194
  scopes: ["admin", "client"],
201
- label: typeof label === "string" && label.trim() ? label.trim() : "OMB Cloud",
195
+ label: typeof label === "string" && label.trim() ? label.trim() : "OpenMausBot Cloud",
202
196
  ttlMs: ttl * 1000,
203
197
  browser,
204
198
  ...(browser ? { owner: owner } : {}),
@@ -29,7 +29,7 @@
29
29
  import { lstatSync, readFileSync } from "node:fs";
30
30
  import { z } from "zod";
31
31
  import { writeFileAtomic } from "./atomic.js";
32
- export const CLOUD_PERSONAL_REFUSAL = "OMB Cloud is personal: only your own devices can connect.";
32
+ export const CLOUD_PERSONAL_REFUSAL = "My Cloud is personal: only your own devices can connect.";
33
33
  const KEY = /^p_[\w-]{22}$/;
34
34
  const keys = z.array(z.string().regex(KEY)).max(100_000);
35
35
  const recordFile = z.object({