openmausbot 0.1.86 → 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 (166) hide show
  1. package/README.md +2 -2
  2. package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-BTiuL79F.js} +1 -1
  3. package/dist/assets/{arc-DW7mrI4p.js → arc-C88Blc2v.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-BZsJIZrb.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CVeMe9Ah.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-Cedk3Bhx.js} +1 -1
  7. package/dist/assets/channel-taCNRlUU.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-D-VRqzTB.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-BChov01Y.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-C-TfwvHy.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-Db7CGj5Y.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-CNvIfqKT.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-C_bEp5tq.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Coi4BOls.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-CzylEuL3.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-m7p7ztaD.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BMy6o9x2.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-BDQ__0_J.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Djv4ghUb.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-BULrlGYH.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-D-jrT_hU.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-p2UhQmfL.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-DHvGWZ7q.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-ChhKDot-.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-B-T7A43R.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-CJm8ENNY.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-510tlza8.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-PkvRPHel.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-BVBVPoX3.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BgYMVm7L.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-BGqsnc_3.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-D_-UeReH.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-D0uooONx.js} +1 -1
  34. package/dist/assets/index-BwdMAQ6p.css +1 -0
  35. package/dist/assets/index-ByJennr7.js +581 -0
  36. package/dist/assets/{index-h9FFNwTu.js → index-zRsJERGJ.js} +1 -1
  37. package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-B9e1z77f.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-BB4vNwvD.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DpkosOTB.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-DbDP4kUu.js} +1 -1
  41. package/dist/assets/{linear-3su2TG93.js → linear-DBJGPmjT.js} +1 -1
  42. package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-DWPRi-ue.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-nszBbJmx.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-CWykJWu_.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-g3Da-Vnv.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-Wzrlpzgh.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-moHTFMC0.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-CwfXdFH8.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-3ZROYvnq.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-E1TBebP8.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-DHKEU5pu.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BguEj09D.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-C7IDYnhd.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-DCW-F5PW.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-xBMFtWYV.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-B5nIkuH8.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-DYFAhdbr.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-DeXwX6Cz.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist-server/browser-proxy.js +2 -1
  62. package/dist-server/container-mcp.js +1029 -164
  63. package/dist-server/drivers/agents-proxy.js +64 -10
  64. package/dist-server/drivers/phone-proxy.js +33 -2
  65. package/dist-server/index.js +14577 -9001
  66. package/dist-server/local-computer-proxy.js +3 -3
  67. package/dist-server/local-computer.js +53 -17
  68. package/dist-server/openmausbot.js +3981 -1165
  69. package/dist-server/pair-cli.js +3981 -1165
  70. package/dist-server/permission-proxy.js +29 -9
  71. package/dist-server/server/attachments.js +55 -0
  72. package/dist-server/server/auto-approve.js +4 -3
  73. package/dist-server/server/auto-vm-claims.js +1 -0
  74. package/dist-server/server/bot-package.js +18 -292
  75. package/dist-server/server/bot-profile.js +5 -17
  76. package/dist-server/server/box-create-idempotency.js +3 -3
  77. package/dist-server/server/box.js +57 -55
  78. package/dist-server/server/browser-proxy.js +3 -2
  79. package/dist-server/server/browser-runtime.js +37 -1
  80. package/dist-server/server/cli.js +2 -2
  81. package/dist-server/server/command-allowlist.js +154 -0
  82. package/dist-server/server/computer-wait.js +22 -9
  83. package/dist-server/server/config.js +59 -9
  84. package/dist-server/server/connector-verdict.js +141 -0
  85. package/dist-server/server/container-computer.js +1 -1
  86. package/dist-server/server/default-model-selection.js +9 -0
  87. package/dist-server/server/delegations.js +85 -34
  88. package/dist-server/server/drivers/acp/core.js +57 -15
  89. package/dist-server/server/drivers/agents-call.js +42 -3
  90. package/dist-server/server/drivers/agents-catalog.js +34 -4
  91. package/dist-server/server/drivers/boxagent.js +174 -36
  92. package/dist-server/server/drivers/builtIn.js +2 -0
  93. package/dist-server/server/drivers/chat-box-tools.js +90 -0
  94. package/dist-server/server/drivers/chat-images.js +54 -0
  95. package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
  96. package/dist-server/server/drivers/chat-tool-approval.js +70 -18
  97. package/dist-server/server/drivers/claude.js +7 -2
  98. package/dist-server/server/drivers/codex-instructions.js +48 -6
  99. package/dist-server/server/drivers/codex-tool-surface.js +20 -0
  100. package/dist-server/server/drivers/codex.js +95 -30
  101. package/dist-server/server/drivers/mistral.js +115 -0
  102. package/dist-server/server/drivers/openai-chat.js +155 -32
  103. package/dist-server/server/drivers/openai-compat.js +1 -0
  104. package/dist-server/server/drivers/permission-command.js +35 -0
  105. package/dist-server/server/drivers/phone-proxy.js +47 -2
  106. package/dist-server/server/drivers/pi.js +75 -4
  107. package/dist-server/server/drivers/prompt-split.js +87 -0
  108. package/dist-server/server/fleet.js +1 -1
  109. package/dist-server/server/group-thread-usage.js +103 -0
  110. package/dist-server/server/harness/registry.js +1 -0
  111. package/dist-server/server/hosted-models.js +1 -1
  112. package/dist-server/server/index.js +1385 -322
  113. package/dist-server/server/local-computer.js +72 -23
  114. package/dist-server/server/managed-desktop.js +1 -1
  115. package/dist-server/server/mcp-bridge.js +6 -6
  116. package/dist-server/server/new-bot-defaults.js +115 -0
  117. package/dist-server/server/org-library.js +1051 -0
  118. package/dist-server/server/package-export.js +627 -39
  119. package/dist-server/server/package-import.js +458 -0
  120. package/dist-server/server/package-parts.js +87 -0
  121. package/dist-server/server/peer-approval.js +16 -7
  122. package/dist-server/server/peer-roster.js +10 -0
  123. package/dist-server/server/presets.js +492 -0
  124. package/dist-server/server/provider-key-check.js +3 -2
  125. package/dist-server/server/published-teams.js +80 -0
  126. package/dist-server/server/room-handoffs.js +19 -3
  127. package/dist-server/server/routes/bot-presets.js +25 -0
  128. package/dist-server/server/routine-requests.js +6 -3
  129. package/dist-server/server/routines.js +41 -1
  130. package/dist-server/server/section-context.js +10 -0
  131. package/dist-server/server/skills.js +34 -61
  132. package/dist-server/server/steer-queue.js +3 -2
  133. package/dist-server/server/store.js +259 -15
  134. package/dist-server/server/surface.js +10 -4
  135. package/dist-server/server/system-prompt.js +12 -4
  136. package/dist-server/server/team-backup.js +6 -2
  137. package/dist-server/server/team-computers.js +10 -0
  138. package/dist-server/server/team-library.js +6 -2
  139. package/dist-server/server/team-manifest.js +4 -3
  140. package/dist-server/server/team-setup-requests.js +16 -5
  141. package/dist-server/server/thread-events.js +17 -1
  142. package/dist-server/server/turn-context.js +2 -0
  143. package/dist-server/server/turn-watchdog.js +12 -3
  144. package/dist-server/server/usage-ledger.js +12 -1
  145. package/dist-server/server/vps-computer.js +139 -51
  146. package/dist-server/server/vps-container-mcp.js +7 -0
  147. package/dist-server/server/vps-ssh.js +36 -7
  148. package/dist-server/server/workspace-backup-policy.js +12 -0
  149. package/dist-server/server/workspace-backup.js +2 -2
  150. package/dist-server/server/workspace.js +1 -1
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/ask-question.js +188 -12
  153. package/dist-server/shared/bot-profile.js +15 -0
  154. package/dist-server/shared/command-allowlist.js +1 -0
  155. package/dist-server/shared/credential-request.js +1 -1
  156. package/dist-server/shared/package-format.js +967 -0
  157. package/dist-server/shared/skill-md.js +64 -0
  158. package/dist-server/shared/team-backup.js +7 -0
  159. package/dist-server/shared/wire.js +4 -0
  160. package/dist-server/vps-container-mcp.js +1129 -170
  161. package/enterprise/README.md +1 -1
  162. package/package.json +1 -1
  163. package/dist/assets/channel-BXFkgus9.js +0 -1
  164. package/dist/assets/index-BGgqlWPJ.css +0 -1
  165. package/dist/assets/index-BtHo8ZKl.js +0 -577
  166. package/dist/assets/swimlanesDiagram-TC7HE7FX-BWrvB2ak.js +0 -8
