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.
- package/README.md +2 -2
- package/dist/assets/{abnfDiagram-O67JEVCF-DpUZNnFD.js → abnfDiagram-O67JEVCF-BTiuL79F.js} +1 -1
- package/dist/assets/{arc-DW7mrI4p.js → arc-C88Blc2v.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CyeZ5WLZ.js → architectureDiagram-NJMV4G6O-BZsJIZrb.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CBVaNjO7.js → blockDiagram-BEXU5L5S-CVeMe9Ah.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-sf8d41uT.js → c4Diagram-YGBWAQC7-Cedk3Bhx.js} +1 -1
- package/dist/assets/channel-taCNRlUU.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BOGvy7Y_.js → chunk-2Q5K7J3B-D-VRqzTB.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BiF9zyiz.js → chunk-3FUC2YCW-BChov01Y.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-EFuZ1SBZ.js → chunk-5VM5RSS4-C-TfwvHy.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-pJLNSAT7.js → chunk-GTNCS2PH-Db7CGj5Y.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Cb-3_3IT.js → chunk-GWA4HPMP-CNvIfqKT.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DAf5aogn.js → chunk-JWPE2WC7-C_bEp5tq.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-DR3kiwZz.js → chunk-NETBCI7D-Coi4BOls.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-BmU8PeyM.js → chunk-WEXAMYUT-CzylEuL3.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Buh8g8OT.js → chunk-XXDRQBXY-m7p7ztaD.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BurfmMWM.js → classDiagram-v2-NBCMYWYE-BMy6o9x2.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-KkGZUEkU.js → cose-bilkent-JH36ORCC-BDQ__0_J.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-C3PpV96d.js → cynefin-OW5HDTMX-Djv4ghUb.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Dt8JwJaI.js → cynefinDiagram-VND7K2PF-BULrlGYH.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DkjN8266.js → dagre-6A5THRUB-D-jrT_hU.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DUAJS0JV.js → diagram-22UHCM2B-p2UhQmfL.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-Dw4KhF1a.js → diagram-3UASUU5V-DHvGWZ7q.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Dd1gmQqf.js → diagram-ATOU4E4O-ChhKDot-.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CjwGs0Yw.js → diagram-CDSNMT55-B-T7A43R.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BL-69Zrq.js → diagram-MLGK6HIB-CJm8ENNY.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-5Zi2N7wo.js → diagram-MPIPVDR6-510tlza8.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-D9NekWUX.js → ebnfDiagram-ZINNZB2B-PkvRPHel.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CkaSQjxw.js → elk-276RUBZZ-BVBVPoX3.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CdAVBV8-.js → erDiagram-OPXOYQCR-BgYMVm7L.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-Bm_lWWH7.js → flowDiagram-KWPJA3E3-BGqsnc_3.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-FZ8ejYWJ.js → ganttDiagram-FUAMR5RP-D_-UeReH.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Dlv9rizZ.js → gitGraphDiagram-X574FWY7-D0uooONx.js} +1 -1
- package/dist/assets/index-BwdMAQ6p.css +1 -0
- package/dist/assets/index-ByJennr7.js +581 -0
- package/dist/assets/{index-h9FFNwTu.js → index-zRsJERGJ.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-BdQ_gW5B.js → infoDiagram-VRGFBTTK-B9e1z77f.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-EwssEp2X.js → ishikawaDiagram-OU5B5YK6-BB4vNwvD.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BX1G1vOd.js → journeyDiagram-ZHPQQLJL-DpkosOTB.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BB4kAsyC.js → kanban-definition-PNTS6WVX-DbDP4kUu.js} +1 -1
- package/dist/assets/{linear-3su2TG93.js → linear-DBJGPmjT.js} +1 -1
- package/dist/assets/{mermaid.core-CDD1E0s9.js → mermaid.core-DWPRi-ue.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-Ba50MsNK.js → mindmap-definition-NLK3R4M7-nszBbJmx.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DZLkkZlM.js → pegDiagram-GJSIUBJH-CWykJWu_.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DaTz77Uq.js → pieDiagram-5QR66LMP-g3Da-Vnv.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-CpaiidpR.js → quadrantDiagram-O4NWA36T-Wzrlpzgh.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-mN4JrMeI.js → railroadDiagram-XR7U4H2S-moHTFMC0.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-BSNDRpg7.js → requirementDiagram-PLB6GJNP-CwfXdFH8.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-FngzGNug.js → sankeyDiagram-IPEJSGJF-3ZROYvnq.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CM4pCC3L.js → sequenceDiagram-PO4LG4MO-E1TBebP8.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CRICHXkw.js → sizeCapture-INFHLROL-DHKEU5pu.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-oyTWpxH2.js → stateDiagram-v2-GCMORJYK-BguEj09D.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-D9h9mPBq.js → swimlanes-2SLR337P-C7IDYnhd.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BJUlN9bW.js → timeline-definition-EJHVYXUP-DCW-F5PW.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-3uBNkbAb.js → usecaseDiagram-POWQR4AR-xBMFtWYV.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-D2odalzT.js → vennDiagram-UO4OBE2U-B5nIkuH8.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DHgFaB9S.js → wardleyDiagram-VNRHLVJA-DYFAhdbr.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-w5GYM2h0.js → xychartDiagram-PMCCYNJV-DeXwX6Cz.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/container-mcp.js +1029 -164
- package/dist-server/drivers/agents-proxy.js +64 -10
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/index.js +14577 -9001
- package/dist-server/local-computer-proxy.js +3 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/openmausbot.js +3981 -1165
- package/dist-server/pair-cli.js +3981 -1165
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/attachments.js +55 -0
- package/dist-server/server/auto-approve.js +4 -3
- package/dist-server/server/auto-vm-claims.js +1 -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.js +2 -2
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +59 -9
- package/dist-server/server/connector-verdict.js +141 -0
- package/dist-server/server/container-computer.js +1 -1
- 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 +57 -15
- package/dist-server/server/drivers/agents-call.js +42 -3
- package/dist-server/server/drivers/agents-catalog.js +34 -4
- package/dist-server/server/drivers/boxagent.js +174 -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 +7 -2
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +95 -30
- package/dist-server/server/drivers/mistral.js +115 -0
- package/dist-server/server/drivers/openai-chat.js +155 -32
- 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 +75 -4
- package/dist-server/server/drivers/prompt-split.js +87 -0
- package/dist-server/server/fleet.js +1 -1
- package/dist-server/server/group-thread-usage.js +103 -0
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/hosted-models.js +1 -1
- package/dist-server/server/index.js +1385 -322
- 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/new-bot-defaults.js +115 -0
- package/dist-server/server/org-library.js +1051 -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-roster.js +10 -0
- package/dist-server/server/presets.js +492 -0
- 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 +6 -3
- package/dist-server/server/routines.js +41 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skills.js +34 -61
- package/dist-server/server/steer-queue.js +3 -2
- package/dist-server/server/store.js +259 -15
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +12 -4
- 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 +16 -5
- package/dist-server/server/thread-events.js +17 -1
- 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 +12 -1
- 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 +2 -2
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/ask-question.js +188 -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 +1 -1
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +7 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/vps-container-mcp.js +1129 -170
- 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
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
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
|
-
//
|
|
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 {
|
|
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
|