openmausbot 0.1.85 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/assets/abnfDiagram-O67JEVCF-BTiuL79F.js +1 -0
  62. package/dist/assets/arc-C88Blc2v.js +1 -0
  63. package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
  64. package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
  65. package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
  66. package/dist/assets/channel-taCNRlUU.js +1 -0
  67. package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
  68. package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
  69. package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
  70. package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
  71. package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
  72. package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
  73. package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
  74. package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
  75. package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
  76. package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
  77. package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
  78. package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
  79. package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
  80. package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
  81. package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
  82. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  83. package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
  84. package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
  85. package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
  86. package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
  87. package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
  88. package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
  89. package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
  90. package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
  91. package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
  92. package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
  93. package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
  94. package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
  95. package/dist/assets/graph-D8UmR1vK.js +1 -0
  96. package/dist/assets/index-BwdMAQ6p.css +1 -0
  97. package/dist/assets/index-ByJennr7.js +581 -0
  98. package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
  99. package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
  100. package/dist/assets/init-Gi6I4Gst.js +1 -0
  101. package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
  102. package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
  103. package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
  104. package/dist/assets/linear-DBJGPmjT.js +1 -0
  105. package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
  106. package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
  107. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  108. package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
  109. package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
  110. package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
  111. package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
  112. package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
  113. package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
  114. package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
  115. package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
  116. package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
  117. package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
  118. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  119. package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
  120. package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
  121. package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
  122. package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
  123. package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
  124. package/dist/index.html +2 -2
  125. package/dist-server/browser-proxy.js +2 -1
  126. package/dist-server/container-mcp.js +1047 -173
  127. package/dist-server/drivers/agents-proxy.js +16121 -1504
  128. package/dist-server/drivers/phone-proxy.js +33 -2
  129. package/dist-server/enterprise/LICENSE +32 -0
  130. package/dist-server/enterprise/server/index.js +5 -5
  131. package/dist-server/index.js +16134 -7446
  132. package/dist-server/local-computer-proxy.js +3 -3
  133. package/dist-server/local-computer.js +53 -17
  134. package/dist-server/mcp-server.js +3 -1
  135. package/dist-server/openmausbot.js +9353 -6124
  136. package/dist-server/pair-cli.js +9353 -6124
  137. package/dist-server/permission-proxy.js +29 -9
  138. package/dist-server/server/admin-activity.js +410 -0
  139. package/dist-server/server/attachments.js +55 -0
  140. package/dist-server/server/auto-approve.js +4 -3
  141. package/dist-server/server/auto-vm-claims.js +1 -0
  142. package/dist-server/server/bot-package.js +18 -292
  143. package/dist-server/server/bot-profile.js +5 -17
  144. package/dist-server/server/bot-visibility.js +462 -0
  145. package/dist-server/server/box-create-idempotency.js +3 -3
  146. package/dist-server/server/box.js +59 -56
  147. package/dist-server/server/browser-proxy.js +3 -2
  148. package/dist-server/server/browser-runtime.js +38 -2
  149. package/dist-server/server/channel-queue.js +2 -0
  150. package/dist-server/server/cli-prompts.js +24 -2
  151. package/dist-server/server/cli.js +81 -12
  152. package/dist-server/server/command-allowlist.js +154 -0
  153. package/dist-server/server/composio.js +49 -11
  154. package/dist-server/server/computer-wait.js +22 -9
  155. package/dist-server/server/config.js +133 -17
  156. package/dist-server/server/connector-verdict.js +141 -0
  157. package/dist-server/server/container-computer.js +1 -1
  158. package/dist-server/server/decision-log.js +209 -48
  159. package/dist-server/server/default-model-selection.js +29 -3
  160. package/dist-server/server/delegations.js +97 -44
  161. package/dist-server/server/drivers/acp/core.js +163 -33
  162. package/dist-server/server/drivers/acp/grok.js +4 -3
  163. package/dist-server/server/drivers/agents-call.js +1162 -0
  164. package/dist-server/server/drivers/agents-catalog.js +689 -0
  165. package/dist-server/server/drivers/agents-client.js +28 -0
  166. package/dist-server/server/drivers/agents-proxy.js +18 -1722
  167. package/dist-server/server/drivers/boxagent.js +174 -36
  168. package/dist-server/server/drivers/builtIn.js +2 -0
  169. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  170. package/dist-server/server/drivers/chat-images.js +54 -0
  171. package/dist-server/server/drivers/chat-mcp-tools.js +55 -10
  172. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  173. package/dist-server/server/drivers/claude.js +28 -8
  174. package/dist-server/server/drivers/codex-instructions.js +48 -6
  175. package/dist-server/server/drivers/codex-mcp-names.js +44 -0
  176. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  177. package/dist-server/server/drivers/codex.js +121 -34
  178. package/dist-server/server/drivers/grok.js +2 -1
  179. package/dist-server/server/drivers/mistral.js +115 -0
  180. package/dist-server/server/drivers/openai-chat.js +155 -32
  181. package/dist-server/server/drivers/openai-compat.js +16 -8
  182. package/dist-server/server/drivers/permission-command.js +35 -0
  183. package/dist-server/server/drivers/phone-proxy.js +47 -2
  184. package/dist-server/server/drivers/pi.js +75 -4
  185. package/dist-server/server/drivers/prompt-split.js +87 -0
  186. package/dist-server/server/enterprise.js +97 -16
  187. package/dist-server/server/external-runtime.js +83 -0
  188. package/dist-server/server/fleet.js +1 -1
  189. package/dist-server/server/group-thread-usage.js +103 -0
  190. package/dist-server/server/guarded-requests.js +60 -0
  191. package/dist-server/server/harness/http.js +61 -0
  192. package/dist-server/server/harness/registry.js +1 -0
  193. package/dist-server/server/hosted-models.js +151 -0
  194. package/dist-server/server/hosted-slack.js +11 -0
  195. package/dist-server/server/index.js +3091 -658
  196. package/dist-server/server/local-computer.js +72 -23
  197. package/dist-server/server/managed-desktop.js +79 -14
  198. package/dist-server/server/managed-policy.js +153 -0
  199. package/dist-server/server/mcp-bridge.js +6 -6
  200. package/dist-server/server/message-db.js +28 -0
  201. package/dist-server/server/model-context-window.js +7 -0
  202. package/dist-server/server/model-prices.js +121 -0
  203. package/dist-server/server/new-bot-defaults.js +115 -0
  204. package/dist-server/server/notify.js +10 -1
  205. package/dist-server/server/org-library.js +1051 -0
  206. package/dist-server/server/package-export.js +627 -39
  207. package/dist-server/server/package-import.js +458 -0
  208. package/dist-server/server/package-parts.js +87 -0
  209. package/dist-server/server/peer-approval.js +16 -7
  210. package/dist-server/server/peer-roster.js +23 -2
  211. package/dist-server/server/presets.js +492 -0
  212. package/dist-server/server/provider-key-check.js +3 -2
  213. package/dist-server/server/published-teams.js +80 -0
  214. package/dist-server/server/recent-work.js +15 -0
  215. package/dist-server/server/request-auth.js +96 -0
  216. package/dist-server/server/room-handoffs.js +44 -14
  217. package/dist-server/server/routes/bot-presets.js +25 -0
  218. package/dist-server/server/routes/hosted-slack.js +20 -0
  219. package/dist-server/server/routes/table.js +14 -0
  220. package/dist-server/server/routine-requests.js +6 -3
  221. package/dist-server/server/routines.js +63 -1
  222. package/dist-server/server/section-context.js +10 -0
  223. package/dist-server/server/skills.js +34 -61
  224. package/dist-server/server/spend.js +111 -16
  225. package/dist-server/server/steer-queue.js +9 -3
  226. package/dist-server/server/store.js +550 -35
  227. package/dist-server/server/surface.js +10 -4
  228. package/dist-server/server/system-prompt.js +12 -4
  229. package/dist-server/server/team-backup.js +16 -3
  230. package/dist-server/server/team-computers.js +10 -0
  231. package/dist-server/server/team-library.js +6 -2
  232. package/dist-server/server/team-manifest.js +4 -3
  233. package/dist-server/server/team-setup-requests.js +16 -5
  234. package/dist-server/server/thread-events.js +17 -1
  235. package/dist-server/server/thread-starters.js +53 -0
  236. package/dist-server/server/turn-context.js +2 -0
  237. package/dist-server/server/turn-watchdog.js +12 -3
  238. package/dist-server/server/usage-ledger.js +43 -9
  239. package/dist-server/server/vps-computer.js +139 -51
  240. package/dist-server/server/vps-container-mcp.js +7 -0
  241. package/dist-server/server/vps-ssh.js +36 -7
  242. package/dist-server/server/workspace-backup-policy.js +21 -2
  243. package/dist-server/server/workspace-backup.js +7 -4
  244. package/dist-server/server/workspace.js +1 -1
  245. package/dist-server/shared/approval-mode.js +1 -1
  246. package/dist-server/shared/ask-question.js +188 -12
  247. package/dist-server/shared/bot-profile.js +15 -0
  248. package/dist-server/shared/command-allowlist.js +1 -0
  249. package/dist-server/shared/credential-request.js +1 -1
  250. package/dist-server/shared/mention-boundary.js +15 -0
  251. package/dist-server/shared/package-format.js +967 -0
  252. package/dist-server/shared/routines.js +7 -1
  253. package/dist-server/shared/skill-md.js +64 -0
  254. package/dist-server/shared/team-backup.js +9 -0
  255. package/dist-server/shared/wire.js +4 -0
  256. package/dist-server/src/lib/composer-attachments.js +16 -0
  257. package/dist-server/vps-container-mcp.js +1147 -179
  258. package/enterprise/README.md +33 -12
  259. package/enterprise/server/index.js +5 -5
  260. package/package.json +1 -1
  261. package/dist/assets/index-Ba9G44HI.js +0 -310
  262. package/dist/assets/index-CkRNp7wX.css +0 -1