@@ -8,10 +8,11 @@ var MAX_OPTIONS = 12;
8
8
  var MAX_QUESTION_TEXT = 400;
9
9
  var MAX_LABEL = 120;
10
10
  var MAX_DESCRIPTION = 400;
11
+ var MAX_CUSTOM_ANSWER = 2e3;
11
12
  function text(value, limit) {
12
13
  if (typeof value !== "string") return void 0;
13
14
  const trimmed = value.trim();
14
- return trimmed ? trimmed.slice(0, limit) : void 0;
15
+ return trimmed ? trimmed.slice(0, limit).trim() : void 0;
15
16
  }
16
17
  function isRecord(value) {
17
18
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -59,19 +60,38 @@ function parseAskQuestions(input) {
59
60
  }
60
61
  return questions.length ? questions : null;
61
62
  }
63
+ var OMB_ASK_FENCE = /(^|\n)[ \t]{0,3}(`{3,}|~{3,})[ \t]*omb-ask[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]{0,3}\2[ \t]*(?=\r?\n|$)/;
64
+ var OMB_ASK_FENCE_GLOBAL = new RegExp(OMB_ASK_FENCE.source, OMB_ASK_FENCE.flags + "g");
65
+ var MAX_ANSWER_ECHO = 6 * MAX_CUSTOM_ANSWER;
62
66
  function questionAnswersByQuestion(message, questions) {
63
- const answers = {};
64
- const known = new Map(questions.map((entry) => [entry.question, entry.question]));
65
- for (const block of message.split("\n\n")) {
66
- const match = /^Q: ([\s\S]+?)\nA: ([\s\S]+)$/.exec(block.trim());
67
- if (!match) continue;
68
- const question = known.get(match[1].trim());
69
- if (question) answers[question] = match[2].trim();
67
+ return questionAnswersById(message, questions.map((question) => ({ id: question.question, question })));
68
+ }
69
+ function questionAnswersById(message, questions) {
70
+ const answers = /* @__PURE__ */ Object.create(null);
71
+ const idByText = /* @__PURE__ */ new Map();
72
+ const ambiguous = /* @__PURE__ */ new Set();
73
+ for (const { id, question } of questions) {
74
+ if (idByText.has(question.question)) {
75
+ ambiguous.add(question.question);
76
+ continue;
77
+ }
78
+ idByText.set(question.question, id);
79
+ }
80
+ const blocks = message.matchAll(/(?:^|\n\n)Q: ([\s\S]+?)\nA: ([\s\S]*?)(?=\n\nQ: |$)/g);
81
+ let sawBlock = false;
82
+ for (const match of blocks) {
83
+ sawBlock = true;
84
+ const asked = match[1].trim();
85
+ if (ambiguous.has(asked)) continue;
86
+ const id = idByText.get(asked);
87
+ const answer = match[2].trim();
88
+ if (id && answer) answers[id] = answer;
70
89
  }
71
90
  if (Object.keys(answers).length) return answers;
91
+ if (sawBlock) return {};
72
92
  const only = questions.length === 1 ? questions[0] : void 0;
73
93
  const flat = message.trim();
74
- return only && flat ? { [only.question]: flat } : {};
94
+ return only && flat ? { [only.id]: flat } : {};
75
95
  }
76
96
 
77
97
  // server/permission-proxy.ts
@@ -539,6 +539,61 @@ export function saveImage(bytes, mime, requestedUploadId) {
539
539
  reservation.release();
540
540
  }
541
541
  }
542
+ /** Persist one synthesized audio note. Providers return audio/mpeg buffers;
543
+ * the same atomic-write and quota discipline as saveImage applies, with a
544
+ * fixed .mp3 extension because the accepted mime determines it exactly. */
545
+ export function saveAudio(bytes, mime) {
546
+ const normalized = mime.split(";")[0].trim().toLowerCase();
547
+ if (normalized !== "audio/mpeg") {
548
+ throw Object.assign(new Error("unsupported audio type"), { status: 400 });
549
+ }
550
+ if (bytes.byteLength === 0)
551
+ throw Object.assign(new Error("empty audio"), { status: 400 });
552
+ if (bytes.byteLength > FILE_MAX_BYTES) {
553
+ throw Object.assign(new Error(`audio exceeds ${FILE_MAX_BYTES} bytes`), { status: 413 });
554
+ }
555
+ ensureAttachmentsDir();
556
+ const reservation = new AttachmentReservation();
557
+ reservation.reserve(bytes.byteLength);
558
+ const id = randomUUID();
559
+ const name = `${id}.mp3`;
560
+ const path = join(ATTACHMENTS_DIR, name);
561
+ const partialPath = join(ATTACHMENTS_DIR, `.openmaus-upload-${id}-${randomUUID()}.partial`);
562
+ activePartials.add(partialPath);
563
+ let partialCleanupFailed = false;
564
+ try {
565
+ writeFileSync(partialPath, bytes, { mode: 0o600, flag: "wx" });
566
+ try {
567
+ linkSync(partialPath, path);
568
+ addCommittedBytes(bytes.byteLength);
569
+ }
570
+ catch (error) {
571
+ if (error.code !== "EEXIST")
572
+ throw error;
573
+ const saved = readFileSync(path);
574
+ if (!saved.equals(bytes)) {
575
+ throw Object.assign(new Error("audio destination already exists with different bytes"), { status: 409 });
576
+ }
577
+ }
578
+ unlinkSync(partialPath);
579
+ return { path, mime: normalized, bytes: bytes.byteLength };
580
+ }
581
+ catch (error) {
582
+ try {
583
+ unlinkSync(partialPath);
584
+ }
585
+ catch {
586
+ partialCleanupFailed = true;
587
+ }
588
+ throw error;
589
+ }
590
+ finally {
591
+ activePartials.delete(partialPath);
592
+ if (partialCleanupFailed)
593
+ invalidateAttachmentAccounting();
594
+ reservation.release();
595
+ }
596
+ }
542
597
  /** HTTP uploads take the same per-ID lock as streamed files. saveImage stays
543
598
  * synchronous for generated avatars, while this wrapper prevents an image
544
599
  * and a document using the same caller-supplied UUID from committing with
@@ -4,8 +4,7 @@
4
4
  // provider's own permission mode passed straight through (Claude `auto`,
5
5
  // Grok `--permission-mode`, Codex `approvalsReviewer`, …), and a request that
6
6
  // reaches this process is one the provider left for a person. The only
7
- // verdict the app synthesizes is Full access, because that level is the
8
- // person's explicit, separately confirmed grant to answer every prompt.
7
+ // grants the app applies are Full access and the person's exact saved commands.
9
8
  // Questions never come through here: a bot's question always reaches a human.
10
9
  import { supportsApprovalMode } from "../shared/approval-mode.js";
11
10
  /** A failed delivery is a runtime error, not another permission decision.
@@ -55,7 +54,7 @@ export function delegationInheritsFullAccess(input) {
55
54
  // the permission path in permission-proxy). Approving it there does not
56
55
  // produce an answer: the CLI runs the tool with none and the model is told
57
56
  // "The user did not answer the questions." — a question silently lost.
58
- const ASKS_A_PERSON = new Set(["askuserquestion", "ask_user"]);
57
+ const ASKS_A_PERSON = new Set(["askuserquestion", "ask_user", "omb-ask"]);
59
58
  export function autoVerdict(mode, tool, context) {
60
59
  // A question is for a person, whatever channel it arrived on — and
61
60
  // whatever the mode: even Full has no answer to give, only an approval
@@ -70,6 +69,8 @@ export function autoVerdict(mode, tool, context) {
70
69
  return { approve: `approved ${tool} (full access)`, source: "full-access" };
71
70
  if (context?.requiresExplicitApproval)
72
71
  return { approve: null, source: "explicit-approval-block" };
72
+ if (context?.commandAllowed)
73
+ return { approve: `approved ${tool} (saved command)`, source: "command-allowlist" };
73
74
  if (mode === "auto" || mode === "custom")
74
75
  return { approve: null, source: "native-approval" };
75
76
  return { approve: null, source: "no-grant" };
@@ -18,5 +18,6 @@ export function startAutoVmClaim(table, threadId, generation) {
18
18
  slot.begin = slot.claim().then(() => { slot.claimed = true; }, (error) => {
19
19
  slot.failed = true;
20
20
  slot.failure = error instanceof Error ? error.message : String(error);
21
+ slot.onRejected?.(slot.failure);
21
22
  });
22
23
  }
@@ -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