openmausbot 0.1.86 → 0.1.88

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 (216) hide show
  1. package/README.md +4 -3
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
  7. package/dist/assets/channel-CnRnO3tF.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
  34. package/dist/assets/index-BIMdIbaw.css +1 -0
  35. package/dist/assets/index-CHnODCr2.js +583 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/companion/src/routes.js +8 -1
  63. package/dist-server/connector-proxy.js +105 -1
  64. package/dist-server/container-mcp.js +1032 -165
  65. package/dist-server/drivers/agents-proxy.js +338 -29
  66. package/dist-server/drivers/phone-proxy.js +33 -2
  67. package/dist-server/index.js +40590 -31811
  68. package/dist-server/local-computer-proxy.js +5 -3
  69. package/dist-server/local-computer.js +53 -17
  70. package/dist-server/mcp-gate.js +2 -0
  71. package/dist-server/mcp-server.js +5 -3
  72. package/dist-server/message-search.worker.js +52 -0
  73. package/dist-server/openmausbot.js +4457 -1399
  74. package/dist-server/pair-cli.js +4457 -1399
  75. package/dist-server/permission-proxy.js +29 -9
  76. package/dist-server/server/admission.js +81 -0
  77. package/dist-server/server/aside-queue.js +294 -0
  78. package/dist-server/server/attachments.js +98 -1
  79. package/dist-server/server/auto-approve.js +4 -3
  80. package/dist-server/server/auto-vm-claims.js +1 -0
  81. package/dist-server/server/bot-attachment.js +107 -0
  82. package/dist-server/server/bot-overview.js +19 -0
  83. package/dist-server/server/bot-package.js +18 -292
  84. package/dist-server/server/bot-profile.js +5 -17
  85. package/dist-server/server/box-create-idempotency.js +3 -3
  86. package/dist-server/server/box.js +57 -55
  87. package/dist-server/server/browser-proxy.js +3 -2
  88. package/dist-server/server/browser-runtime.js +37 -1
  89. package/dist-server/server/cli-api-setup.js +1 -0
  90. package/dist-server/server/cli.js +2 -2
  91. package/dist-server/server/command-allowlist.js +154 -0
  92. package/dist-server/server/composio.js +297 -1
  93. package/dist-server/server/computer-wait.js +22 -9
  94. package/dist-server/server/config.js +84 -12
  95. package/dist-server/server/connector-advertisement.js +165 -0
  96. package/dist-server/server/connector-proxy.js +14 -2
  97. package/dist-server/server/connector-verdict.js +168 -0
  98. package/dist-server/server/container-computer.js +61 -1
  99. package/dist-server/server/contracts.js +22 -0
  100. package/dist-server/server/default-model-selection.js +9 -0
  101. package/dist-server/server/delegations.js +85 -34
  102. package/dist-server/server/drivers/acp/core.js +80 -20
  103. package/dist-server/server/drivers/acp/cursor.js +3 -7
  104. package/dist-server/server/drivers/acp/droid.js +4 -7
  105. package/dist-server/server/drivers/acp/gemini.js +4 -4
  106. package/dist-server/server/drivers/acp/grok.js +4 -9
  107. package/dist-server/server/drivers/acp/hermes.js +3 -5
  108. package/dist-server/server/drivers/acp/opencode-go.js +2 -5
  109. package/dist-server/server/drivers/acp/qwen.js +9 -15
  110. package/dist-server/server/drivers/agents-call.js +179 -13
  111. package/dist-server/server/drivers/agents-catalog.js +134 -10
  112. package/dist-server/server/drivers/boxagent.js +160 -36
  113. package/dist-server/server/drivers/builtIn.js +2 -0
  114. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  115. package/dist-server/server/drivers/chat-images.js +54 -0
  116. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  117. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  118. package/dist-server/server/drivers/claude.js +38 -19
  119. package/dist-server/server/drivers/codex-catalog.js +14 -6
  120. package/dist-server/server/drivers/codex-identity.js +2 -1
  121. package/dist-server/server/drivers/codex-instructions.js +48 -6
  122. package/dist-server/server/drivers/codex-release.js +48 -0
  123. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  124. package/dist-server/server/drivers/codex.js +102 -55
  125. package/dist-server/server/drivers/mistral.js +115 -0
  126. package/dist-server/server/drivers/openai-chat.js +180 -33
  127. package/dist-server/server/drivers/openai-compat.js +1 -0
  128. package/dist-server/server/drivers/permission-command.js +35 -0
  129. package/dist-server/server/drivers/phone-proxy.js +47 -2
  130. package/dist-server/server/drivers/pi.js +306 -28
  131. package/dist-server/server/drivers/prompt-split.js +110 -0
  132. package/dist-server/server/drivers/session-idle.js +29 -0
  133. package/dist-server/server/env-path.js +15 -0
  134. package/dist-server/server/fleet.js +1 -1
  135. package/dist-server/server/group-thread-usage.js +103 -0
  136. package/dist-server/server/guarded-requests.js +2 -2
  137. package/dist-server/server/harness/registry.js +1 -0
  138. package/dist-server/server/harness-capabilities.js +46 -0
  139. package/dist-server/server/hosted-models.js +1 -1
  140. package/dist-server/server/index.js +2396 -505
  141. package/dist-server/server/local-computer.js +72 -23
  142. package/dist-server/server/managed-desktop.js +1 -1
  143. package/dist-server/server/mcp-bridge.js +6 -6
  144. package/dist-server/server/message-db.js +22 -42
  145. package/dist-server/server/message-file.js +9 -1
  146. package/dist-server/server/message-search-query.js +43 -0
  147. package/dist-server/server/message-search-worker.js +80 -0
  148. package/dist-server/server/message-search.worker.js +21 -0
  149. package/dist-server/server/model-requests.js +248 -0
  150. package/dist-server/server/new-bot-defaults.js +115 -0
  151. package/dist-server/server/options-card.js +21 -0
  152. package/dist-server/server/org-library.js +1065 -0
  153. package/dist-server/server/package-export.js +627 -39
  154. package/dist-server/server/package-import.js +458 -0
  155. package/dist-server/server/package-parts.js +87 -0
  156. package/dist-server/server/peer-approval.js +16 -7
  157. package/dist-server/server/peer-delivery.js +60 -0
  158. package/dist-server/server/peer-roster.js +67 -0
  159. package/dist-server/server/presets.js +492 -0
  160. package/dist-server/server/profile-requests.js +66 -39
  161. package/dist-server/server/profile-revision.js +9 -1
  162. package/dist-server/server/profile-versions.js +6 -3
  163. package/dist-server/server/provider-key-check.js +3 -2
  164. package/dist-server/server/published-teams.js +80 -0
  165. package/dist-server/server/room-handoffs.js +19 -3
  166. package/dist-server/server/routes/bot-presets.js +25 -0
  167. package/dist-server/server/routine-requests.js +35 -10
  168. package/dist-server/server/routines.js +41 -1
  169. package/dist-server/server/section-context.js +10 -0
  170. package/dist-server/server/skill-fetch.js +72 -14
  171. package/dist-server/server/skills.js +35 -61
  172. package/dist-server/server/steer-queue.js +3 -2
  173. package/dist-server/server/store.js +291 -17
  174. package/dist-server/server/surface.js +10 -4
  175. package/dist-server/server/system-prompt.js +38 -6
  176. package/dist-server/server/team-backup.js +6 -2
  177. package/dist-server/server/team-computers.js +10 -0
  178. package/dist-server/server/team-library.js +6 -2
  179. package/dist-server/server/team-manifest.js +4 -3
  180. package/dist-server/server/team-setup-requests.js +139 -27
  181. package/dist-server/server/thread-events.js +20 -2
  182. package/dist-server/server/tightening-requests.js +405 -0
  183. package/dist-server/server/tts/chatterbox.js +27 -5
  184. package/dist-server/server/turn-context.js +2 -0
  185. package/dist-server/server/turn-watchdog.js +12 -3
  186. package/dist-server/server/usage-ledger.js +19 -2
  187. package/dist-server/server/vps-computer.js +139 -51
  188. package/dist-server/server/vps-container-mcp.js +7 -0
  189. package/dist-server/server/vps-ssh.js +36 -7
  190. package/dist-server/server/workspace-backup-policy.js +12 -0
  191. package/dist-server/server/workspace-backup.js +42 -5
  192. package/dist-server/server/workspace-backup.worker.js +10 -0
  193. package/dist-server/server/workspace.js +1 -1
  194. package/dist-server/shared/approval-mode.js +1 -1
  195. package/dist-server/shared/ask-question.js +207 -12
  196. package/dist-server/shared/bot-profile.js +15 -0
  197. package/dist-server/shared/command-allowlist.js +1 -0
  198. package/dist-server/shared/credential-request.js +6 -2
  199. package/dist-server/shared/line-diff.js +23 -0
  200. package/dist-server/shared/model-request.js +1 -0
  201. package/dist-server/shared/options-card.js +51 -0
  202. package/dist-server/shared/package-format.js +967 -0
  203. package/dist-server/shared/profile-request.js +5 -4
  204. package/dist-server/shared/skill-md.js +64 -0
  205. package/dist-server/shared/team-backup.js +7 -0
  206. package/dist-server/shared/tightening-request.js +87 -0
  207. package/dist-server/shared/wire.js +4 -0
  208. package/dist-server/src/lib/composer-attachments.js +25 -3
  209. package/dist-server/vps-container-mcp.js +1132 -171
  210. package/dist-server/workspace-backup.worker.js +19474 -0
  211. package/enterprise/README.md +1 -1
  212. package/package.json +1 -1
  213. package/dist/assets/channel-BXFkgus9.js +0 -1
  214. package/dist/assets/index-BGgqlWPJ.css +0 -1
  215. package/dist/assets/index-BtHo8ZKl.js +0 -577
  216. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
