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
|
@@ -29,7 +29,7 @@ function text(value, limit) {
|
|
|
29
29
|
if (typeof value !== "string")
|
|
30
30
|
return undefined;
|
|
31
31
|
const trimmed = value.trim();
|
|
32
|
-
return trimmed ? trimmed.slice(0, limit) : undefined;
|
|
32
|
+
return trimmed ? trimmed.slice(0, limit).trim() : undefined;
|
|
33
33
|
}
|
|
34
34
|
function isRecord(value) {
|
|
35
35
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -94,6 +94,55 @@ export function parseAskQuestions(input) {
|
|
|
94
94
|
}
|
|
95
95
|
return questions.length ? questions : null;
|
|
96
96
|
}
|
|
97
|
+
/** The fenced block a turn-boundary agent ends its run with when it wants
|
|
98
|
+
* to ask the person something (the BoxAgent transport): the harness cannot
|
|
99
|
+
* pause mid-run, so the questions ride the final output and OMB parses them
|
|
100
|
+
* at settle. The block is model-authored — untrusted input like any tool
|
|
101
|
+
* call — so its body runs through parseAskQuestions and the same caps. */
|
|
102
|
+
const 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|$)/;
|
|
103
|
+
const OMB_ASK_FENCE_GLOBAL = new RegExp(OMB_ASK_FENCE.source, OMB_ASK_FENCE.flags + "g");
|
|
104
|
+
/** The questions inside the first fenced omb-ask block in an output, or
|
|
105
|
+
* null when there is nothing worth showing (no fence, invalid JSON, or no
|
|
106
|
+
* entry that parses as a question). The first fence wins: a second block
|
|
107
|
+
* in the same output is ignored, not merged. */
|
|
108
|
+
export function parseOmbAskQuestions(output) {
|
|
109
|
+
const match = OMB_ASK_FENCE.exec(output);
|
|
110
|
+
if (!match)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
return parseAskQuestions(JSON.parse(match[3]));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** The output with its omb-ask block(s) removed, for display: the person
|
|
120
|
+
* reads the prose around the ask, never the raw protocol JSON. Text
|
|
121
|
+
* without a block is returned untouched. */
|
|
122
|
+
export function stripOmbAskBlock(output) {
|
|
123
|
+
if (!OMB_ASK_FENCE.test(output))
|
|
124
|
+
return output;
|
|
125
|
+
return output.replace(OMB_ASK_FENCE_GLOBAL, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
126
|
+
}
|
|
127
|
+
/** The prompt contract that teaches a model the turn-held ask transport: end
|
|
128
|
+
* the reply with a fenced `omb-ask` block carrying the questions, and the
|
|
129
|
+
* answers come back on the next prompt. A harness that cannot pause mid-run
|
|
130
|
+
* (the BoxAgent transport) appends this to every prompt; the caps in the text
|
|
131
|
+
* are the constants above, so the taught contract and the parser cannot
|
|
132
|
+
* drift apart. */
|
|
133
|
+
export function ombAskProtocolPrompt() {
|
|
134
|
+
return [
|
|
135
|
+
"",
|
|
136
|
+
"## Asking the person a question",
|
|
137
|
+
"When a decision belongs to the person, end your reply with a fenced block exactly like this:",
|
|
138
|
+
"",
|
|
139
|
+
"```omb-ask",
|
|
140
|
+
'{"questions":[{"question":"Ship the release now?","header":"Release","options":[{"label":"Ship now"},{"label":"Wait for the QA signoff"}]}]}',
|
|
141
|
+
"```",
|
|
142
|
+
"",
|
|
143
|
+
`The block must be the last thing in your reply. You may ask up to ${MAX_QUESTIONS} questions at once, each with up to ${MAX_OPTIONS} options; the person can always answer in their own words. Their answers arrive on your next prompt as \`Q:\`/\`A:\` lines — never invent them.`,
|
|
144
|
+
].join("\n");
|
|
145
|
+
}
|
|
97
146
|
/** The one line the card subtitle and a spoken prompt show. */
|
|
98
147
|
export function askQuestionSummary(questions) {
|
|
99
148
|
const first = questions[0]?.question ?? "";
|
|
@@ -139,6 +188,61 @@ export function questionChoices(questions) {
|
|
|
139
188
|
return undefined;
|
|
140
189
|
return only.options.map((option) => option.label);
|
|
141
190
|
}
|
|
191
|
+
/** The chat runtime's own ask tool: the OpenAI-compatible engines'
|
|
192
|
+
* counterpart of ASK_USER_QUESTION. Named ask_user so the ASKS_A_PERSON
|
|
193
|
+
* backstop already covers it — no auto mode may answer a question. */
|
|
194
|
+
export const ASK_USER_TOOL = "ask_user";
|
|
195
|
+
/** The ask_user definition shared by every chat-runtime engine, shaped for
|
|
196
|
+
* an OpenAI function-calling tool list. The schema states the caps; the
|
|
197
|
+
* parser enforces them again on arrival, because a model-authored argument
|
|
198
|
+
* is a request to follow the schema, not a guarantee that it did. */
|
|
199
|
+
export const ASK_USER_TOOL_DEFINITION = {
|
|
200
|
+
type: "function",
|
|
201
|
+
function: {
|
|
202
|
+
name: ASK_USER_TOOL,
|
|
203
|
+
description: "Ask the person questions when the decision is theirs. Offer concrete options where they exist; they can always answer in their own words. Ask up to six questions in one call rather than one at a time.",
|
|
204
|
+
parameters: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
questions: {
|
|
208
|
+
type: "array",
|
|
209
|
+
minItems: 1,
|
|
210
|
+
maxItems: MAX_QUESTIONS,
|
|
211
|
+
items: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
question: { type: "string", description: "The question to show the person." },
|
|
215
|
+
header: { type: "string", description: "A short label for the question's tab." },
|
|
216
|
+
multiSelect: { type: "boolean", description: "Whether several options may be picked." },
|
|
217
|
+
options: {
|
|
218
|
+
type: "array",
|
|
219
|
+
maxItems: MAX_OPTIONS,
|
|
220
|
+
description: "Suggested answers; the person may always write their own.",
|
|
221
|
+
items: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
label: { type: "string" },
|
|
225
|
+
description: { type: "string" },
|
|
226
|
+
},
|
|
227
|
+
required: ["label"],
|
|
228
|
+
additionalProperties: false,
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
required: ["question"],
|
|
233
|
+
additionalProperties: false,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
required: ["questions"],
|
|
238
|
+
additionalProperties: false,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
/** The synthetic tool string for an ask parsed out of model-authored final
|
|
243
|
+
* output (the BoxAgent turn-held transport): there is no tool call to name,
|
|
244
|
+
* but the event and the ASKS_A_PERSON backstop need one string. */
|
|
245
|
+
export const OMB_ASK_TOOL = "omb-ask";
|
|
142
246
|
/** The lead-in on a formatted answer. It exists for the model — the answer
|
|
143
247
|
* is delivered on the deny channel, so it has to say what it is — and the
|
|
144
248
|
* card strips it back off when it shows the person what they sent. */
|
|
@@ -164,6 +268,23 @@ export function formatQuestionAnswers(questions, answers) {
|
|
|
164
268
|
export function answerWithoutPreamble(answer) {
|
|
165
269
|
return answer.startsWith(`${ANSWER_PREAMBLE}\n\n`) ? answer.slice(ANSWER_PREAMBLE.length + 2) : answer;
|
|
166
270
|
}
|
|
271
|
+
/** The longest formatted answer OMB will echo back into a follow-up prompt
|
|
272
|
+
* (the BoxAgent continuation). formatQuestionAnswers itself does not
|
|
273
|
+
* truncate — the tool-result channel has no stated limit — but a prompt is
|
|
274
|
+
* not the place to find one: six answers at the custom-answer cap is the
|
|
275
|
+
* most a legitimate reply weighs, so that is the ceiling. */
|
|
276
|
+
export const MAX_ANSWER_ECHO = 6 * MAX_CUSTOM_ANSWER;
|
|
277
|
+
/** Cap a formatted answer for echoing into a follow-up prompt. An over-cap
|
|
278
|
+
* echo is cut back to the last whole block so no partial answer reads as
|
|
279
|
+
* one, and says it was truncated. */
|
|
280
|
+
export function capAnswerEcho(answer, limit = MAX_ANSWER_ECHO) {
|
|
281
|
+
if (answer.length <= limit)
|
|
282
|
+
return answer;
|
|
283
|
+
const cut = answer.slice(0, limit);
|
|
284
|
+
const boundary = cut.lastIndexOf("\n\nQ: ");
|
|
285
|
+
const kept = boundary > 0 ? cut.slice(0, boundary) : cut;
|
|
286
|
+
return kept + "\n\n[answer truncated]";
|
|
287
|
+
}
|
|
167
288
|
/**
|
|
168
289
|
* The answer text, read back as one value per question.
|
|
169
290
|
*
|
|
@@ -180,21 +301,95 @@ export function answerWithoutPreamble(answer) {
|
|
|
180
301
|
* With more than one there is, and nothing is filed.
|
|
181
302
|
*/
|
|
182
303
|
export function questionAnswersByQuestion(message, questions) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
304
|
+
return questionAnswersById(message, questions.map(question => ({ id: question.question, question })));
|
|
305
|
+
}
|
|
306
|
+
/** Longest protocol id accepted. Ids are harness-internal keys, never shown
|
|
307
|
+
* on the card, but they ride the reply — so they are capped like the rest. */
|
|
308
|
+
const MAX_QUESTION_ID = 200;
|
|
309
|
+
/** Parse a protocol questions array whose entries carry their own string
|
|
310
|
+
* ids (codex's item/tool/requestUserInput). The same caps and skip rules
|
|
311
|
+
* as parseAskQuestions; an entry without both a usable id and a usable
|
|
312
|
+
* question is skipped rather than fatal, and nothing answerable parses to
|
|
313
|
+
* null. */
|
|
314
|
+
export function parseProtocolAskQuestions(entries) {
|
|
315
|
+
if (!Array.isArray(entries))
|
|
316
|
+
return null;
|
|
317
|
+
const parsed = [];
|
|
318
|
+
const seenIds = new Set();
|
|
319
|
+
for (const raw of entries) {
|
|
320
|
+
if (!isRecord(raw))
|
|
321
|
+
continue;
|
|
322
|
+
const question = parseQuestion(raw);
|
|
323
|
+
// The id is opaque protocol data echoed back verbatim in the answer, so
|
|
324
|
+
// it is preserved exactly as sent — trimmed only to reject an
|
|
325
|
+
// all-whitespace id — and an overlong id is rejected whole rather than
|
|
326
|
+
// truncated. A duplicate id fails the whole request: two questions
|
|
327
|
+
// cannot share one answer slot, and silently dropping one would ask
|
|
328
|
+
// the person a question whose answer could never be delivered.
|
|
329
|
+
const id = raw.id;
|
|
330
|
+
if (!question || typeof id !== "string" || !id.trim() || id.length > MAX_QUESTION_ID)
|
|
331
|
+
continue;
|
|
332
|
+
if (seenIds.has(id))
|
|
333
|
+
return null;
|
|
334
|
+
seenIds.add(id);
|
|
335
|
+
parsed.push({ id, question });
|
|
336
|
+
if (parsed.length === MAX_QUESTIONS)
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
return parsed.length ? parsed : null;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The id-keyed counterpart of questionAnswersByQuestion, for a protocol
|
|
343
|
+
* that answers by id (codex's requestUserInput) rather than by question
|
|
344
|
+
* text.
|
|
345
|
+
*
|
|
346
|
+
* Each protocol entry's id is paired with the AskQuestion parsed from that
|
|
347
|
+
* same entry, so a Q:/A: block the card wrote is matched against the text
|
|
348
|
+
* that produced it and filed under the id that asked. Only ids with a real
|
|
349
|
+
* block are answered — a question left unanswered gets no entry rather
|
|
350
|
+
* than a guess. When the reply carries no blocks at all, the flat path
|
|
351
|
+
* applies with exactly one question: the whole message is that question's
|
|
352
|
+
* answer, as it is for a phone or an older client. With more than one,
|
|
353
|
+
* nothing is filed.
|
|
354
|
+
*/
|
|
355
|
+
export function questionAnswersById(message, questions) {
|
|
356
|
+
const answers = Object.create(null);
|
|
357
|
+
const idByText = new Map();
|
|
358
|
+
const ambiguous = new Set();
|
|
359
|
+
for (const { id, question } of questions) {
|
|
360
|
+
if (idByText.has(question.question)) {
|
|
361
|
+
// Two questions with identical text cannot be told apart in a block;
|
|
362
|
+
// answering either id would be a guess, so neither is answered.
|
|
363
|
+
ambiguous.add(question.question);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
idByText.set(question.question, id);
|
|
367
|
+
}
|
|
368
|
+
// A block runs from one Q: header to the next (blank lines inside an
|
|
369
|
+
// answer are the answer's, not block boundaries), so the reply is scanned
|
|
370
|
+
// for header-to-header spans instead of split on every blank line.
|
|
371
|
+
const blocks = message.matchAll(/(?:^|\n\n)Q: ([\s\S]+?)\nA: ([\s\S]*?)(?=\n\nQ: |$)/g);
|
|
372
|
+
let sawBlock = false;
|
|
373
|
+
for (const match of blocks) {
|
|
374
|
+
sawBlock = true;
|
|
375
|
+
const asked = match[1].trim();
|
|
376
|
+
if (ambiguous.has(asked))
|
|
188
377
|
continue;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
378
|
+
const id = idByText.get(asked);
|
|
379
|
+
const answer = match[2].trim();
|
|
380
|
+
// A blank A: is no answer: the id stays unanswered rather than filed
|
|
381
|
+
// as an empty string.
|
|
382
|
+
if (id && answer)
|
|
383
|
+
answers[id] = answer;
|
|
194
384
|
}
|
|
195
385
|
if (Object.keys(answers).length)
|
|
196
386
|
return answers;
|
|
387
|
+
// A structured reply that matched a block is authoritative even when it
|
|
388
|
+
// answered nothing: falling through to the flat fallback would file the
|
|
389
|
+
// literal Q:/A: text as the person's answer.
|
|
390
|
+
if (sawBlock)
|
|
391
|
+
return {};
|
|
197
392
|
const only = questions.length === 1 ? questions[0] : undefined;
|
|
198
393
|
const flat = message.trim();
|
|
199
|
-
return only && flat ? { [only.
|
|
394
|
+
return only && flat ? { [only.id]: flat } : {};
|
|
200
395
|
}
|
|
@@ -9,3 +9,18 @@ export const BOT_PROFILE_LIMITS = {
|
|
|
9
9
|
voice: 200,
|
|
10
10
|
soul: 24_000,
|
|
11
11
|
};
|
|
12
|
+
/** A name or title is quoted inside prompts and cards as one line — a
|
|
13
|
+
* roster entry, a "Name: …" speaker line, the bracketed provenance note.
|
|
14
|
+
* Text that can break out of that line (a newline, a control byte, the
|
|
15
|
+
* Unicode separators) is refused at the door rather than flattened later,
|
|
16
|
+
* so what the person sees in the sidebar is what every prompt sees too.
|
|
17
|
+
* Written as a scan because a control-character class is the kind of
|
|
18
|
+
* literal the linter (rightly) refuses. */
|
|
19
|
+
export function fitsOnOneLine(value) {
|
|
20
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
21
|
+
const code = value.charCodeAt(i);
|
|
22
|
+
if (code < 0x20 || (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029)
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -14,7 +14,7 @@ export const CREDENTIAL_TARGETS = {
|
|
|
14
14
|
label: "Box API key",
|
|
15
15
|
description: "Gives bots an isolated cloud computer when Box is selected.",
|
|
16
16
|
placeholder: "Paste your Box API key",
|
|
17
|
-
helpUrl: "https://docs.
|
|
17
|
+
helpUrl: "https://docs.boat.dev/api-keys",
|
|
18
18
|
},
|
|
19
19
|
opencodeGoApiKey: {
|
|
20
20
|
label: "OpenCode API key",
|
|
@@ -76,11 +76,15 @@ export function credentialIsConfigured(config, id) {
|
|
|
76
76
|
return Boolean(config.imageGen?.key);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
/** A still-pending credential card for this target — the set a newer
|
|
80
|
+
* request supersedes. Provided, dismissed, and already-superseded cards
|
|
81
|
+
* keep their settled state untouched. */
|
|
82
|
+
export function isPendingCredentialRequest(message, target, requestingBotId, roomThread) {
|
|
80
83
|
return (message.kind === "secret" &&
|
|
81
84
|
message.secret?.target === target &&
|
|
82
85
|
message.secret.provided !== true &&
|
|
83
86
|
message.secret.dismissed !== true &&
|
|
87
|
+
message.secret.superseded !== true &&
|
|
84
88
|
(!roomThread || message.from?.botId === requestingBotId));
|
|
85
89
|
}
|
|
86
90
|
export function credentialResumeOutcome(state) {
|
|
@@ -40,3 +40,26 @@ export function lineDiff(before, after) {
|
|
|
40
40
|
out.push(`+${b[j++]}`);
|
|
41
41
|
return out;
|
|
42
42
|
}
|
|
43
|
+
/** Cap on diff lines rendered inside an approval card. Past this the card
|
|
44
|
+
* shows the complete proposed document instead of a truncated diff, because
|
|
45
|
+
* truncation can hide the very instructions being approved. */
|
|
46
|
+
export const MAX_DIFF_LINES = 400;
|
|
47
|
+
/** The SOUL.md review block shared by the profile and team-setup cards:
|
|
48
|
+
* byte counts, a line diff for reviewable changes, and the complete
|
|
49
|
+
* replacement when the diff would outrun the card. The full replacement is
|
|
50
|
+
* already byte-bounded by profile validation and is readable in the same
|
|
51
|
+
* generic card on desktop and phones. */
|
|
52
|
+
export function soulDiffLines(before, after) {
|
|
53
|
+
const encoder = new TextEncoder();
|
|
54
|
+
const bytesBefore = encoder.encode(before).length;
|
|
55
|
+
const bytesAfter = encoder.encode(after).length;
|
|
56
|
+
const diff = lineDiff(before, after);
|
|
57
|
+
if (diff.length > MAX_DIFF_LINES) {
|
|
58
|
+
return [
|
|
59
|
+
`SOUL.md (${bytesBefore} → ${bytesAfter} bytes):`,
|
|
60
|
+
"Large change — complete proposed instructions (replaces the current SOUL.md):",
|
|
61
|
+
after || "(empty)",
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
return [`SOUL.md (${bytesBefore} → ${bytesAfter} bytes):`, ...diff];
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The one bot allowed to create passive choice cards for the Watcher flow. */
|
|
2
|
+
export const WATCHER_OPTIONS_CARD_BOT_ID = "f08dd8e3-f942-4eb4-8783-df32b26b88a4";
|
|
3
|
+
/** Keep agent-authored cards small enough to remain useful on desktop and mobile. */
|
|
4
|
+
export const OPTIONS_CARD_LIMITS = {
|
|
5
|
+
title: 120,
|
|
6
|
+
subtitle: 1_000,
|
|
7
|
+
option: 120,
|
|
8
|
+
minOptions: 2,
|
|
9
|
+
maxOptions: 6,
|
|
10
|
+
};
|
|
11
|
+
function record(value) {
|
|
12
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function boundedText(value, field, maximum) {
|
|
15
|
+
if (typeof value !== "string")
|
|
16
|
+
return { error: `${field} must be a string.` };
|
|
17
|
+
const text = value.trim();
|
|
18
|
+
if (!text)
|
|
19
|
+
return { error: `${field} must not be blank.` };
|
|
20
|
+
if (text.length > maximum)
|
|
21
|
+
return { error: `${field} must be at most ${maximum} characters.` };
|
|
22
|
+
return text;
|
|
23
|
+
}
|
|
24
|
+
/** Normalize untrusted model input before it is persisted into a native card. */
|
|
25
|
+
export function parseOptionsCardInput(value) {
|
|
26
|
+
if (!record(value))
|
|
27
|
+
return { ok: false, error: "create_options_card needs an object." };
|
|
28
|
+
const title = boundedText(value.title, "title", OPTIONS_CARD_LIMITS.title);
|
|
29
|
+
if (typeof title !== "string")
|
|
30
|
+
return { ok: false, error: title.error };
|
|
31
|
+
const subtitle = boundedText(value.subtitle, "subtitle", OPTIONS_CARD_LIMITS.subtitle);
|
|
32
|
+
if (typeof subtitle !== "string")
|
|
33
|
+
return { ok: false, error: subtitle.error };
|
|
34
|
+
if (!Array.isArray(value.options))
|
|
35
|
+
return { ok: false, error: "options must be an array of strings." };
|
|
36
|
+
if (value.options.length < OPTIONS_CARD_LIMITS.minOptions || value.options.length > OPTIONS_CARD_LIMITS.maxOptions) {
|
|
37
|
+
return { ok: false, error: `options must contain ${OPTIONS_CARD_LIMITS.minOptions}-${OPTIONS_CARD_LIMITS.maxOptions} items.` };
|
|
38
|
+
}
|
|
39
|
+
const options = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
for (let index = 0; index < value.options.length; index += 1) {
|
|
42
|
+
const option = boundedText(value.options[index], `options[${index}]`, OPTIONS_CARD_LIMITS.option);
|
|
43
|
+
if (typeof option !== "string")
|
|
44
|
+
return { ok: false, error: option.error };
|
|
45
|
+
if (seen.has(option))
|
|
46
|
+
return { ok: false, error: `options must be unique; "${option}" is repeated.` };
|
|
47
|
+
seen.add(option);
|
|
48
|
+
options.push(option);
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, value: { title, subtitle, options } };
|
|
51
|
+
}
|