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.
- package/README.md +4 -3
- package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
- package/dist/assets/{arc-DW7mrI4p.js → arc-3RwkUjRe.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
- package/dist/assets/channel-CnRnO3tF.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-CHnODCr2.js +583 -0
- package/dist/assets/{index-h9FFNwTu.js → index-CIaHn5NS.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
- package/dist/assets/{linear-3su2TG93.js → linear-C2pWPSxO.js} +1 -1
- package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-CR-j5WQU.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +1032 -165
- package/dist-server/drivers/agents-proxy.js +338 -29
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/index.js +40590 -31811
- package/dist-server/local-computer-proxy.js +5 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-gate.js +2 -0
- package/dist-server/mcp-server.js +5 -3
- package/dist-server/message-search.worker.js +52 -0
- package/dist-server/openmausbot.js +4457 -1399
- package/dist-server/pair-cli.js +4457 -1399
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +98 -1
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -0
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/bot-package.js +18 -292
- package/dist-server/server/bot-profile.js +5 -17
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +57 -55
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +37 -1
- package/dist-server/server/cli-api-setup.js +1 -0
- package/dist-server/server/cli.js +2 -2
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +84 -12
- package/dist-server/server/connector-advertisement.js +165 -0
- package/dist-server/server/connector-proxy.js +14 -2
- package/dist-server/server/connector-verdict.js +168 -0
- package/dist-server/server/container-computer.js +61 -1
- package/dist-server/server/contracts.js +22 -0
- package/dist-server/server/default-model-selection.js +9 -0
- package/dist-server/server/delegations.js +85 -34
- package/dist-server/server/drivers/acp/core.js +80 -20
- package/dist-server/server/drivers/acp/cursor.js +3 -7
- package/dist-server/server/drivers/acp/droid.js +4 -7
- package/dist-server/server/drivers/acp/gemini.js +4 -4
- package/dist-server/server/drivers/acp/grok.js +4 -9
- package/dist-server/server/drivers/acp/hermes.js +3 -5
- package/dist-server/server/drivers/acp/opencode-go.js +2 -5
- package/dist-server/server/drivers/acp/qwen.js +9 -15
- package/dist-server/server/drivers/agents-call.js +179 -13
- package/dist-server/server/drivers/agents-catalog.js +134 -10
- package/dist-server/server/drivers/boxagent.js +160 -36
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/chat-box-tools.js +90 -0
- package/dist-server/server/drivers/chat-images.js +54 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +38 -8
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +38 -19
- package/dist-server/server/drivers/codex-catalog.js +14 -6
- package/dist-server/server/drivers/codex-identity.js +2 -1
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-release.js +48 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +102 -55
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +180 -33
- package/dist-server/server/drivers/openai-compat.js +1 -0
- package/dist-server/server/drivers/permission-command.js +35 -0
- package/dist-server/server/drivers/phone-proxy.js +47 -2
- package/dist-server/server/drivers/pi.js +306 -28
- package/dist-server/server/drivers/prompt-split.js +110 -0
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/harness-capabilities.js +46 -0
- package/dist-server/server/hosted-models.js +1 -1
- package/dist-server/server/index.js +2396 -505
- package/dist-server/server/local-computer.js +72 -23
- package/dist-server/server/managed-desktop.js +1 -1
- package/dist-server/server/mcp-bridge.js +6 -6
- package/dist-server/server/message-db.js +22 -42
- package/dist-server/server/message-file.js +9 -1
- package/dist-server/server/message-search-query.js +43 -0
- package/dist-server/server/message-search-worker.js +80 -0
- package/dist-server/server/message-search.worker.js +21 -0
- package/dist-server/server/model-requests.js +248 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/options-card.js +21 -0
- package/dist-server/server/org-library.js +1065 -0
- package/dist-server/server/package-export.js +627 -39
- package/dist-server/server/package-import.js +458 -0
- package/dist-server/server/package-parts.js +87 -0
- package/dist-server/server/peer-approval.js +16 -7
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +67 -0
- package/dist-server/server/presets.js +492 -0
- package/dist-server/server/profile-requests.js +66 -39
- package/dist-server/server/profile-revision.js +9 -1
- package/dist-server/server/profile-versions.js +6 -3
- package/dist-server/server/provider-key-check.js +3 -2
- package/dist-server/server/published-teams.js +80 -0
- package/dist-server/server/room-handoffs.js +19 -3
- package/dist-server/server/routes/bot-presets.js +25 -0
- package/dist-server/server/routine-requests.js +35 -10
- package/dist-server/server/routines.js +41 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +35 -61
- package/dist-server/server/steer-queue.js +3 -2
- package/dist-server/server/store.js +291 -17
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +38 -6
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-computers.js +10 -0
- package/dist-server/server/team-library.js +6 -2
- package/dist-server/server/team-manifest.js +4 -3
- package/dist-server/server/team-setup-requests.js +139 -27
- package/dist-server/server/thread-events.js +20 -2
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/turn-context.js +2 -0
- package/dist-server/server/turn-watchdog.js +12 -3
- package/dist-server/server/usage-ledger.js +19 -2
- package/dist-server/server/vps-computer.js +139 -51
- package/dist-server/server/vps-container-mcp.js +7 -0
- package/dist-server/server/vps-ssh.js +36 -7
- package/dist-server/server/workspace-backup-policy.js +12 -0
- package/dist-server/server/workspace-backup.js +42 -5
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +207 -12
- package/dist-server/shared/bot-profile.js +15 -0
- package/dist-server/shared/command-allowlist.js +1 -0
- package/dist-server/shared/credential-request.js +6 -2
- package/dist-server/shared/line-diff.js +23 -0
- package/dist-server/shared/model-request.js +1 -0
- package/dist-server/shared/options-card.js +51 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/profile-request.js +5 -4
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +7 -0
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +25 -3
- package/dist-server/vps-container-mcp.js +1132 -171
- package/dist-server/workspace-backup.worker.js +19474 -0
- package/enterprise/README.md +1 -1
- package/package.json +1 -1
- package/dist/assets/channel-BXFkgus9.js +0 -1
- package/dist/assets/index-BGgqlWPJ.css +0 -1
- package/dist/assets/index-BtHo8ZKl.js +0 -577
- 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 {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
|
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("
|
|
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
|