@@ -0,0 +1,107 @@
1
+ // Lets a bot hand a finished file to the user as a real chat attachment.
2
+ // The source is opened with the same hardened resolver the message-file route
3
+ // uses (realpath containment, O_NOFOLLOW, size cap), then copied into the
4
+ // private attachment store, so the chat never needs to reach into a bot's
5
+ // workspace or VM home afterwards.
6
+ import { basename, join, posix } from "node:path";
7
+ import { extensionForFileMime, extensionForMime, FILE_MAX_BYTES, IMAGE_MAX_BYTES, saveFile, saveImage, } from "./attachments.js";
8
+ import { mimeFor, openMessageFile } from "./message-file.js";
9
+ export const ATTACH_SUPPORTED_TYPES = "images (png, jpg, gif, webp), video (mp4, webm, mov), audio (mp3, m4a, aac, wav, ogg, opus, flac), " +
10
+ "pdf, Word/Excel/PowerPoint and OpenDocument files, and csv, tsv, txt, md, json, rtf";
11
+ function statusError(status, message) {
12
+ return Object.assign(new Error(message), { status });
13
+ }
14
+ /** Map a path inside a VM's durable workspace (for example
15
+ * /home/cua/workspace/report.pdf, optionally as a file:// URL) onto the host
16
+ * directory that is bind-mounted there. Anything outside the guest workspace,
17
+ * including `..` escapes, is not a VM path and returns null. */
18
+ export function guestWorkspaceToHost(input, guestRoot, hostRoot) {
19
+ let value = input.trim();
20
+ if (/^file:\/\//i.test(value)) {
21
+ try {
22
+ value = decodeURIComponent(new URL(value).pathname);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ if (!value.startsWith("/"))
29
+ return null;
30
+ const normalized = posix.normalize(value);
31
+ const relative = posix.relative(guestRoot, normalized);
32
+ if (!relative || relative === ".." || relative.startsWith("../") || posix.isAbsolute(relative))
33
+ return null;
34
+ return join(hostRoot, ...relative.split("/"));
35
+ }
36
+ /** A display name for the chat: the model's suggestion if it gave one, else
37
+ * the file's own name. Directory parts are dropped, never trusted. */
38
+ function displayNameFor(requested, fallback) {
39
+ const cleaned = requested?.trim().replace(/\\/g, "/");
40
+ const name = cleaned ? posix.basename(cleaned) : "";
41
+ return name || fallback;
42
+ }
43
+ export async function saveBotAttachment(input) {
44
+ const requested = input.path.trim();
45
+ if (!requested)
46
+ throw statusError(400, "path is required");
47
+ const guestHost = input.guest ? guestWorkspaceToHost(requested, input.guest.root, input.guest.host) : null;
48
+ // A bot with a VM works in it: a relative path means its VM workspace first.
49
+ const roots = input.guest ? [input.guest.host, ...input.roots] : input.roots;
50
+ const file = await openMessageFile(guestHost ?? requested, roots);
51
+ try {
52
+ const mime = mimeFor(file.name);
53
+ const name = displayNameFor(input.name, file.name);
54
+ if (extensionForMime(mime)) {
55
+ if (file.bytes > IMAGE_MAX_BYTES)
56
+ throw statusError(413, `image exceeds ${IMAGE_MAX_BYTES} bytes`);
57
+ const saved = saveImage(await file.handle.readFile(), mime);
58
+ return { attachment: { kind: "image", path: saved.path, mime: saved.mime }, bytes: saved.bytes };
59
+ }
60
+ if (!extensionForFileMime(mime)) {
61
+ throw statusError(415, `${file.name} is not a supported attachment type. Supported: ${ATTACH_SUPPORTED_TYPES}.`);
62
+ }
63
+ if (file.bytes > FILE_MAX_BYTES)
64
+ throw statusError(413, `file exceeds ${FILE_MAX_BYTES} bytes`);
65
+ const saved = await saveFile(file.handle.createReadStream({ start: 0, autoClose: false }), name || basename(file.name), mime, {
66
+ expectedBytes: file.bytes,
67
+ });
68
+ return { attachment: { kind: "file", path: saved.path, mime: saved.mime, name: saved.name }, bytes: saved.bytes };
69
+ }
70
+ finally {
71
+ await file.handle.close().catch(() => undefined);
72
+ }
73
+ }
74
+ /**
75
+ * Attach one file for one provider turn. Two things must hold while the copy
76
+ * is awaited, so both are handled here rather than in the route:
77
+ *
78
+ * - The per-turn count is exact under parallel calls: the slot is reserved
79
+ * before the await, so simultaneous calls cannot all pass the same check.
80
+ * A call that fails or is refused gives its slot back.
81
+ * - Nothing is published for a turn that ended (stopped, replaced, deleted, or
82
+ * moved) while the copy ran. `stillLive` is asked again after the await,
83
+ * just before `publish`, and the copy is discarded when it says no.
84
+ */
85
+ export async function attachForTurn(turn, max, hooks) {
86
+ const used = turn.attachedFiles ?? 0;
87
+ if (used >= max)
88
+ return { status: "limit" };
89
+ turn.attachedFiles = used + 1;
90
+ let saved;
91
+ let published = false;
92
+ try {
93
+ saved = await hooks.save();
94
+ if (!hooks.stillLive())
95
+ return { status: "ended" };
96
+ hooks.publish(saved);
97
+ published = true;
98
+ return { status: "attached", saved };
99
+ }
100
+ finally {
101
+ if (!published) {
102
+ turn.attachedFiles = Math.max(0, (turn.attachedFiles ?? 1) - 1);
103
+ if (saved !== undefined)
104
+ hooks.discard(saved);
105
+ }
106
+ }
107
+ }
@@ -126,6 +126,24 @@ export async function connectedAppsFacts(configured, availability, read) {
126
126
  function couldUseApps(facts) {
127
127
  return facts.bot.composio !== false && facts.connectedApps.configured && Boolean(facts.engine?.composioMcp);
128
128
  }
129
+ /** Summarize a bot's connectorTools record for the Overview. Undefined in,
130
+ * undefined out: a bot with no record keeps legacy all-tools behavior and
131
+ * the field stays off the wire. An explicit record — even an empty one —
132
+ * becomes a (possibly empty) list, sorted by slug so both phones and the
133
+ * web dialog render it in one deterministic order. */
134
+ export function grantsSummary(connectorTools) {
135
+ if (connectorTools === undefined)
136
+ return undefined;
137
+ return Object.entries(connectorTools)
138
+ .map(([slug, grant]) => {
139
+ if (grant.tools === "*")
140
+ return { slug, level: "all", toolCount: 0 };
141
+ if (grant.tools.length > 0)
142
+ return { slug, level: "partial", toolCount: grant.tools.length };
143
+ return { slug, level: "none", toolCount: 0 };
144
+ })
145
+ .sort((a, b) => (a.slug < b.slug ? -1 : 1));
146
+ }
129
147
  function computerReach(computer) {
130
148
  switch (computer) {
131
149
  case "cloud":
@@ -210,6 +228,7 @@ export function buildBotOverview(facts) {
210
228
  reaches: reachesLines(facts),
211
229
  wont: wontLines(facts),
212
230
  recent: facts.recent,
231
+ grants: grantsSummary(facts.bot.connectorTools),
213
232
  setup: setupSteps(facts),
214
233
  };
215
234
  }
@@ -1,300 +1,26 @@
1
- import { z } from "zod";
2
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
3
- import { schemaIssue } from "./schema.js";
4
- import { isSkillName, parseSkillMd, SKILL_FILE_MAX_BYTES } from "./skills.js";
5
- import { BOT_PROFILE_LIMITS } from "../shared/bot-profile.js";
6
- import { normalizeCronSchedule } from "../shared/routine-schedule.js";
1
+ import { stringify as stringifyYaml } from "yaml";
7
2
  import { cronScheduleLabel } from "../shared/cron-label.js";
8
- export const BOT_PACKAGE_FORMAT = "openmaus.package";
9
- export const BOT_PACKAGE_VERSION = 1;
10
- export const BOTMRR_MARKDOWN_VERSION = 1;
3
+ import { BOTMRR_MARKDOWN_VERSION, isPackageDocument, PACKAGE_FORMAT, PACKAGE_V1_MAX_SKILLS, PACKAGE_V1_VERSION, parsePackageV1, utf8Bytes, } from "../shared/package-format.js";
4
+ // The package file format (v1 and v2) lives in shared/package-format.ts: one
5
+ // validation gate for the server, the renderer and Admin. This module keeps
6
+ // the v1 names the v1 exporter and its Markdown playbook are written in, and
7
+ // re-exports the v2 reader so server code has one place to import from.
8
+ export { canonicalJson, downgradeToV1, PACKAGE_MAX_BYTES, PACKAGE_VERSION, PackageFormatError, packageKeys, packageScanFindings, packageSecretFindings, packageSummary, parsePackageDocument, redactPackageSecrets, upgradeV1, } from "../shared/package-format.js";
9
+ export const BOT_PACKAGE_FORMAT = PACKAGE_FORMAT;
10
+ export const BOT_PACKAGE_VERSION = PACKAGE_V1_VERSION;
11
+ export { BOTMRR_MARKDOWN_VERSION };
11
12
  export const BOT_PACKAGE_SKILLS_VERSION = 1;
12
- export const BOT_PACKAGE_MAX_SKILLS = 20;
13
+ export const BOT_PACKAGE_MAX_SKILLS = PACKAGE_V1_MAX_SKILLS;
13
14
  const BOT_PACKAGE_MARKDOWN_MAX_BYTES = 1_000_000;
14
- const COLORS = [
15
- "green",
16
- "blue",
17
- "red",
18
- "orange",
19
- "purple",
20
- "cyan",
21
- "pink",
22
- "yellow",
23
- "teal",
24
- "coral",
25
- ];
26
- const requiredText = (max) => z.string({ error: "must be text" }).trim().min(1, { message: "is required" }).max(max, { message: "is too long" });
27
- const optionalText = (max) => z
28
- .union([z.string({ error: "must be text" }), z.null(), z.undefined()])
29
- .transform((value) => value?.trim() || undefined)
30
- .refine((value) => value === undefined || value.length <= max, { message: "is too long" })
31
- .optional();
32
- const key = requiredText(64).regex(/^[a-z0-9][a-z0-9_-]*$/, {
33
- message: "may only contain lowercase letters, numbers, - and _",
34
- });
35
- const MAX_DATE_MS = 8_640_000_000_000_000;
36
- const skillName = requiredText(64).refine(isSkillName, {
37
- message: "must be a lowercase skill name",
38
- });
39
- const portableSource = optionalText(2_000).refine((value) => value === undefined || !/^(?:[a-z]:[\\/]|[\\/]|\\\\|file:)/i.test(value), { message: "must not be an absolute path" });
40
- const skillInstructions = z
41
- .string({ error: "must be text" })
42
- .min(1, { message: "is required" })
43
- .max(SKILL_FILE_MAX_BYTES, { message: "is too long" })
44
- .refine((value) => Buffer.byteLength(value, "utf8") <= SKILL_FILE_MAX_BYTES, { message: "is too large" })
45
- .refine((value) => /^---\r?\n/.test(value), { message: "must start with SKILL.md frontmatter" });
46
- const portableSkillSchema = z.object({
47
- name: skillName,
48
- description: requiredText(1_024),
49
- source: portableSource,
50
- license: optionalText(200),
51
- compatibility: optionalText(200),
52
- instructions: skillInstructions,
53
- });
54
- const CLOCK_TIME = /^([01]\d|2[0-3]):[0-5]\d$/;
55
- const intervalWeekdays = z.array(z.number().int().min(0).max(6)).min(1).max(7).refine((weekdays) => new Set(weekdays).size === weekdays.length, "must contain unique weekdays");
56
- const intervalWindow = z.object({
57
- start: z.string().regex(CLOCK_TIME, { message: "must use HH:MM" }),
58
- end: z.string().regex(CLOCK_TIME, { message: "must use HH:MM" }),
59
- }).strict().refine(({ start, end }) => start < end, {
60
- message: "must end later on the same day",
61
- });
62
- const packageRoutineScheduleSchema = z.discriminatedUnion("type", [
63
- z.object({ type: z.literal("cron"), expression: requiredText(256), timeZone: requiredText(100) }).strict(),
64
- z.object({ type: z.literal("once"), at: z.number().int() }),
65
- z.object({
66
- type: z.literal("daily"),
67
- time: requiredText(5).regex(CLOCK_TIME, { message: "must use HH:MM" }),
68
- weekdays: z.array(z.number().int().min(0).max(6)).min(1).max(7),
69
- }),
70
- z.object({
71
- type: z.literal("interval"),
72
- everyMinutes: z.number().int().min(5).max(1_440),
73
- anchorAt: z.number().int().nonnegative().max(MAX_DATE_MS),
74
- weekdays: intervalWeekdays.optional(),
75
- window: intervalWindow.optional(),
76
- endsAt: z.number().int().nonnegative().max(MAX_DATE_MS).optional(),
77
- }),
78
- ]).superRefine((schedule, context) => {
79
- if (schedule.type === "cron") {
80
- try {
81
- normalizeCronSchedule(schedule);
82
- }
83
- catch (error) {
84
- context.addIssue({ code: "custom", message: error instanceof Error ? error.message : "Invalid cron schedule" });
85
- }
86
- return;
87
- }
88
- if (schedule.type !== "interval")
89
- return;
90
- if (schedule.window) {
91
- const [startHour, startMinute] = schedule.window.start.split(":").map(Number);
92
- const [endHour, endMinute] = schedule.window.end.split(":").map(Number);
93
- const windowMinutes = endHour * 60 + endMinute - (startHour * 60 + startMinute);
94
- if (windowMinutes < schedule.everyMinutes) {
95
- context.addIssue({
96
- code: "custom",
97
- message: "must be at least as long as the interval cadence",
98
- path: ["window"],
99
- });
100
- }
101
- }
102
- if (schedule.endsAt !== undefined && schedule.endsAt < schedule.anchorAt) {
103
- context.addIssue({
104
- code: "custom",
105
- message: "must not be before the interval anchor",
106
- path: ["endsAt"],
107
- });
108
- }
109
- });
110
- const packageSchema = z.object({
111
- format: z.literal(BOT_PACKAGE_FORMAT, { error: "This is not an OpenMaus package" }),
112
- version: z.literal(BOT_PACKAGE_VERSION, { error: "Package version is not supported" }),
113
- package: z.object({
114
- id: requiredText(80).regex(/^[a-z0-9][a-z0-9-]*$/, { message: "must be a lowercase slug" }),
115
- release: requiredText(30).regex(/^\d+\.\d+\.\d+$/, { message: "must be semantic versioning" }),
116
- name: requiredText(100),
117
- tagline: requiredText(160),
118
- summary: requiredText(2_000),
119
- category: requiredText(80),
120
- author: z.object({ name: requiredText(100), url: optionalText(500) }),
121
- license: requiredText(80),
122
- featured: z.boolean().optional(),
123
- tags: z.array(requiredText(80)).max(30).optional(),
124
- outcomes: z.array(requiredText(240)).min(1).max(12),
125
- setupMinutes: z.number().int().min(1).max(240),
126
- requirements: z.object({
127
- apps: z.array(z.object({
128
- slug: key,
129
- label: requiredText(100),
130
- reason: requiredText(240),
131
- optional: z.boolean().optional(),
132
- })).max(30),
133
- capabilities: z.array(requiredText(80)).max(20),
134
- platforms: z.array(requiredText(80)).max(10).optional(),
135
- }),
136
- agents: z.array(z.object({
137
- key,
138
- name: requiredText(100),
139
- title: optionalText(200),
140
- description: optionalText(4_000),
141
- soul: z.string().refine((value) => Buffer.byteLength(value, "utf8") <= BOT_PROFILE_LIMITS.soul, {
142
- error: "standing instructions must be at most 24000 bytes",
143
- }).optional(),
144
- appearance: z.object({
145
- color: z.enum(COLORS, { error: "is not supported" }),
146
- mascotExpression: optionalText(80),
147
- mascotBody: optionalText(40),
148
- }),
149
- playbooks: z.array(key).max(40).optional(),
150
- skills: z.array(skillName).max(BOT_PACKAGE_MAX_SKILLS).optional(),
151
- })).min(1).max(200),
152
- chiefOfStaff: key.optional(),
153
- rooms: z.array(z.object({
154
- key,
155
- name: requiredText(100),
156
- members: z.array(key).min(1).max(200),
157
- bulletin: optionalText(12_000),
158
- defaultResponder: z.discriminatedUnion("kind", [
159
- z.object({ kind: z.literal("agent"), agent: key }),
160
- z.object({ kind: z.literal("everyone") }),
161
- z.object({ kind: z.literal("mentions") }),
162
- ]),
163
- })).max(30).optional(),
164
- routines: z.array(z.object({
165
- key,
166
- name: requiredText(80),
167
- agent: key,
168
- prompt: requiredText(20_000),
169
- runOn: z.enum(["maus", "cloud"]),
170
- schedule: packageRoutineScheduleSchema,
171
- durationMinutes: z.number().int().min(5).max(240),
172
- timeoutMinutes: z.number().int().min(5).max(240).optional(),
173
- overlap: z.enum(["skip", "queue"]).optional(),
174
- enabledAfterInstall: z.literal(false),
175
- })).max(50).optional(),
176
- playbooks: z.array(z.object({
177
- key,
178
- name: requiredText(100),
179
- summary: requiredText(300),
180
- triggers: z.array(requiredText(100)).min(1).max(30),
181
- instructions: requiredText(24_000),
182
- })).max(80).optional(),
183
- skills: z.object({
184
- version: z.literal(BOT_PACKAGE_SKILLS_VERSION),
185
- entries: z.array(portableSkillSchema).min(1).max(BOT_PACKAGE_MAX_SKILLS),
186
- }).optional(),
187
- examples: z.array(z.object({
188
- title: requiredText(120),
189
- input: requiredText(4_000),
190
- output: requiredText(8_000),
191
- })).max(12).optional(),
192
- }),
193
- });
194
15
  export function isBotPackage(value) {
195
- if (typeof value === "string")
196
- return /^---\r?\n[\s\S]*?\bbotmrr:\s*1\b/m.test(value);
197
- return Boolean(value) && typeof value === "object" && !Array.isArray(value) &&
198
- value.format === BOT_PACKAGE_FORMAT;
16
+ return isPackageDocument(value);
199
17
  }
200
- function markdownDocument(markdown) {
201
- if (Buffer.byteLength(markdown) > BOT_PACKAGE_MARKDOWN_MAX_BYTES)
202
- throw new Error("The bot playbook is too large");
203
- const frontmatter = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
204
- if (!frontmatter)
205
- throw new Error("This Markdown is missing YAML frontmatter");
206
- let metadata;
207
- try {
208
- metadata = parseYaml(frontmatter[1]);
209
- }
210
- catch {
211
- throw new Error("This Markdown has invalid YAML frontmatter");
212
- }
213
- if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
214
- throw new Error("This Markdown is missing its BotMRR blueprint");
215
- }
216
- const { botmrr, ...definition } = metadata;
217
- if (botmrr !== BOTMRR_MARKDOWN_VERSION)
218
- throw new Error("BotMRR Markdown version is not supported");
219
- for (const heading of ["Activation", "Mission", "Outcomes", "Connections", "Team", "Chief of Staff", "Completion rule"]) {
220
- if (!markdown.includes(`## ${heading}`))
221
- throw new Error(`This Markdown is missing its ${heading} section`);
222
- }
223
- return {
224
- format: BOT_PACKAGE_FORMAT,
225
- version: BOT_PACKAGE_VERSION,
226
- package: definition,
227
- };
228
- }
229
- /** Parse and cross-reference one complete, portable package. Unknown fields
230
- * are stripped; ids, grants, credentials, paths, model selections, and
231
- * runtime state therefore cannot ride through the package boundary. */
18
+ /** Parse and cross-reference one complete v1 package. Unknown fields are
19
+ * stripped; ids, grants, credentials, paths, model selections, and runtime
20
+ * state therefore cannot ride through the package boundary. v2 documents
21
+ * go through parsePackageDocument instead. */
232
22
  export function parseBotPackage(value) {
233
- const source = typeof value === "string" ? markdownDocument(value) : value;
234
- const parsed = packageSchema.safeParse(source);
235
- if (!parsed.success)
236
- throw new Error(schemaIssue(parsed.error, "This is not a bot package"));
237
- const pkg = parsed.data.package;
238
- const unique = (values, label) => {
239
- const seen = new Set();
240
- for (const value of values) {
241
- if (seen.has(value))
242
- throw new Error(`Duplicate ${label} key: ${value}`);
243
- seen.add(value);
244
- }
245
- return seen;
246
- };
247
- const agents = unique(pkg.agents.map((agent) => agent.key), "agent");
248
- const playbooks = unique((pkg.playbooks ?? []).map((playbook) => playbook.key), "playbook");
249
- const skills = unique((pkg.skills?.entries ?? []).map((skill) => skill.name), "skill");
250
- unique((pkg.rooms ?? []).map((room) => room.key), "room");
251
- unique((pkg.routines ?? []).map((routine) => routine.key), "routine");
252
- if (pkg.chiefOfStaff && !agents.has(pkg.chiefOfStaff)) {
253
- throw new Error(`Unknown Chief of Staff: ${pkg.chiefOfStaff}`);
254
- }
255
- for (const agent of pkg.agents) {
256
- for (const playbook of agent.playbooks ?? []) {
257
- if (!playbooks.has(playbook))
258
- throw new Error(`Agent ${agent.key} references unknown playbook: ${playbook}`);
259
- }
260
- const assignedSkills = unique(agent.skills ?? [], `skill in agent ${agent.key}`);
261
- for (const skill of assignedSkills) {
262
- if (!skills.has(skill))
263
- throw new Error(`Agent ${agent.key} references unknown skill: ${skill}`);
264
- }
265
- }
266
- const referencedSkills = new Set(pkg.agents.flatMap((agent) => agent.skills ?? []));
267
- for (const skill of pkg.skills?.entries ?? []) {
268
- const parsed = parseSkillMd(skill.instructions);
269
- if ("error" in parsed)
270
- throw new Error(`Skill ${skill.name} is invalid: ${parsed.error}`);
271
- if (parsed.name !== skill.name)
272
- throw new Error(`Skill ${skill.name} does not match its SKILL.md name`);
273
- if (parsed.description !== skill.description)
274
- throw new Error(`Skill ${skill.name} does not match its description`);
275
- for (const field of ["license", "compatibility"]) {
276
- if (skill[field] !== undefined && skill[field] !== parsed[field]) {
277
- throw new Error(`Skill ${skill.name} does not match its ${field}`);
278
- }
279
- }
280
- if (!referencedSkills.has(skill.name))
281
- throw new Error(`Skill ${skill.name} is not referenced by an agent`);
282
- }
283
- for (const room of pkg.rooms ?? []) {
284
- const members = unique(room.members, `member in room ${room.key}`);
285
- for (const member of members) {
286
- if (!agents.has(member))
287
- throw new Error(`Room ${room.key} references unknown agent: ${member}`);
288
- }
289
- if (room.defaultResponder.kind === "agent" && !members.has(room.defaultResponder.agent)) {
290
- throw new Error(`Room ${room.key} has an unknown default responder`);
291
- }
292
- }
293
- for (const routine of pkg.routines ?? []) {
294
- if (!agents.has(routine.agent))
295
- throw new Error(`Routine ${routine.key} references unknown agent: ${routine.agent}`);
296
- }
297
- return parsed.data;
23
+ return parsePackageV1(value);
298
24
  }
299
25
  const list = (values) => values.map((value) => `- ${value}`).join("\n");
300
26
  const WEEKDAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
@@ -368,7 +94,7 @@ export function renderBotPackageMarkdown(document) {
368
94
  ? pkg.requirements.apps.map((app) => `- **${app.label}${app.optional ? " (optional)" : ""}:** ${app.reason}`).join("\n")
369
95
  : "- No connected apps are required.";
370
96
  const markdown = `---\n${frontmatter}\n---\n\n# ${pkg.name}\n\n${pkg.tagline}\n\n> **Give this file to your Chief of Staff.** It is the complete team blueprint. Any agent system can run it; OpenMausBot can also install it directly.\n\n## Activation\n\nYou are the Chief of Staff for this blueprint. Read the whole document before acting. Confirm the user's goal and any missing inputs, then create or delegate to the specialist roles below. Preserve their names, ownership, boundaries, shared-room rules, and playbooks. If your platform cannot literally spawn agents, perform the roles one at a time and keep their outputs clearly separated.\n\nNever request pasted passwords or secret keys. Use the platform's normal connection flow. Do not send messages, publish content, spend money, delete data, or enable a schedule without the user's explicit approval. All routines start paused.\n\n## Mission\n\n${pkg.summary}\n\n## Outcomes\n\n${list(pkg.outcomes)}\n\n## Connections\n\n${connections}\n\n## Team\n\n${agents}\n\n## Chief of Staff\n\nThe Chief of Staff role is \`${pkg.chiefOfStaff ?? pkg.agents[0].key}\`. This role owns delegation, synthesis, conflict resolution, and the final answer to the user.\n${rooms ? `\n## Shared rooms\n\n${rooms}\n` : ""}${routines ? `\n## Suggested routines\n\n${routines}\n` : ""}${playbooks ? `\n## Playbooks\n\n${playbooks}\n` : ""}${examples ? `\n## Example job\n\n${examples}\n` : ""}\n## Completion rule\n\nReturn one clear result to the user, distinguish evidence from inference, cite source links when the work uses external material, and state what still needs human approval or a connected app.\n`;
371
- if (Buffer.byteLength(markdown) > BOT_PACKAGE_MARKDOWN_MAX_BYTES)
97
+ if (utf8Bytes(markdown) > BOT_PACKAGE_MARKDOWN_MAX_BYTES)
372
98
  throw new Error("The bot playbook is too large");
373
99
  return markdown;
374
100
  }
@@ -1,22 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { BOT_AVATAR_CROPS, botAvatarCropSchema, botAvatarUrlSchema } from "../shared/bot-avatar.js";
3
- import { BOT_PROFILE_LIMITS } from "../shared/bot-profile.js";
3
+ import { BOT_PROFILE_LIMITS, fitsOnOneLine } from "../shared/bot-profile.js";
4
4
  import { MASCOT_BODY_IDS, mascotBodySchema } from "../shared/mascot-bodies.js";
5
- /** A name or title is quoted inside prompts and cards as one line — a
6
- * roster entry, a "Name: …" speaker line, the bracketed provenance note.
7
- * Text that can break out of that line (a newline, a control byte, the
8
- * Unicode separators) is refused at the door rather than flattened later,
9
- * so what the person sees in the sidebar is what every prompt sees too.
10
- * Written as a scan because a control-character class is the kind of
11
- * literal the linter (rightly) refuses. */
12
- export function fitsOnOneLine(value) {
13
- for (let i = 0; i < value.length; i += 1) {
14
- const code = value.charCodeAt(i);
15
- if (code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029)
16
- return false;
17
- }
18
- return true;
19
- }
5
+ // Shared with the package format, which checks the same rule on names it
6
+ // carries; re-exported under its historical path.
7
+ export { fitsOnOneLine } from "../shared/bot-profile.js";
20
8
  export const BOT_PROFILE_PATCH_FIELDS = [
21
9
  "name",
22
10
  "title",
@@ -29,7 +17,7 @@ export const BOT_PROFILE_PATCH_FIELDS = [
29
17
  "voice",
30
18
  "speakReplies",
31
19
  ];
32
- const profilePatchSchema = z.object({
20
+ export const profilePatchSchema = z.object({
33
21
  name: z
34
22
  .string({ error: "name must be a string" })
35
23
  .max(BOT_PROFILE_LIMITS.name, { error: "name must be at most 100 characters" })
@@ -33,7 +33,7 @@ function isRequest(value) {
33
33
  }
34
34
  function recoveryStateError(detail, cause) {
35
35
  return Object.assign(new Error(`Cloud computer creation is paused because its recovery state is ${detail}. `
36
- + "Check ascii.dev for an unnamed Box before repairing OpenMausBot's local state."), { status: 503, cause });
36
+ + "Check boat.dev for an unnamed Box before repairing OpenMausBot's local state."), { status: 503, cause });
37
37
  }
38
38
  function loadFresh() {
39
39
  let raw;
@@ -337,7 +337,7 @@ export function beginBoxCreate(botId, requestBody) {
337
337
  // Once the provider forgets the idempotency key, retrying it (or using
338
338
  // a new key) may create a second billable Box. Absence cannot be
339
339
  // inferred from a lost response, so stop for manual reconciliation.
340
- throw recoveryStateError("older than ascii.dev's 24-hour retry window");
340
+ throw recoveryStateError("older than boat.dev's 24-hour retry window");
341
341
  }
342
342
  return { request: { ...pending }, startedNow: false };
343
343
  }
@@ -356,7 +356,7 @@ export function beginBoxCreate(botId, requestBody) {
356
356
  /** Persist the returned identity before the caller attempts to rename it. */
357
357
  export function rememberCreatedBox(request, boxId) {
358
358
  if (!BOX_ID.test(boxId))
359
- throw new Error("ascii.dev returned an invalid cloud computer id");
359
+ throw new Error("boat.dev returned an invalid cloud computer id");
360
360
  return withJournalLock((requests) => {
361
361
  const current = requests.find((candidate) => (candidate.botId === request.botId
362
362
  && candidate.requestBody === request.requestBody