@@ -1,4 +1,4 @@
1
- // Box (box.ascii.dev) provider — the bot's cloud computer. Ported from
1
+ // Box (boat.dev) provider — the bot's cloud computer. Ported from
2
2
  // agentcal-api src/providers/box.js, reshaped per-bot instead of
3
3
  // per-customer: every bot gets one persistent box (deterministic name),
4
4
  // stop pauses billing while the disk survives, and Join always mints a
@@ -134,7 +134,7 @@ function boxDeletionOperation(body, boxId, expectedOperationId) {
134
134
  return { id, kind: "box", targetId: boxId, status: status };
135
135
  }
136
136
  function deletionBlockedError(boxId) {
137
- return Object.assign(new Error(`ascii.dev accepted deletion of ${boxId}, but the deletion operation is blocked — check ascii.dev and retry`), { status: 409 });
137
+ return Object.assign(new Error(`boat.dev accepted deletion of ${boxId}, but the deletion operation is blocked — check boat.dev and retry`), { status: 409 });
138
138
  }
139
139
  function boxDeleteProvedAbsent(result) {
140
140
  return result.status === 404 || result.status === 410;
@@ -194,7 +194,7 @@ async function reconcileRecordedBoxDeletion(cfg, initial, pollDelaysMs = []) {
194
194
  return "confirmed";
195
195
  }
196
196
  if (inspected.identity.name !== record.name) {
197
- throw Object.assign(new Error("A cloud computer being deleted no longer has its remembered name — repair it in ascii.dev before continuing"), { status: 503 });
197
+ throw Object.assign(new Error("A cloud computer being deleted no longer has its remembered name — repair it in boat.dev before continuing"), { status: 503 });
198
198
  }
199
199
  return record.phase === "blocked" ? "blocked" : "pending";
200
200
  }
