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,248 @@
1
+ import { harnessCapabilityLines } from "./harness-capabilities.js";
2
+ import { newId } from "./contracts.js";
3
+ import { redactSecretsInText } from "./redact.js";
4
+ const MAX_REASON = 500;
5
+ const STALE = "This bot's default model changed after this card was prepared. Ask the bot to review it and propose again.";
6
+ const NO_SUCH_BOT = "That bot no longer exists";
7
+ export class ModelRequestError extends Error {
8
+ status;
9
+ constructor(message, status = 400) {
10
+ super(message);
11
+ this.name = "ModelRequestError";
12
+ this.status = status;
13
+ }
14
+ }
15
+ const sameSelection = (a, b) => a.instanceId === b.instanceId && a.model === b.model && a.effort === b.effort && a.variant === b.variant;
16
+ function reasonText(value) {
17
+ if (typeof value !== "string")
18
+ throw new ModelRequestError("reason is required");
19
+ const trimmed = value.trim();
20
+ if (!trimmed)
21
+ throw new ModelRequestError("reason is required");
22
+ if (trimmed.length > MAX_REASON)
23
+ throw new ModelRequestError("reason must be 500 characters or fewer");
24
+ return redactSecretsInText(trimmed);
25
+ }
26
+ /** Structural check only — the injected validateModel hook owns catalog and
27
+ * approval-compatibility checks with the live registry, at propose and again
28
+ * at confirm. */
29
+ function parseSelection(input) {
30
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
31
+ throw new ModelRequestError("model_selection must be an object with instanceId and model");
32
+ }
33
+ const value = input;
34
+ if (typeof value.instanceId !== "string" || !value.instanceId.trim()) {
35
+ throw new ModelRequestError("model_selection.instanceId is required");
36
+ }
37
+ if (typeof value.model !== "string" || !value.model.trim()) {
38
+ throw new ModelRequestError("model_selection.model is required");
39
+ }
40
+ const selection = { instanceId: value.instanceId.trim(), model: value.model.trim() };
41
+ if (value.effort !== undefined) {
42
+ if (typeof value.effort !== "string" || !value.effort.trim())
43
+ throw new ModelRequestError("model_selection.effort must be a level name");
44
+ selection.effort = value.effort;
45
+ }
46
+ if (value.variant !== undefined) {
47
+ if (typeof value.variant !== "string" || !value.variant.trim())
48
+ throw new ModelRequestError("model_selection.variant must be a variant id");
49
+ selection.variant = value.variant;
50
+ }
51
+ return selection;
52
+ }
53
+ export function modelCardCopy(args) {
54
+ const title = args.crossBot
55
+ ? "Change @" + args.targetName + "'s default engine/model?"
56
+ : "Change " + args.targetName + "'s default engine/model?";
57
+ const lines = args.crossBot
58
+ ? ["Whose default model: @" + args.targetName, "Why: " + args.reason]
59
+ : ["Why: " + args.reason];
60
+ lines.push("Default engine/model: " + JSON.stringify(args.before) + " → " + JSON.stringify(args.selection));
61
+ lines.push(...args.capabilityLines);
62
+ // The same consent line the team-setup card shows for a model change:
63
+ // the default moves, saved thread selections do not.
64
+ lines.push("Default models apply to groups and new threads. Every existing thread keeps its current model and permissions.");
65
+ lines.push("Nothing runs.");
66
+ return { title, summary: title + " · default engine/model", detail: lines.join("\n") };
67
+ }
68
+ export class ModelRequestService {
69
+ store;
70
+ now;
71
+ canPersist;
72
+ autoApply;
73
+ validateTarget;
74
+ validateModel;
75
+ driverCapabilities;
76
+ constructor(options) {
77
+ this.store = options.store;
78
+ this.now = options.now ?? Date.now;
79
+ this.canPersist = options.canPersist;
80
+ this.autoApply = options.autoApply;
81
+ this.validateTarget = options.validateTarget;
82
+ this.validateModel = options.validateModel;
83
+ this.driverCapabilities = options.driverCapabilities;
84
+ }
85
+ propose(args) {
86
+ return this.prepare(args);
87
+ }
88
+ submit(args) {
89
+ const proposal = this.prepare(args, true);
90
+ return { ...proposal, state: proposal.result ? "applied" : "pending" };
91
+ }
92
+ prepare(args, submitted = false) {
93
+ const reason = reasonText(args.reason);
94
+ const selection = parseSelection(args.selection);
95
+ const targetBotId = args.targetBotId?.trim() || args.botId;
96
+ const target = this.store.bot(targetBotId);
97
+ if (!target)
98
+ throw new ModelRequestError(NO_SUCH_BOT, 404);
99
+ const crossBot = targetBotId !== args.botId;
100
+ if (crossBot && this.validateTarget) {
101
+ const refusal = this.validateTarget(args.botId, targetBotId);
102
+ if (refusal)
103
+ throw new ModelRequestError(refusal, 403);
104
+ }
105
+ if (sameSelection(selection, target.modelSelection))
106
+ throw new ModelRequestError("Nothing would change");
107
+ if (this.validateModel) {
108
+ const refusal = this.validateModel(selection, target);
109
+ if (refusal)
110
+ throw new ModelRequestError(refusal, 400);
111
+ }
112
+ const requestId = newId();
113
+ const targetName = redactSecretsInText(target.name);
114
+ const before = structuredClone(target.modelSelection);
115
+ const capabilityLines = this.driverCapabilities
116
+ ? harnessCapabilityLines(this.driverCapabilities(before.instanceId), this.driverCapabilities(selection.instanceId))
117
+ : [];
118
+ const payload = {
119
+ version: 1,
120
+ requestId,
121
+ botId: args.botId,
122
+ threadId: args.threadId,
123
+ targetBotId,
124
+ targetName,
125
+ createdAt: this.now(),
126
+ reason,
127
+ selection,
128
+ before,
129
+ };
130
+ const copy = modelCardCopy({ targetName, crossBot, before, selection, reason, capabilityLines });
131
+ const persistence = this.canPersist?.(args.botId, args.threadId);
132
+ if (persistence && !persistence.ok)
133
+ throw new ModelRequestError(persistence.error, persistence.status);
134
+ const automatic = submitted && this.autoApply?.(args.botId, args.threadId) === true;
135
+ const messageInput = {
136
+ role: "bot",
137
+ kind: "options",
138
+ card: {
139
+ title: copy.title,
140
+ subtitle: copy.detail,
141
+ options: automatic ? [] : ["Confirm", "Cancel"],
142
+ ...(automatic ? { dismissed: true } : {}),
143
+ requestId,
144
+ tool: "update_model",
145
+ modelRequest: payload,
146
+ },
147
+ };
148
+ if (args.from)
149
+ messageInput.from = args.from;
150
+ const message = this.store.appendMessage(args.threadId, messageInput);
151
+ const proposal = { requestId, messageId: message.id, title: copy.title, summary: copy.summary, detail: copy.detail };
152
+ if (!automatic)
153
+ return proposal;
154
+ const result = this.resolve({ botId: args.botId, threadId: args.threadId, requestId, behavior: "allow" });
155
+ if (result.state === "applied")
156
+ return { ...proposal, result };
157
+ throw new ModelRequestError(result.state === "invalid" ? result.error : "The model change could not be applied", result.state === "invalid" ? result.status : 409);
158
+ }
159
+ /** Claims a model card even after it was settled, so a duplicate click
160
+ * never re-applies an already-applied change. */
161
+ resolve(args) {
162
+ const message = this.store
163
+ .messagesFor(args.threadId)
164
+ .find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.modelRequest);
165
+ const card = message?.card;
166
+ const payload = card?.modelRequest;
167
+ if (!message || !card || !payload)
168
+ return { claimed: false, state: "not_found" };
169
+ if (payload.requestId !== card.requestId) {
170
+ return { claimed: true, state: "invalid", error: "This model request does not match its card", status: 409 };
171
+ }
172
+ if (args.behavior !== "allow" && args.behavior !== "deny") {
173
+ return { claimed: true, state: "invalid", error: "Model confirmations must be confirmed or cancelled", status: 400 };
174
+ }
175
+ if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
176
+ return { claimed: true, state: "invalid", error: "This model request belongs to another conversation", status: 403 };
177
+ }
178
+ if (card.answered)
179
+ return { claimed: true, state: "already_settled", behavior: card.answered };
180
+ // Cross-bot cards re-check authority on every allow, before any settle
181
+ // path can run: a proposer who lost Chief rights must not confirm, even
182
+ // when the requested selection is already live. Card level, not inside
183
+ // the try, so the crash-recovery catch cannot recast a refusal as an
184
+ // applied change.
185
+ if (args.behavior === "allow" && payload.targetBotId !== payload.botId && this.validateTarget) {
186
+ const refusal = this.validateTarget(payload.botId, payload.targetBotId);
187
+ if (refusal)
188
+ return { claimed: true, state: "invalid", error: refusal, status: 404 };
189
+ }
190
+ try {
191
+ const target = this.store.bot(payload.targetBotId);
192
+ if (!target)
193
+ throw new ModelRequestError(NO_SUCH_BOT, 404);
194
+ if (args.behavior === "deny") {
195
+ this.store.patchMessage(args.threadId, message.id, { card: { ...card, answered: "deny", held: undefined } });
196
+ return { claimed: true, state: "denied" };
197
+ }
198
+ // Idempotent close: if the exact requested selection is already the
199
+ // default (a crash after apply, or the same change arriving twice),
200
+ // settle the card without mutating anything again.
201
+ if (sameSelection(payload.selection, target.modelSelection)) {
202
+ const settled = this.store.patchMessage(args.threadId, message.id, {
203
+ card: { ...card, answered: "allow", held: undefined, modelRequest: { ...payload, appliedAt: payload.appliedAt ?? this.now() } },
204
+ });
205
+ if (!settled)
206
+ throw new ModelRequestError("This model confirmation card is no longer available", 409);
207
+ return { claimed: true, state: "already_settled", behavior: "allow" };
208
+ }
209
+ if (!sameSelection(payload.before, target.modelSelection)) {
210
+ throw new ModelRequestError(STALE, 409);
211
+ }
212
+ if (this.validateModel) {
213
+ const refusal = this.validateModel(payload.selection, target);
214
+ if (refusal)
215
+ throw new ModelRequestError(refusal, 409);
216
+ }
217
+ if (!this.store.applyModelDefault(target.id, payload.selection))
218
+ throw new ModelRequestError(NO_SUCH_BOT, 404);
219
+ const appliedAt = this.now();
220
+ const settled = this.store.patchMessage(args.threadId, message.id, {
221
+ card: { ...card, answered: "allow", held: undefined, modelRequest: { ...payload, appliedAt } },
222
+ });
223
+ if (!settled)
224
+ throw new ModelRequestError("This model confirmation card is no longer available", 409);
225
+ return { claimed: true, state: "applied", targetBotId: target.id };
226
+ }
227
+ catch (error) {
228
+ const status = error instanceof ModelRequestError ? error.status : 400;
229
+ const detail = error instanceof Error ? error.message : String(error);
230
+ const applied = sameSelection(payload.selection, this.store.bot(payload.targetBotId)?.modelSelection ?? payload.before);
231
+ const notice = card.dismissed && card.options.length === 0
232
+ ? "Default model saved. Recording the operation receipt could not finish; the change will not be applied again."
233
+ : "Default model saved. Confirm again to finish recording this decision; the change will not be applied again.";
234
+ try {
235
+ this.store.patchMessage(args.threadId, message.id, {
236
+ card: { ...card, held: applied ? notice : redactSecretsInText(detail).slice(0, 500) },
237
+ });
238
+ }
239
+ catch { /* The idempotent close above still permits a safe retry. */ }
240
+ if (applied)
241
+ return {
242
+ claimed: true, state: "applied", targetBotId: payload.targetBotId,
243
+ settlementPending: true, message: notice,
244
+ };
245
+ return { claimed: true, state: "invalid", error: detail, status };
246
+ }
247
+ }
248
+ }
@@ -0,0 +1,115 @@
1
+ import { z } from "zod";
2
+ import { profilePatchSchema, fitsOnOneLine } from "./bot-profile.js";
3
+ import { EFFORT_LEVELS } from "../shared/wire.js";
4
+ import { isModelVariant } from "./contracts.js";
5
+ import { normalizeCronSchedule } from "../shared/routine-schedule.js";
6
+ const strings = (max, length) => z.array(z.string().max(length)).max(max);
7
+ const clock = z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/);
8
+ const weekdays = z.array(z.number().int().min(0).max(6)).min(1).max(7);
9
+ const timestamp = z.number().int().nonnegative().max(8_640_000_000_000_000);
10
+ export const botDefaultModelSchema = z.object({
11
+ instanceId: z.string().trim().min(1).max(200),
12
+ model: z.string().trim().min(1).max(500),
13
+ effort: z.enum(EFFORT_LEVELS).optional(),
14
+ variant: z.string().refine(isModelVariant, "invalid model variant").optional(),
15
+ }).strict().refine(value => value.effort === undefined || value.variant === undefined, "choose either a model variant or an effort level");
16
+ // Preferences only. Consent flags, live grants, bot IDs and execution state
17
+ // cannot be stored here and therefore cannot be replayed as permission grants.
18
+ export const botDefaultsProfileSchema = profilePatchSchema.extend({
19
+ name: z.string().max(100).refine(fitsOnOneLine, "name must fit on one line").optional(),
20
+ section: z.string().trim().max(60).optional(),
21
+ color: z.enum(["green", "blue", "red", "orange", "purple", "cyan", "pink", "yellow", "teal", "coral"]).optional(),
22
+ mascotExpression: z.string().max(60).nullable().optional(),
23
+ modelSelection: botDefaultModelSchema.optional(),
24
+ computer: z.enum(["cloud", "vm", "local", "browser", "off"]).nullable().optional(),
25
+ cloudBackend: z.enum(["box", "vps"]).optional(),
26
+ autoStartVps: z.boolean().optional(),
27
+ cwd: z.string().max(4096).nullable().optional(),
28
+ approvalMode: z.enum(["ask", "auto", "full", "custom"]).optional(),
29
+ alwaysAllow: strings(200, 500).optional(),
30
+ chiefOfStaff: z.boolean().optional(),
31
+ managedSections: strings(100, 60).optional(),
32
+ approvePeerComms: z.boolean().optional(),
33
+ peers: strings(1000, 100).nullable().optional(),
34
+ composio: z.boolean().optional(),
35
+ browser: z.boolean().optional(),
36
+ browserProfile: z.string().max(100).nullable().optional(),
37
+ mcpServers: strings(100, 100).nullable().optional(),
38
+ parkDirectMessages: z.boolean().optional(),
39
+ }).strict();
40
+ const scheduleSchema = z.discriminatedUnion("type", [
41
+ z.object({ type: z.literal("once"), at: timestamp }).strict(),
42
+ z.object({ type: z.literal("daily"), time: clock, weekdays }).strict(),
43
+ z.object({
44
+ type: z.literal("interval"), everyMinutes: z.number().int().min(5).max(1440), anchorAt: timestamp,
45
+ weekdays: weekdays.nullable().optional(),
46
+ window: z.object({ start: clock, end: clock }).strict().nullable().optional(),
47
+ endsAt: timestamp.nullable().optional(),
48
+ }).strict(),
49
+ z.object({ type: z.literal("cron"), expression: z.string().max(256), timeZone: z.string().max(128) }).strict(),
50
+ ]).superRefine((value, ctx) => {
51
+ if (value.type === "cron") {
52
+ try {
53
+ normalizeCronSchedule(value);
54
+ }
55
+ catch (error) {
56
+ ctx.addIssue({ code: "custom", message: String(error) });
57
+ }
58
+ }
59
+ if (value.type === "interval" && value.window) {
60
+ const minutes = (time) => Number(time.slice(0, 2)) * 60 + Number(time.slice(3));
61
+ if (minutes(value.window.end) - minutes(value.window.start) < value.everyMinutes) {
62
+ ctx.addIssue({ code: "custom", message: "The interval must fit inside its time window" });
63
+ }
64
+ }
65
+ });
66
+ export const botRoutineTemplateSchema = z.object({
67
+ name: z.string().trim().min(1).max(80), prompt: z.string().trim().min(1).max(20_000),
68
+ schedule: scheduleSchema, enabled: z.boolean().optional(),
69
+ runOn: z.enum(["maus", "cloud"]).optional(),
70
+ durationMinutes: z.number().int().min(5).max(240).optional(),
71
+ timeoutMinutes: z.number().min(1).max(1440).nullable().optional(),
72
+ overlap: z.enum(["skip", "queue"]).optional(),
73
+ attachments: z.array(z.object({
74
+ id: z.string().min(1).max(200), kind: z.enum(["file", "image"]),
75
+ name: z.string().min(1).max(255), path: z.string().min(1).max(4096), size: z.number().nonnegative(),
76
+ }).strict()).max(50).optional(),
77
+ }).strict();
78
+ export const botSkillTemplateSchema = z.object({
79
+ name: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(64),
80
+ description: z.string().max(1024),
81
+ warnings: strings(100, 2000).default([]),
82
+ source: z.string().min(1).max(2000),
83
+ text: z.string().refine(value => Buffer.byteLength(value, "utf8") <= 262_144, "Skill exceeds 256 KiB"),
84
+ enabled: z.boolean(),
85
+ }).strict();
86
+ export const newBotDefaultsSchema = z.object({
87
+ profile: botDefaultsProfileSchema.default({}),
88
+ memory: z.record(z.string().regex(/^(?:MEMORY\.md|memory\/[a-zA-Z0-9_-]+\.md)$/), z.string().refine(value => Buffer.byteLength(value, "utf8") <= 262_144, "Memory file exceeds 256 KiB"))
89
+ .refine(value => Object.keys(value).length <= 100, "At most 100 memory files").default({}),
90
+ skills: z.array(botSkillTemplateSchema).max(100).default([]),
91
+ routines: z.array(botRoutineTemplateSchema).max(100).default([]),
92
+ }).strict().refine(value => Buffer.byteLength(JSON.stringify(value), "utf8") <= 900_000, "Bot defaults must fit within 900 KB");
93
+ /** Explicit values (including empty values) win. A resolved renderer draft
94
+ * opts out so deleted fields and template extras are not restored on POST. */
95
+ export function resolveBotCreationDefaults(saved, body) {
96
+ if (body.useDefaults !== undefined && typeof body.useDefaults !== "boolean") {
97
+ throw Object.assign(new Error("useDefaults must be true or false"), { status: 400 });
98
+ }
99
+ const template = newBotDefaultsSchema.parse(body.useDefaults === false ? {} : saved ?? {});
100
+ const checked = botDefaultsProfileSchema.safeParse(body.settings === undefined ? {} : body.settings);
101
+ if (!checked.success)
102
+ throw Object.assign(new Error(checked.error.message), { status: 400 });
103
+ const explicit = checked.data;
104
+ const profile = { ...template.profile, ...explicit };
105
+ for (const key of ["name", "title", "description", "modelSelection", "section"]) {
106
+ if (Object.hasOwn(body, key))
107
+ Object.assign(profile, { [key]: key === "section" && body[key] === null ? "" : body[key] });
108
+ }
109
+ const resolved = botDefaultsProfileSchema.safeParse(profile);
110
+ if (!resolved.success)
111
+ throw Object.assign(new Error(resolved.error.message), { status: 400 });
112
+ if (!resolved.data.name?.trim())
113
+ delete resolved.data.name;
114
+ return { ...template, profile: resolved.data };
115
+ }
@@ -0,0 +1,21 @@
1
+ import { parseOptionsCardInput, WATCHER_OPTIONS_CARD_BOT_ID, } from "../shared/options-card.js";
2
+ /**
3
+ * Persist one passive, legacy options card. It deliberately has no requestId
4
+ * or tool field: clicking it replies to the conversation, but grants no tool
5
+ * approval and performs no external action.
6
+ */
7
+ export function createOptionsCard(args) {
8
+ if (args.bot.id !== WATCHER_OPTIONS_CARD_BOT_ID) {
9
+ return { ok: false, status: 403, error: "create_options_card is not enabled for this bot." };
10
+ }
11
+ const parsed = parseOptionsCardInput(args.input);
12
+ if (!parsed.ok)
13
+ return { ok: false, status: 400, error: parsed.error };
14
+ const message = args.store.appendMessage(args.threadId, {
15
+ role: "bot",
16
+ kind: "options",
17
+ from: { botId: args.bot.id, name: args.bot.name, color: args.bot.color },
18
+ card: parsed.value,
19
+ });
20
+ return { ok: true, messageId: message.id };
21
+ }