@@ -251,7 +251,7 @@ async function confirmAcceptedBoxDeletion(cfg, record, acceptedBody, pollDelaysM
251
251
  finishRecordedBoxDeletion(record.boxId);
252
252
  return "confirmed";
253
253
  }
254
- throw Object.assign(new Error(`ascii.dev returned an invalid deletion receipt for ${record.boxId}; its deletion fence was kept`), { status: 503 });
254
+ throw Object.assign(new Error(`boat.dev returned an invalid deletion receipt for ${record.boxId}; its deletion fence was kept`), { status: 503 });
255
255
  }
256
256
  const next = operation.status === "blocked"
257
257
  ? markBoxDeletionBlocked(record.boxId, operation)
@@ -270,7 +270,7 @@ async function requestRecordedBoxDeletion(cfg, identity, pollDelaysMs = BOX_DELE
270
270
  });
271
271
  }
272
272
  catch (error) {
273
- throw Object.assign(new Error("Could not confirm whether ascii.dev accepted the delete. The computer was kept fenced; retry Delete to reconcile it"), { status: 503, cause: error });
273
+ throw Object.assign(new Error("Could not confirm whether boat.dev accepted the delete. The computer was kept fenced; retry Delete to reconcile it"), { status: 503, cause: error });
274
274
  }
275
275
  if (boxDeleteProvedAbsent(removed)) {
276
276
  finishRecordedBoxDeletion(identity.boxId);
@@ -305,12 +305,12 @@ export async function boxNameFor(botId) {
305
305
  export async function boxNameMatchesBot(botId, name) {
306
306
  return name === await boxNameFor(botId) || name === legacyBoxNameFor(botId);
307
307
  }
308
- export async function runCommand(cfg, boxId, command, { timeoutMs = 120_000 } = {}) {
308
+ export async function runCommand(cfg, boxId, command, { timeoutMs = 120_000, signal } = {}) {
309
309
  assertBoxNotDeleting(boxId);
310
310
  const res = await boxFetch(cfg, `/boxes/${boxId}/commands`, {
311
311
  method: "POST",
312
312
  body: JSON.stringify({ command }),
313
- signal: AbortSignal.timeout(timeoutMs),
313
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
314
314
  });
315
315
  const body = await res.json().catch(() => null);
316
316
  return {
@@ -367,12 +367,12 @@ async function waitReady(cfg, boxId, budgetMs = 90_000) {
367
367
  const boxIdCache = new Map();
368
368
  function boxInventoryProblem(status, body) {
369
369
  if (status === 401 || status === 403) {
370
- return "ascii.dev rejected the Box API key — update it in Settings → Connections";
370
+ return "boat.dev rejected the Box API key — update it in Settings → Connections";
371
371
  }
372
372
  if (status === 429)
373
- return "ascii.dev is rate-limiting this account — wait a minute and refresh";
373
+ return "boat.dev is rate-limiting this account — wait a minute and refresh";
374
374
  const message = typeof body?.message === "string" ? body.message.trim() : "";
375
- return message ? `ascii.dev could not list cloud computers: ${message}` : `ascii.dev could not list cloud computers (${status})`;
375
+ return message ? `boat.dev could not list cloud computers: ${message}` : `boat.dev could not list cloud computers (${status})`;
376
376
  }
377
377
  function safeBoxState(value) {
378
378
  if (typeof value !== "string")
@@ -391,25 +391,25 @@ async function listBoxPages(cfg) {
391
391
  listed = await boxJson(cfg, path, { signal: AbortSignal.timeout(20_000) });
392
392
  }
393
393
  catch {
394
- return { ok: false, problem: "Could not reach ascii.dev to list cloud computers — check your connection and refresh" };
394
+ return { ok: false, problem: "Could not reach boat.dev to list cloud computers — check your connection and refresh" };
395
395
  }
396
396
  if (!listed.ok || !Array.isArray(listed.body?.boxes)) {
397
- return { ok: false, problem: boxInventoryProblem(listed.status, listed.body) };
397
+ return { ok: false, problem: boxInventoryProblem(listed.status, listed.body), credentialRejected: listed.status === 401 || listed.status === 403 };
398
398
  }
399
399
  boxes.push(...listed.body.boxes);
400
400
  const next = listed.body?.pageInfo?.nextCursor;
401
401
  if (next === undefined || next === null || next === "")
402
402
  return { ok: true, boxes };
403
403
  if (typeof next !== "string" || next.length > 4_096) {
404
- return { ok: false, problem: "ascii.dev returned an invalid cloud computer page cursor" };
404
+ return { ok: false, problem: "boat.dev returned an invalid cloud computer page cursor" };
405
405
  }
406
406
  if (seenCursors.has(next)) {
407
- return { ok: false, problem: "ascii.dev repeated a cloud computer page cursor — refresh and try again" };
407
+ return { ok: false, problem: "boat.dev repeated a cloud computer page cursor — refresh and try again" };
408
408
  }
409
409
  seenCursors.add(next);
410
410
  cursor = next;
411
411
  }
412
- return { ok: false, problem: "ascii.dev returned too many cloud computer pages — narrow the account inventory and refresh" };
412
+ return { ok: false, problem: "boat.dev returned too many cloud computer pages — narrow the account inventory and refresh" };
413
413
  }
414
414
  /**
415
415
  * One account listing for Settings and deletion guards. Only boxes
@@ -431,6 +431,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
431
431
  configured: true,
432
432
  available: false,
433
433
  problem: listed.problem,
434
+ credentialRejected: listed.credentialRejected,
434
435
  instances: [],
435
436
  };
436
437
  }
@@ -468,12 +469,12 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
468
469
  continue;
469
470
  const matchingRows = candidates.filter((candidate) => candidate?.id === recovery.boxId);
470
471
  if (matchingRows.length > 1) {
471
- return invalidInventory("ascii.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in ascii.dev");
472
+ return invalidInventory("boat.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
472
473
  }
473
474
  if (matchingRows.length === 1) {
474
475
  const listedName = typeof matchingRows[0]?.name === "string" ? matchingRows[0].name : "";
475
476
  if (listedName !== namedOwner.currentName && listedName !== namedOwner.legacyName) {
476
- return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in ascii.dev before continuing");
477
+ return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in boat.dev before continuing");
477
478
  }
478
479
  continue;
479
480
  }
@@ -489,7 +490,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
489
490
  }
490
491
  if (inspected.identity.name !== namedOwner.currentName
491
492
  && inspected.identity.name !== namedOwner.legacyName) {
492
- return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in ascii.dev before continuing");
493
+ return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in boat.dev before continuing");
493
494
  }
494
495
  const directCandidate = {
495
496
  id: inspected.identity.boxId,
@@ -521,11 +522,11 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
521
522
  }
522
523
  const matchingRows = candidates.filter((candidate) => candidate?.id === deletion.boxId);
523
524
  if (matchingRows.length > 1) {
524
- return invalidInventory("ascii.dev returned a conflicting id for a cloud computer being deleted");
525
+ return invalidInventory("boat.dev returned a conflicting id for a cloud computer being deleted");
525
526
  }
526
527
  if (matchingRows.length === 1) {
527
528
  if (matchingRows[0]?.name !== deletion.name) {
528
- return invalidInventory("A cloud computer being deleted no longer has its remembered name — repair it in ascii.dev before continuing");
529
+ return invalidInventory("A cloud computer being deleted no longer has its remembered name — repair it in boat.dev before continuing");
529
530
  }
530
531
  if (getBoxDeletion(deletion.boxId)?.phase === "accepted") {
531
532
  matchingRows[0] = { ...matchingRows[0], state: "removing" };
@@ -573,11 +574,11 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
573
574
  continue;
574
575
  const boxId = typeof candidate.id === "string" ? candidate.id : "";
575
576
  if (!BOX_ID.test(boxId)) {
576
- return invalidInventory("ascii.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in ascii.dev");
577
+ return invalidInventory("boat.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
577
578
  }
578
579
  const existing = ownedBoxByBot.get(owner.botId);
579
580
  if (existing && existing !== boxId) {
580
- return invalidInventory("ascii.dev returned conflicting cloud computers for one OpenMaus bot — repair them in ascii.dev before continuing");
581
+ return invalidInventory("boat.dev returned conflicting cloud computers for one OpenMaus bot — repair them in boat.dev before continuing");
581
582
  }
582
583
  ownedBoxByBot.set(owner.botId, boxId);
583
584
  }
@@ -601,7 +602,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
601
602
  else if (LEGACY_MANAGED_BOX_NAME.test(name)) {
602
603
  // Pre-scope names have no installation provenance. A live local bot is
603
604
  // the only safe ownership proof; unmatched legacy rows stay provider-
604
- // managed until the person handles them in ascii.dev directly.
605
+ // managed until the person handles them in boat.dev directly.
605
606
  owner = ownerByLegacyName.get(name) ?? null;
606
607
  if (!owner)
607
608
  continue;
@@ -614,17 +615,17 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
614
615
  // deterministic name), silently skipping a malformed/duplicated identity
615
616
  // could let bot deletion mistake provider corruption for absence.
616
617
  if (!BOX_ID.test(boxId)) {
617
- return invalidInventory("ascii.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in ascii.dev");
618
+ return invalidInventory("boat.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
618
619
  }
619
620
  if ((boxIdCounts.get(boxId) ?? 0) !== 1 || seenBoxIds.has(boxId)) {
620
- return invalidInventory("ascii.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in ascii.dev");
621
+ return invalidInventory("boat.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
621
622
  }
622
623
  if (legacyOwner && owner && options.adoptLegacy !== false) {
623
624
  try {
624
625
  adoptResolvedBox(owner.botId, boxId);
625
626
  }
626
627
  catch {
627
- return invalidInventory("OpenMausBot could not safely remember this legacy cloud computer's owner — repair it in ascii.dev before continuing");
628
+ return invalidInventory("OpenMausBot could not safely remember this legacy cloud computer's owner — repair it in boat.dev before continuing");
628
629
  }
629
630
  }
630
631
  seenBoxIds.add(boxId);
@@ -662,7 +663,7 @@ export async function inspectBoxIdentity(cfg, boxId) {
662
663
  return {
663
664
  available: false,
664
665
  identity: null,
665
- problem: "Could not reach ascii.dev to verify a remembered cloud computer",
666
+ problem: "Could not reach boat.dev to verify a remembered cloud computer",
666
667
  };
667
668
  }
668
669
  if (inspected.status === 404 || inspected.status === 410) {
@@ -675,7 +676,7 @@ export async function inspectBoxIdentity(cfg, boxId) {
675
676
  const returnedId = typeof candidate?.id === "string" ? candidate.id : "";
676
677
  const name = typeof candidate?.name === "string" ? candidate.name : "";
677
678
  if (returnedId !== boxId || name.length === 0 || name.length > 100 || /[\r\n]/.test(name)) {
678
- return { available: false, identity: null, problem: "ascii.dev returned an invalid cloud computer identity" };
679
+ return { available: false, identity: null, problem: "boat.dev returned an invalid cloud computer identity" };
679
680
  }
680
681
  return { available: true, identity: { boxId, name, state: safeBoxState(candidate.state) }, problem: null };
681
682
  }
@@ -716,7 +717,7 @@ const QUIESCE_BROWSER = [
716
717
  async function stopBox(cfg, boxId) {
717
718
  assertBoxNotDeleting(boxId);
718
719
  // Browser shutdown is best-effort, but the provider stop is not: Settings
719
- // must never say a computer is sleeping when ascii.dev rejected the action.
720
+ // must never say a computer is sleeping when boat.dev rejected the action.
720
721
  await runCommand(cfg, boxId, QUIESCE_BROWSER, { timeoutMs: 5_000 }).catch(() => null);
721
722
  const stopped = await boxJson(cfg, `/boxes/${boxId}/stop`, { method: "POST" });
722
723
  if (!stopped.ok)
@@ -752,7 +753,7 @@ export async function sleepManagedBox(cfg, owners, boxId, claim) {
752
753
  }
753
754
  }
754
755
  /** Permanent Settings action. The caller must echo the exact freshly-listed
755
- * machine name as well as its id; ascii.dev independently requires the id in
756
+ * machine name as well as its id; boat.dev independently requires the id in
756
757
  * its confirmation header. */
757
758
  export async function deleteManagedBox(cfg, owners, boxId, confirmName, claim, options = {}) {
758
759
  cfg = snapshotBoxConfig(cfg);
@@ -811,7 +812,7 @@ export async function findBox(cfg, botId) {
811
812
  if (direct?.ok && directBox?.id === cachedId && directBox.state !== "error")
812
813
  return directBox;
813
814
  if (direct?.ok && directBox?.id !== cachedId) {
814
- throw Object.assign(new Error("ascii.dev returned an invalid cloud computer identity"), { status: 503 });
815
+ throw Object.assign(new Error("boat.dev returned an invalid cloud computer identity"), { status: 503 });
815
816
  }
816
817
  boxIdCache.delete(botId); // gone or broken — fall back to the listing
817
818
  }
@@ -825,7 +826,7 @@ export async function findBox(cfg, botId) {
825
826
  // discoverable only for this exact local bot id.
826
827
  const expected = listed.boxes.filter((candidate) => candidate?.name === name || candidate?.name === legacyName);
827
828
  if (expected.some((candidate) => !BOX_ID.test(candidate?.id))) {
828
- throw Object.assign(new Error("ascii.dev returned an invalid cloud computer identity"), { status: 503 });
829
+ throw Object.assign(new Error("boat.dev returned an invalid cloud computer identity"), { status: 503 });
829
830
  }
830
831
  const found = expected.find((candidate) => candidate.name === name && candidate.state !== "error")
831
832
  ?? expected.find((candidate) => candidate.name === legacyName && candidate.state !== "error")
@@ -833,7 +834,7 @@ export async function findBox(cfg, botId) {
833
834
  if (found) {
834
835
  const duplicateId = listed.boxes.filter((candidate) => candidate?.id === found.id).length !== 1;
835
836
  if (duplicateId) {
836
- throw Object.assign(new Error("ascii.dev returned a conflicting cloud computer identity"), { status: 503 });
837
+ throw Object.assign(new Error("boat.dev returned a conflicting cloud computer identity"), { status: 503 });
837
838
  }
838
839
  if (found.name === legacyName)
839
840
  adoptResolvedBox(botId, found.id);
@@ -871,14 +872,14 @@ export async function verifyToken(token) {
871
872
  return {
872
873
  ok: false,
873
874
  message: token.startsWith("box_")
874
- ? "ascii.dev rejected that token — it may have been revoked or expired. Copy a fresh one from your ascii.dev account."
875
- : "That doesn't look like a box API key: they start with box_. Copy the API key from your ascii.dev account (an account or session token won't work here).",
875
+ ? "boat.dev rejected that token — it may have been revoked or expired. Copy a fresh one from your boat.dev account."
876
+ : "That doesn't look like a box API key: they start with box_. Copy the API key from your boat.dev account (an account or session token won't work here).",
876
877
  };
877
878
  }
878
- return { ok: false, message: `ascii.dev returned ${res.status} for that token — try again in a moment.` };
879
+ return { ok: false, message: `boat.dev returned ${res.status} for that token — try again in a moment.` };
879
880
  }
880
881
  catch {
881
- return { ok: false, message: "Couldn't reach ascii.dev to check that token — check your connection and retry." };
882
+ return { ok: false, message: "Couldn't reach boat.dev to check that token — check your connection and retry." };
882
883
  }
883
884
  }
884
885
  /** Turn a provider refusal into something a person can act on. The
@@ -890,17 +891,17 @@ export function boxErrorMessage(status, what, body) {
890
891
  const link = typeof body?.error?.details?.billingUrl === "string" ? body.error.details.billingUrl : "";
891
892
  if (status === 402) {
892
893
  // e.g. "Start the $20/month Box plan to create sandboxes."
893
- return [theirs || "ascii.dev needs a paid Box plan before it will create a computer.", link].filter(Boolean).join(" ");
894
+ return [theirs || "boat.dev needs a paid Box plan before it will create a computer.", link].filter(Boolean).join(" ");
894
895
  }
895
896
  if (status === 401 || status === 403) {
896
- return "your box token was rejected by ascii.dev — open App Settings and paste a current token (it starts with box_)";
897
+ return "your box token was rejected by boat.dev — open App Settings and paste a current token (it starts with box_)";
897
898
  }
898
899
  if (status === 429) {
899
- return theirs || "ascii.dev is rate-limiting this account — wait a minute and try again";
900
+ return theirs || "boat.dev is rate-limiting this account — wait a minute and try again";
900
901
  }
901
902
  return theirs ? `${what} failed: ${theirs}` : `${what} failed (${status})`;
902
903
  }
903
- /** ascii.dev trial accounts reject the normal eight-hour auto-stop with a
904
+ /** boat.dev trial accounts reject the normal eight-hour auto-stop with a
904
905
  * structured `trial_auto_stop_required` refusal. Retry that one condition
905
906
  * once at the provider's advertised maximum (or the documented two-hour
906
907
  * trial ceiling). Other create failures must retain their original error. */
@@ -921,7 +922,7 @@ function idempotentCreateInProgress(result) {
921
922
  }
922
923
  /** The keys this OpenMausBot already holds, as the environment its bots'
923
924
  * agents read on the box. The box is created with `noEnv: true`, so the
924
- * ascii.dev account's own logins never land in the guest: the box has exactly
925
+ * boat.dev account's own logins never land in the guest: the box has exactly
925
926
  * these and nothing else (see "Whose keys" in the Box integrated-agents docs). */
926
927
  export function boxCredentialEnv(cfg, env = process.env) {
927
928
  const out = {};
@@ -990,7 +991,7 @@ async function requestBoxCreate(cfg, botId, ttlSeconds, env) {
990
991
  });
991
992
  }
992
993
  catch (error) {
993
- // A dropped response is ambiguous: ascii.dev may already have created
994
+ // A dropped response is ambiguous: boat.dev may already have created
994
995
  // the Box. One retry with the same key recovers it safely.
995
996
  if (ambiguousRetries++ === 0)
996
997
  continue;
@@ -1083,7 +1084,7 @@ export async function provisionBox(cfg, botId, _botName) {
1083
1084
  assertBotBoxNotDeleting(botId);
1084
1085
  // Provider-side backstop: archives itself (billing pauses, disk
1085
1086
  // survives) if every stop path dies. Trial accounts get one narrower
1086
- // retry when ascii.dev reports their shorter TTL ceiling.
1087
+ // retry when boat.dev reports their shorter TTL ceiling.
1087
1088
  const createRes = await createBox(cfg, botId, credentialEnv);
1088
1089
  if (!createRes.ok || !createRes.body?.box?.id) {
1089
1090
  throw new Error(boxErrorMessage(createRes.status, "box create", createRes.body));
@@ -1133,13 +1134,13 @@ export async function provisionBox(cfg, botId, _botName) {
1133
1134
  }
1134
1135
  catch (cleanupError) {
1135
1136
  const cleanupMessage = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
1136
- throw new Error(`${originalMessage}. The new computer's deletion was not confirmed: ${cleanupMessage}. Check box ${box.id} in ascii.dev.`);
1137
+ throw new Error(`${originalMessage}. The new computer's deletion was not confirmed: ${cleanupMessage}. Check box ${box.id} in boat.dev.`);
1137
1138
  }
1138
1139
  if (cleanupConfirmation === "confirmed")
1139
1140
  throw error;
1140
1141
  boxIdCache.delete(botId);
1141
- throw Object.assign(new Error(`${originalMessage}. ascii.dev accepted deletion of the new computer, but it is still pending; `
1142
- + `its recovery record was kept, along with its deletion fence. Check box ${box.id} in ascii.dev.`, { cause: error }), { status: 503 });
1142
+ throw Object.assign(new Error(`${originalMessage}. boat.dev accepted deletion of the new computer, but it is still pending; `
1143
+ + `its recovery record was kept, along with its deletion fence. Check box ${box.id} in boat.dev.`, { cause: error }), { status: 503 });
1143
1144
  }
1144
1145
  }
1145
1146
  /** Wake the bot's box and return a FRESH desktop URL. */
@@ -1215,16 +1216,16 @@ export const PANEL_FRAME_QUALITY = 85;
1215
1216
  // ffmpeg's -q:v runs 2 (best) to 31; 3 lands near JPEG quality 85.
1216
1217
  const PANEL_FRAME_FFMPEG_Q = 3;
1217
1218
  /** The shell that captures one panel frame on the box. Exported for tests. */
1218
- export function panelShotCommand({ width = PANEL_FRAME_WIDTH, quality = PANEL_FRAME_QUALITY } = {}) {
1219
+ export function panelShotCommand({ width = PANEL_FRAME_WIDTH, quality = PANEL_FRAME_QUALITY, framePath = PANEL_PATH, nativeSize = false } = {}) {
1219
1220
  return [
1220
1221
  "export DISPLAY=${DISPLAY:-:0}",
1221
- `f=${PANEL_PATH}`,
1222
+ `f=${shellQuote(framePath)}`,
1222
1223
  // a stale frame must not pass `test -s` when every capture tool fails
1223
1224
  'rm -f "$f"',
1224
1225
  'w=$(xdotool getdisplaygeometry 2>/dev/null | cut -d" " -f1)',
1225
1226
  'case "$w" in ""|*[!0-9]*) w=0;; esac',
1226
1227
  `scrot -o -p -q ${quality} "$f" 2>/dev/null || import -window root -quality ${quality} "$f" 2>/dev/null || ffmpeg -y -f x11grab -draw_mouse 1 -i "$DISPLAY" -frames:v 1 -q:v ${PANEL_FRAME_FFMPEG_Q} "$f" >/dev/null 2>&1`,
1227
- `if [ "$w" -gt ${width} ] 2>/dev/null && command -v convert >/dev/null 2>&1; then convert "$f" -resize ${width}x -quality ${quality} "$f" 2>/dev/null || true; fi`,
1228
+ ...(nativeSize ? [] : [`if [ "$w" -gt ${width} ] 2>/dev/null && command -v convert >/dev/null 2>&1; then convert "$f" -resize ${width}x -quality ${quality} "$f" 2>/dev/null || true; fi`]),
1228
1229
  'test -s "$f" && echo captured',
1229
1230
  ].join("; ");
1230
1231
  }
@@ -1236,11 +1237,11 @@ const MAX_FRAME_BYTES = 8 * 1024 * 1024;
1236
1237
  const FRAME_TOO_LARGE = "the box frame exceeds the 8 MB limit";
1237
1238
  /** Read a file off the box as base64 — raw artifact bytes when the API
1238
1239
  * supports it (33% less transfer, no JSON envelope), else the files API. */
1239
- async function readFileBase64(cfg, boxId, path) {
1240
+ async function readFileBase64(cfg, boxId, path, signal) {
1240
1241
  let bytes = null;
1241
1242
  let tooLarge = false;
1242
1243
  try {
1243
- const res = await boxFetch(cfg, `/boxes/${boxId}/artifacts?path=${encodeURIComponent(path)}`);
1244
+ const res = await boxFetch(cfg, `/boxes/${boxId}/artifacts?path=${encodeURIComponent(path)}`, { signal });
1244
1245
  if (res.ok) {
1245
1246
  const declaredLength = res.headers.get("content-length");
1246
1247
  if (declaredLength !== null && Number(declaredLength) > MAX_FRAME_BYTES)
@@ -1257,7 +1258,8 @@ async function readFileBase64(cfg, boxId, path) {
1257
1258
  }
1258
1259
  if (bytes?.length)
1259
1260
  return bytes.toString("base64");
1260
- const { ok, body } = await boxJson(cfg, `/boxes/${boxId}/files?path=${encodeURIComponent(path)}&encoding=base64`);
1261
+ signal?.throwIfAborted();
1262
+ const { ok, body } = await boxJson(cfg, `/boxes/${boxId}/files?path=${encodeURIComponent(path)}&encoding=base64`, { signal });
1261
1263
  const content = body?.content;
1262
1264
  if (ok && typeof content === "string" && content) {
1263
1265
  if (Buffer.byteLength(content, "base64") > MAX_FRAME_BYTES)
@@ -1268,7 +1270,7 @@ async function readFileBase64(cfg, boxId, path) {
1268
1270
  }
1269
1271
  /** `knownBoxId` skips box resolution entirely — the screen poller holds
1270
1272
  * the id for the whole turn and must not re-resolve it every frame. */
1271
- export async function screenshotBox(cfg, botId, knownBoxId) {
1273
+ export async function screenshotBox(cfg, botId, knownBoxId, options) {
1272
1274
  cfg = snapshotBoxConfig(cfg);
1273
1275
  let boxId = knownBoxId;
1274
1276
  if (!boxId) {
@@ -1279,11 +1281,12 @@ export async function screenshotBox(cfg, botId, knownBoxId) {
1279
1281
  throw new Error(`box is ${box.state}`);
1280
1282
  boxId = box.id;
1281
1283
  }
1282
- const out = await runCommand(cfg, boxId, SHOT_CMD, { timeoutMs: 60_000 });
1284
+ const framePath = options?.nativeSize ? PANEL_PATH + ".model.jpg" : PANEL_PATH;
1285
+ const out = await runCommand(cfg, boxId, options?.nativeSize ? panelShotCommand({ framePath, nativeSize: true }) : SHOT_CMD, { timeoutMs: 60_000, signal: options?.signal });
1283
1286
  if (!/captured/.test(out.stdout)) {
1284
1287
  throw new Error(out.stderr.slice(0, 200) || "screen capture failed on the box");
1285
1288
  }
1286
- const data = await readFileBase64(cfg, boxId, PANEL_PATH);
1289
+ const data = await readFileBase64(cfg, boxId, framePath, options?.signal);
1287
1290
  if (!data)
1288
1291
  throw new Error("could not read the frame back from the box");
1289
1292
  return { png: data, format: "jpeg" };
@@ -69,8 +69,9 @@ export async function browserProxyRequest(frame, connection) {
69
69
  return { jsonrpc: "2.0", id, result: payload.result };
70
70
  }
71
71
  catch (error) {
72
- return failure(id, message.method, error instanceof Error && !/fetch failed|abort|timeout/i.test(error.message)
73
- ? error.message : "Browser connection was interrupted. Do not repeat the action until its state is checked.");
72
+ const detail = error instanceof Error && !/fetch failed|abort|timeout/i.test(error.message)
73
+ ? error.message : "Browser connection was interrupted. Do not repeat the action until its state is checked.";
74
+ return failure(id, message.method, detail + " This failure does not establish that all browsers are unavailable. Inspect available connections with select_computer if that tool is present. Keep the requested computer and account; do not bypass a permission refusal or human takeover, and do not repeat an uncertain action without checking its result.");
74
75
  }
75
76
  }
76
77
  function run() {
@@ -13,7 +13,7 @@ export function browserRuntimeEnv(overrides) {
13
13
  const env = Object.fromEntries(HOST_ENV.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]));
14
14
  return { ...env, ...overrides };
15
15
  }
16
- class TransportError extends Error {
16
+ export class TransportError extends Error {
17
17
  }
18
18
  /** A server-owned JSONL client. Neither child stderr nor its environment is
19
19
  * returned to the agent. Reuse the existing cross-platform spawn/kill rules. */
@@ -279,11 +279,47 @@ export class BrowserRuntime {
279
279
  // The model sees slimmed schemas and text-only, bounded results; the
280
280
  // launch/session parameters OMB owns never reach the engine from a call.
281
281
  const request = method === "tools/call" ? stripHarnessOwnedArguments(params) : params;
282
- const result = await entry.client.rpc(method, request);
282
+ let result = await entry.client.rpc(method, request);
283
283
  beforeDispatch?.(); // A turn revoked while the tool ran receives no result.
284
284
  if (method === "tools/list")
285
285
  return slimBrowserToolList(result);
286
286
  const toolName = request && typeof request === "object" && typeof request.name === "string" ? request.name : undefined;
287
+ if (toolName === "agent_browser_open" && result && typeof result === "object" &&
288
+ result.isError !== true) {
289
+ // Navigation alone does not prove that the requested page loaded.
290
+ // Observe in the same scoped session, without replaying the action.
291
+ beforeDispatch?.();
292
+ if (this.gate(session).owner !== null)
293
+ throw new Error(BROWSER_CONTROL_REFUSAL);
294
+ let observation;
295
+ try {
296
+ observation = await entry.client.rpc("tools/call", {
297
+ name: "agent_browser_snapshot", arguments: { compact: true },
298
+ });
299
+ }
300
+ catch (error) {
301
+ // A tool-level refusal does not undo the completed navigation.
302
+ // Transport failures still engage the uncertainty/recovery gate.
303
+ if (error instanceof TransportError)
304
+ throw error;
305
+ observation = { isError: true, content: [] };
306
+ }
307
+ beforeDispatch?.();
308
+ const navigation = result;
309
+ const page = observation;
310
+ const observed = page?.isError !== true && Array.isArray(page?.content) && page.content.some((item) => item && typeof item === "object" && item.type === "text" &&
311
+ typeof item.text === "string" && item.text.trim().length > 0);
312
+ result = {
313
+ content: [
314
+ ...(Array.isArray(navigation.content) ? navigation.content : []),
315
+ { type: "text", text: !observed
316
+ ? "Navigation returned, but page verification failed. Do not claim the requested page loaded and do not blindly repeat navigation."
317
+ : "Page observed after navigation. Check this result for redirects, sign-in requirements or page errors before reporting task success:" },
318
+ ...(Array.isArray(page?.content) ? page.content : []),
319
+ ],
320
+ ...(!observed ? { isError: true } : {}),
321
+ };
322
+ }
287
323
  return shapeBrowserToolResult(result, { toolName, budget: this.options.resultBudget });
288
324
  }
289
325
  catch (error) {
@@ -31,6 +31,8 @@ export function queueChannelMessage(groupId, threadId, text, options = {}) {
31
31
  sendId: options.sendId,
32
32
  mode: options.mode ?? "chat",
33
33
  via: options.via,
34
+ sender: options.sender,
35
+ trigger: options.trigger,
34
36
  };
35
37
  saveChatFollowup({ id: item.id, kind: "channel", ownerId: groupId, threadId, payload: item });
36
38
  entry.items.push(item);
@@ -9,10 +9,32 @@ export class SetupCancelled extends Error {
9
9
  }
10
10
  }
11
11
  function displayText(value, multiline = false) {
12
- const plain = stripVTControlCharacters(value);
12
+ // OSC 8 hyperlinks slip past stripVTControlCharacters on supported Node
13
+ // versions whenever the URI carries a character RFC 3986 allows unencoded
14
+ // ((, ), +, and friends): the failed match strands most of the URI and its
15
+ // BEL as printable text (nodejs/node#64313). Strip whole OSC sequences
16
+ // here, from the raw value while their openers are still intact; the
17
+ // label between a link's opening and closing sequences survives, and an
18
+ // unterminated sequence is removed outright. Both introducer forms
19
+ // match: ESC ] and the lone C1 byte, which carries no closing bracket.
20
+ // eslint-disable-next-line no-control-regex
21
+ const withoutOsc = value.replace(/(?:\u001b\]|\u009d)[^\u0007\u001b\u009c]*(?:\u0007|\u001b\\|\u009c)?/g, "");
22
+ const plain = stripVTControlCharacters(withoutOsc);
13
23
  // Provider-supplied labels must not issue terminal control commands.
24
+ // stripVTControlCharacters glues a trailing BEL onto the escape sequence
25
+ // before it on some Node versions and leaves it bare on others, so the
26
+ // fallback below must be version-independent: controls that render no
27
+ // glyph (BEL, DEL, C1, …) are removed outright — padding them would
28
+ // counterfeit whitespace — while tab and carriage return collapse to one
29
+ // space and a newline survives only in multiline mode.
14
30
  // eslint-disable-next-line no-control-regex
15
- const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => character === "\n" && multiline ? "\n" : " ");
31
+ const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
32
+ if (character === "\n")
33
+ return multiline ? "\n" : " ";
34
+ if (character === "\t" || character === "\r")
35
+ return " ";
36
+ return "";
37
+ });
16
38
  // Log lines must not end in trailing spaces; question prompts keep spacing like "Key: ".
17
39
  return multiline ? withoutControls.replace(/[ \t]+(?=\n|$)/g, "") : withoutControls;
18
40
  }