openmausbot 0.1.85 → 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/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/abnfDiagram-O67JEVCF-BTiuL79F.js +1 -0
- package/dist/assets/arc-C88Blc2v.js +1 -0
- package/dist/assets/architectureDiagram-NJMV4G6O-BZsJIZrb.js +36 -0
- package/dist/assets/blockDiagram-BEXU5L5S-CVeMe9Ah.js +140 -0
- package/dist/assets/c4Diagram-YGBWAQC7-Cedk3Bhx.js +38 -0
- package/dist/assets/channel-taCNRlUU.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-D-VRqzTB.js +1 -0
- package/dist/assets/chunk-3FUC2YCW-BChov01Y.js +2 -0
- package/dist/assets/chunk-5VM5RSS4-C-TfwvHy.js +15 -0
- package/dist/assets/chunk-GTNCS2PH-Db7CGj5Y.js +88 -0
- package/dist/assets/chunk-GWA4HPMP-CNvIfqKT.js +1 -0
- package/dist/assets/chunk-JWPE2WC7-C_bEp5tq.js +1 -0
- package/dist/assets/chunk-NETBCI7D-Coi4BOls.js +1 -0
- package/dist/assets/chunk-WEXAMYUT-CzylEuL3.js +1 -0
- package/dist/assets/chunk-XXDRQBXY-m7p7ztaD.js +1 -0
- package/dist/assets/classDiagram-v2-NBCMYWYE-BMy6o9x2.js +217 -0
- package/dist/assets/cose-bilkent-JH36ORCC-BDQ__0_J.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-Djv4ghUb.js +166 -0
- package/dist/assets/cynefinDiagram-VND7K2PF-BULrlGYH.js +62 -0
- package/dist/assets/cytoscape.esm-BB4DxJjf.js +321 -0
- package/dist/assets/dagre-6A5THRUB-D-jrT_hU.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-22UHCM2B-p2UhQmfL.js +200 -0
- package/dist/assets/diagram-3UASUU5V-DHvGWZ7q.js +24 -0
- package/dist/assets/diagram-ATOU4E4O-ChhKDot-.js +3 -0
- package/dist/assets/diagram-CDSNMT55-B-T7A43R.js +30 -0
- package/dist/assets/diagram-MLGK6HIB-CJm8ENNY.js +24 -0
- package/dist/assets/diagram-MPIPVDR6-510tlza8.js +41 -0
- package/dist/assets/ebnfDiagram-ZINNZB2B-PkvRPHel.js +1 -0
- package/dist/assets/elk-276RUBZZ-BVBVPoX3.js +27 -0
- package/dist/assets/erDiagram-OPXOYQCR-BgYMVm7L.js +99 -0
- package/dist/assets/flowDiagram-KWPJA3E3-BGqsnc_3.js +213 -0
- package/dist/assets/ganttDiagram-FUAMR5RP-D_-UeReH.js +292 -0
- package/dist/assets/gitGraphDiagram-X574FWY7-D0uooONx.js +106 -0
- package/dist/assets/graph-D8UmR1vK.js +1 -0
- package/dist/assets/index-BwdMAQ6p.css +1 -0
- package/dist/assets/index-ByJennr7.js +581 -0
- package/dist/assets/{index-CTNNoqSJ.js → index-zRsJERGJ.js} +1 -1
- package/dist/assets/infoDiagram-VRGFBTTK-B9e1z77f.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/ishikawaDiagram-OU5B5YK6-BB4vNwvD.js +70 -0
- package/dist/assets/journeyDiagram-ZHPQQLJL-DpkosOTB.js +139 -0
- package/dist/assets/kanban-definition-PNTS6WVX-DbDP4kUu.js +89 -0
- package/dist/assets/linear-DBJGPmjT.js +1 -0
- package/dist/assets/mermaid.core-DWPRi-ue.js +311 -0
- package/dist/assets/mindmap-definition-NLK3R4M7-nszBbJmx.js +96 -0
- package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/assets/pegDiagram-GJSIUBJH-CWykJWu_.js +1 -0
- package/dist/assets/pieDiagram-5QR66LMP-g3Da-Vnv.js +39 -0
- package/dist/assets/quadrantDiagram-O4NWA36T-Wzrlpzgh.js +7 -0
- package/dist/assets/railroadDiagram-XR7U4H2S-moHTFMC0.js +1 -0
- package/dist/assets/requirementDiagram-PLB6GJNP-CwfXdFH8.js +84 -0
- package/dist/assets/sankeyDiagram-IPEJSGJF-3ZROYvnq.js +40 -0
- package/dist/assets/sequenceDiagram-PO4LG4MO-E1TBebP8.js +169 -0
- package/dist/assets/sizeCapture-INFHLROL-DHKEU5pu.js +1 -0
- package/dist/assets/stateDiagram-v2-GCMORJYK-BguEj09D.js +291 -0
- package/dist/assets/swimlanes-2SLR337P-C7IDYnhd.js +1 -0
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +8 -0
- package/dist/assets/timeline-definition-EJHVYXUP-DCW-F5PW.js +120 -0
- package/dist/assets/usecaseDiagram-POWQR4AR-xBMFtWYV.js +370 -0
- package/dist/assets/vennDiagram-UO4OBE2U-B5nIkuH8.js +34 -0
- package/dist/assets/wardleyDiagram-VNRHLVJA-DYFAhdbr.js +78 -0
- package/dist/assets/xychartDiagram-PMCCYNJV-DeXwX6Cz.js +7 -0
- package/dist/index.html +2 -2
- package/dist-server/browser-proxy.js +2 -1
- package/dist-server/container-mcp.js +1047 -173
- package/dist-server/drivers/agents-proxy.js +16121 -1504
- package/dist-server/drivers/phone-proxy.js +33 -2
- package/dist-server/enterprise/LICENSE +32 -0
- package/dist-server/enterprise/server/index.js +5 -5
- package/dist-server/index.js +16134 -7446
- package/dist-server/local-computer-proxy.js +3 -3
- package/dist-server/local-computer.js +53 -17
- package/dist-server/mcp-server.js +3 -1
- package/dist-server/openmausbot.js +9353 -6124
- package/dist-server/pair-cli.js +9353 -6124
- package/dist-server/permission-proxy.js +29 -9
- package/dist-server/server/admin-activity.js +410 -0
- 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/bot-visibility.js +462 -0
- package/dist-server/server/box-create-idempotency.js +3 -3
- package/dist-server/server/box.js +59 -56
- package/dist-server/server/browser-proxy.js +3 -2
- package/dist-server/server/browser-runtime.js +38 -2
- package/dist-server/server/channel-queue.js +2 -0
- package/dist-server/server/cli-prompts.js +24 -2
- package/dist-server/server/cli.js +81 -12
- package/dist-server/server/command-allowlist.js +154 -0
- package/dist-server/server/composio.js +49 -11
- package/dist-server/server/computer-wait.js +22 -9
- package/dist-server/server/config.js +133 -17
- package/dist-server/server/connector-verdict.js +141 -0
- package/dist-server/server/container-computer.js +1 -1
- package/dist-server/server/decision-log.js +209 -48
- package/dist-server/server/default-model-selection.js +29 -3
- package/dist-server/server/delegations.js +97 -44
- package/dist-server/server/drivers/acp/core.js +163 -33
- package/dist-server/server/drivers/acp/grok.js +4 -3
- package/dist-server/server/drivers/agents-call.js +1162 -0
- package/dist-server/server/drivers/agents-catalog.js +689 -0
- package/dist-server/server/drivers/agents-client.js +28 -0
- package/dist-server/server/drivers/agents-proxy.js +18 -1722
- 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 +55 -10
- package/dist-server/server/drivers/chat-tool-approval.js +70 -18
- package/dist-server/server/drivers/claude.js +28 -8
- package/dist-server/server/drivers/codex-instructions.js +48 -6
- package/dist-server/server/drivers/codex-mcp-names.js +44 -0
- package/dist-server/server/drivers/codex-tool-surface.js +20 -0
- package/dist-server/server/drivers/codex.js +121 -34
- package/dist-server/server/drivers/grok.js +2 -1
- 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 +16 -8
- 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/enterprise.js +97 -16
- package/dist-server/server/external-runtime.js +83 -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 +60 -0
- package/dist-server/server/harness/http.js +61 -0
- package/dist-server/server/harness/registry.js +1 -0
- package/dist-server/server/hosted-models.js +151 -0
- package/dist-server/server/hosted-slack.js +11 -0
- package/dist-server/server/index.js +3091 -658
- package/dist-server/server/local-computer.js +72 -23
- package/dist-server/server/managed-desktop.js +79 -14
- package/dist-server/server/managed-policy.js +153 -0
- package/dist-server/server/mcp-bridge.js +6 -6
- package/dist-server/server/message-db.js +28 -0
- package/dist-server/server/model-context-window.js +7 -0
- package/dist-server/server/model-prices.js +121 -0
- package/dist-server/server/new-bot-defaults.js +115 -0
- package/dist-server/server/notify.js +10 -1
- 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 +23 -2
- 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/recent-work.js +15 -0
- package/dist-server/server/request-auth.js +96 -0
- package/dist-server/server/room-handoffs.js +44 -14
- package/dist-server/server/routes/bot-presets.js +25 -0
- package/dist-server/server/routes/hosted-slack.js +20 -0
- package/dist-server/server/routes/table.js +14 -0
- package/dist-server/server/routine-requests.js +6 -3
- package/dist-server/server/routines.js +63 -1
- package/dist-server/server/section-context.js +10 -0
- package/dist-server/server/skills.js +34 -61
- package/dist-server/server/spend.js +111 -16
- package/dist-server/server/steer-queue.js +9 -3
- package/dist-server/server/store.js +550 -35
- package/dist-server/server/surface.js +10 -4
- package/dist-server/server/system-prompt.js +12 -4
- package/dist-server/server/team-backup.js +16 -3
- 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/thread-starters.js +53 -0
- 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 +43 -9
- 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 +21 -2
- package/dist-server/server/workspace-backup.js +7 -4
- 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/mention-boundary.js +15 -0
- package/dist-server/shared/package-format.js +967 -0
- package/dist-server/shared/routines.js +7 -1
- package/dist-server/shared/skill-md.js +64 -0
- package/dist-server/shared/team-backup.js +9 -0
- package/dist-server/shared/wire.js +4 -0
- package/dist-server/src/lib/composer-attachments.js +16 -0
- package/dist-server/vps-container-mcp.js +1147 -179
- package/enterprise/README.md +33 -12
- package/enterprise/server/index.js +5 -5
- package/package.json +1 -1
- package/dist/assets/index-Ba9G44HI.js +0 -310
- package/dist/assets/index-CkRNp7wX.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Box (
|
|
1
|
+
// Box (boat.dev) provider — the bot's cloud computer. Ported from
|
|
2
2
|
// agentcal-api src/providers/box.js, reshaped per-bot instead of
|
|
3
3
|
// per-customer: every bot gets one persistent box (deterministic name),
|
|
4
4
|
// stop pauses billing while the disk survives, and Join always mints a
|
|
@@ -134,7 +134,7 @@ function boxDeletionOperation(body, boxId, expectedOperationId) {
|
|
|
134
134
|
return { id, kind: "box", targetId: boxId, status: status };
|
|
135
135
|
}
|
|
136
136
|
function deletionBlockedError(boxId) {
|
|
137
|
-
return Object.assign(new Error(`
|
|
137
|
+
return Object.assign(new Error(`boat.dev accepted deletion of ${boxId}, but the deletion operation is blocked — check boat.dev and retry`), { status: 409 });
|
|
138
138
|
}
|
|
139
139
|
function boxDeleteProvedAbsent(result) {
|
|
140
140
|
return result.status === 404 || result.status === 410;
|
|
@@ -194,7 +194,7 @@ async function reconcileRecordedBoxDeletion(cfg, initial, pollDelaysMs = []) {
|
|
|
194
194
|
return "confirmed";
|
|
195
195
|
}
|
|
196
196
|
if (inspected.identity.name !== record.name) {
|
|
197
|
-
throw Object.assign(new Error("A cloud computer being deleted no longer has its remembered name — repair it in
|
|
197
|
+
throw Object.assign(new Error("A cloud computer being deleted no longer has its remembered name — repair it in boat.dev before continuing"), { status: 503 });
|
|
198
198
|
}
|
|
199
199
|
return record.phase === "blocked" ? "blocked" : "pending";
|
|
200
200
|
}
|
|
@@ -251,7 +251,7 @@ async function confirmAcceptedBoxDeletion(cfg, record, acceptedBody, pollDelaysM
|
|
|
251
251
|
finishRecordedBoxDeletion(record.boxId);
|
|
252
252
|
return "confirmed";
|
|
253
253
|
}
|
|
254
|
-
throw Object.assign(new Error(`
|
|
254
|
+
throw Object.assign(new Error(`boat.dev returned an invalid deletion receipt for ${record.boxId}; its deletion fence was kept`), { status: 503 });
|
|
255
255
|
}
|
|
256
256
|
const next = operation.status === "blocked"
|
|
257
257
|
? markBoxDeletionBlocked(record.boxId, operation)
|
|
@@ -270,7 +270,7 @@ async function requestRecordedBoxDeletion(cfg, identity, pollDelaysMs = BOX_DELE
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
catch (error) {
|
|
273
|
-
throw Object.assign(new Error("Could not confirm whether
|
|
273
|
+
throw Object.assign(new Error("Could not confirm whether boat.dev accepted the delete. The computer was kept fenced; retry Delete to reconcile it"), { status: 503, cause: error });
|
|
274
274
|
}
|
|
275
275
|
if (boxDeleteProvedAbsent(removed)) {
|
|
276
276
|
finishRecordedBoxDeletion(identity.boxId);
|
|
@@ -305,12 +305,12 @@ export async function boxNameFor(botId) {
|
|
|
305
305
|
export async function boxNameMatchesBot(botId, name) {
|
|
306
306
|
return name === await boxNameFor(botId) || name === legacyBoxNameFor(botId);
|
|
307
307
|
}
|
|
308
|
-
export async function runCommand(cfg, boxId, command, { timeoutMs = 120_000 } = {}) {
|
|
308
|
+
export async function runCommand(cfg, boxId, command, { timeoutMs = 120_000, signal } = {}) {
|
|
309
309
|
assertBoxNotDeleting(boxId);
|
|
310
310
|
const res = await boxFetch(cfg, `/boxes/${boxId}/commands`, {
|
|
311
311
|
method: "POST",
|
|
312
312
|
body: JSON.stringify({ command }),
|
|
313
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
313
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs),
|
|
314
314
|
});
|
|
315
315
|
const body = await res.json().catch(() => null);
|
|
316
316
|
return {
|
|
@@ -367,12 +367,12 @@ async function waitReady(cfg, boxId, budgetMs = 90_000) {
|
|
|
367
367
|
const boxIdCache = new Map();
|
|
368
368
|
function boxInventoryProblem(status, body) {
|
|
369
369
|
if (status === 401 || status === 403) {
|
|
370
|
-
return "
|
|
370
|
+
return "boat.dev rejected the Box API key — update it in Settings → Connections";
|
|
371
371
|
}
|
|
372
372
|
if (status === 429)
|
|
373
|
-
return "
|
|
373
|
+
return "boat.dev is rate-limiting this account — wait a minute and refresh";
|
|
374
374
|
const message = typeof body?.message === "string" ? body.message.trim() : "";
|
|
375
|
-
return message ? `
|
|
375
|
+
return message ? `boat.dev could not list cloud computers: ${message}` : `boat.dev could not list cloud computers (${status})`;
|
|
376
376
|
}
|
|
377
377
|
function safeBoxState(value) {
|
|
378
378
|
if (typeof value !== "string")
|
|
@@ -391,25 +391,25 @@ async function listBoxPages(cfg) {
|
|
|
391
391
|
listed = await boxJson(cfg, path, { signal: AbortSignal.timeout(20_000) });
|
|
392
392
|
}
|
|
393
393
|
catch {
|
|
394
|
-
return { ok: false, problem: "Could not reach
|
|
394
|
+
return { ok: false, problem: "Could not reach boat.dev to list cloud computers — check your connection and refresh" };
|
|
395
395
|
}
|
|
396
396
|
if (!listed.ok || !Array.isArray(listed.body?.boxes)) {
|
|
397
|
-
return { ok: false, problem: boxInventoryProblem(listed.status, listed.body) };
|
|
397
|
+
return { ok: false, problem: boxInventoryProblem(listed.status, listed.body), credentialRejected: listed.status === 401 || listed.status === 403 };
|
|
398
398
|
}
|
|
399
399
|
boxes.push(...listed.body.boxes);
|
|
400
400
|
const next = listed.body?.pageInfo?.nextCursor;
|
|
401
401
|
if (next === undefined || next === null || next === "")
|
|
402
402
|
return { ok: true, boxes };
|
|
403
403
|
if (typeof next !== "string" || next.length > 4_096) {
|
|
404
|
-
return { ok: false, problem: "
|
|
404
|
+
return { ok: false, problem: "boat.dev returned an invalid cloud computer page cursor" };
|
|
405
405
|
}
|
|
406
406
|
if (seenCursors.has(next)) {
|
|
407
|
-
return { ok: false, problem: "
|
|
407
|
+
return { ok: false, problem: "boat.dev repeated a cloud computer page cursor — refresh and try again" };
|
|
408
408
|
}
|
|
409
409
|
seenCursors.add(next);
|
|
410
410
|
cursor = next;
|
|
411
411
|
}
|
|
412
|
-
return { ok: false, problem: "
|
|
412
|
+
return { ok: false, problem: "boat.dev returned too many cloud computer pages — narrow the account inventory and refresh" };
|
|
413
413
|
}
|
|
414
414
|
/**
|
|
415
415
|
* One account listing for Settings and deletion guards. Only boxes
|
|
@@ -431,6 +431,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
431
431
|
configured: true,
|
|
432
432
|
available: false,
|
|
433
433
|
problem: listed.problem,
|
|
434
|
+
credentialRejected: listed.credentialRejected,
|
|
434
435
|
instances: [],
|
|
435
436
|
};
|
|
436
437
|
}
|
|
@@ -468,12 +469,12 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
468
469
|
continue;
|
|
469
470
|
const matchingRows = candidates.filter((candidate) => candidate?.id === recovery.boxId);
|
|
470
471
|
if (matchingRows.length > 1) {
|
|
471
|
-
return invalidInventory("
|
|
472
|
+
return invalidInventory("boat.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
|
|
472
473
|
}
|
|
473
474
|
if (matchingRows.length === 1) {
|
|
474
475
|
const listedName = typeof matchingRows[0]?.name === "string" ? matchingRows[0].name : "";
|
|
475
476
|
if (listedName !== namedOwner.currentName && listedName !== namedOwner.legacyName) {
|
|
476
|
-
return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in
|
|
477
|
+
return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in boat.dev before continuing");
|
|
477
478
|
}
|
|
478
479
|
continue;
|
|
479
480
|
}
|
|
@@ -489,7 +490,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
489
490
|
}
|
|
490
491
|
if (inspected.identity.name !== namedOwner.currentName
|
|
491
492
|
&& inspected.identity.name !== namedOwner.legacyName) {
|
|
492
|
-
return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in
|
|
493
|
+
return invalidInventory("A remembered cloud computer no longer has its OpenMausBot owner name — repair it in boat.dev before continuing");
|
|
493
494
|
}
|
|
494
495
|
const directCandidate = {
|
|
495
496
|
id: inspected.identity.boxId,
|
|
@@ -521,11 +522,11 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
521
522
|
}
|
|
522
523
|
const matchingRows = candidates.filter((candidate) => candidate?.id === deletion.boxId);
|
|
523
524
|
if (matchingRows.length > 1) {
|
|
524
|
-
return invalidInventory("
|
|
525
|
+
return invalidInventory("boat.dev returned a conflicting id for a cloud computer being deleted");
|
|
525
526
|
}
|
|
526
527
|
if (matchingRows.length === 1) {
|
|
527
528
|
if (matchingRows[0]?.name !== deletion.name) {
|
|
528
|
-
return invalidInventory("A cloud computer being deleted no longer has its remembered name — repair it in
|
|
529
|
+
return invalidInventory("A cloud computer being deleted no longer has its remembered name — repair it in boat.dev before continuing");
|
|
529
530
|
}
|
|
530
531
|
if (getBoxDeletion(deletion.boxId)?.phase === "accepted") {
|
|
531
532
|
matchingRows[0] = { ...matchingRows[0], state: "removing" };
|
|
@@ -573,11 +574,11 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
573
574
|
continue;
|
|
574
575
|
const boxId = typeof candidate.id === "string" ? candidate.id : "";
|
|
575
576
|
if (!BOX_ID.test(boxId)) {
|
|
576
|
-
return invalidInventory("
|
|
577
|
+
return invalidInventory("boat.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
|
|
577
578
|
}
|
|
578
579
|
const existing = ownedBoxByBot.get(owner.botId);
|
|
579
580
|
if (existing && existing !== boxId) {
|
|
580
|
-
return invalidInventory("
|
|
581
|
+
return invalidInventory("boat.dev returned conflicting cloud computers for one OpenMaus bot — repair them in boat.dev before continuing");
|
|
581
582
|
}
|
|
582
583
|
ownedBoxByBot.set(owner.botId, boxId);
|
|
583
584
|
}
|
|
@@ -601,7 +602,7 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
601
602
|
else if (LEGACY_MANAGED_BOX_NAME.test(name)) {
|
|
602
603
|
// Pre-scope names have no installation provenance. A live local bot is
|
|
603
604
|
// the only safe ownership proof; unmatched legacy rows stay provider-
|
|
604
|
-
// managed until the person handles them in
|
|
605
|
+
// managed until the person handles them in boat.dev directly.
|
|
605
606
|
owner = ownerByLegacyName.get(name) ?? null;
|
|
606
607
|
if (!owner)
|
|
607
608
|
continue;
|
|
@@ -614,17 +615,17 @@ export async function listManagedBoxes(cfg, owners, options = {}) {
|
|
|
614
615
|
// deterministic name), silently skipping a malformed/duplicated identity
|
|
615
616
|
// could let bot deletion mistake provider corruption for absence.
|
|
616
617
|
if (!BOX_ID.test(boxId)) {
|
|
617
|
-
return invalidInventory("
|
|
618
|
+
return invalidInventory("boat.dev returned an invalid id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
|
|
618
619
|
}
|
|
619
620
|
if ((boxIdCounts.get(boxId) ?? 0) !== 1 || seenBoxIds.has(boxId)) {
|
|
620
|
-
return invalidInventory("
|
|
621
|
+
return invalidInventory("boat.dev returned a conflicting id for an OpenMaus-managed cloud computer — refresh or repair it in boat.dev");
|
|
621
622
|
}
|
|
622
623
|
if (legacyOwner && owner && options.adoptLegacy !== false) {
|
|
623
624
|
try {
|
|
624
625
|
adoptResolvedBox(owner.botId, boxId);
|
|
625
626
|
}
|
|
626
627
|
catch {
|
|
627
|
-
return invalidInventory("OpenMausBot could not safely remember this legacy cloud computer's owner — repair it in
|
|
628
|
+
return invalidInventory("OpenMausBot could not safely remember this legacy cloud computer's owner — repair it in boat.dev before continuing");
|
|
628
629
|
}
|
|
629
630
|
}
|
|
630
631
|
seenBoxIds.add(boxId);
|
|
@@ -662,7 +663,7 @@ export async function inspectBoxIdentity(cfg, boxId) {
|
|
|
662
663
|
return {
|
|
663
664
|
available: false,
|
|
664
665
|
identity: null,
|
|
665
|
-
problem: "Could not reach
|
|
666
|
+
problem: "Could not reach boat.dev to verify a remembered cloud computer",
|
|
666
667
|
};
|
|
667
668
|
}
|
|
668
669
|
if (inspected.status === 404 || inspected.status === 410) {
|
|
@@ -675,7 +676,7 @@ export async function inspectBoxIdentity(cfg, boxId) {
|
|
|
675
676
|
const returnedId = typeof candidate?.id === "string" ? candidate.id : "";
|
|
676
677
|
const name = typeof candidate?.name === "string" ? candidate.name : "";
|
|
677
678
|
if (returnedId !== boxId || name.length === 0 || name.length > 100 || /[\r\n]/.test(name)) {
|
|
678
|
-
return { available: false, identity: null, problem: "
|
|
679
|
+
return { available: false, identity: null, problem: "boat.dev returned an invalid cloud computer identity" };
|
|
679
680
|
}
|
|
680
681
|
return { available: true, identity: { boxId, name, state: safeBoxState(candidate.state) }, problem: null };
|
|
681
682
|
}
|
|
@@ -716,7 +717,7 @@ const QUIESCE_BROWSER = [
|
|
|
716
717
|
async function stopBox(cfg, boxId) {
|
|
717
718
|
assertBoxNotDeleting(boxId);
|
|
718
719
|
// Browser shutdown is best-effort, but the provider stop is not: Settings
|
|
719
|
-
// must never say a computer is sleeping when
|
|
720
|
+
// must never say a computer is sleeping when boat.dev rejected the action.
|
|
720
721
|
await runCommand(cfg, boxId, QUIESCE_BROWSER, { timeoutMs: 5_000 }).catch(() => null);
|
|
721
722
|
const stopped = await boxJson(cfg, `/boxes/${boxId}/stop`, { method: "POST" });
|
|
722
723
|
if (!stopped.ok)
|
|
@@ -752,7 +753,7 @@ export async function sleepManagedBox(cfg, owners, boxId, claim) {
|
|
|
752
753
|
}
|
|
753
754
|
}
|
|
754
755
|
/** Permanent Settings action. The caller must echo the exact freshly-listed
|
|
755
|
-
* machine name as well as its id;
|
|
756
|
+
* machine name as well as its id; boat.dev independently requires the id in
|
|
756
757
|
* its confirmation header. */
|
|
757
758
|
export async function deleteManagedBox(cfg, owners, boxId, confirmName, claim, options = {}) {
|
|
758
759
|
cfg = snapshotBoxConfig(cfg);
|
|
@@ -811,7 +812,7 @@ export async function findBox(cfg, botId) {
|
|
|
811
812
|
if (direct?.ok && directBox?.id === cachedId && directBox.state !== "error")
|
|
812
813
|
return directBox;
|
|
813
814
|
if (direct?.ok && directBox?.id !== cachedId) {
|
|
814
|
-
throw Object.assign(new Error("
|
|
815
|
+
throw Object.assign(new Error("boat.dev returned an invalid cloud computer identity"), { status: 503 });
|
|
815
816
|
}
|
|
816
817
|
boxIdCache.delete(botId); // gone or broken — fall back to the listing
|
|
817
818
|
}
|
|
@@ -825,7 +826,7 @@ export async function findBox(cfg, botId) {
|
|
|
825
826
|
// discoverable only for this exact local bot id.
|
|
826
827
|
const expected = listed.boxes.filter((candidate) => candidate?.name === name || candidate?.name === legacyName);
|
|
827
828
|
if (expected.some((candidate) => !BOX_ID.test(candidate?.id))) {
|
|
828
|
-
throw Object.assign(new Error("
|
|
829
|
+
throw Object.assign(new Error("boat.dev returned an invalid cloud computer identity"), { status: 503 });
|
|
829
830
|
}
|
|
830
831
|
const found = expected.find((candidate) => candidate.name === name && candidate.state !== "error")
|
|
831
832
|
?? expected.find((candidate) => candidate.name === legacyName && candidate.state !== "error")
|
|
@@ -833,7 +834,7 @@ export async function findBox(cfg, botId) {
|
|
|
833
834
|
if (found) {
|
|
834
835
|
const duplicateId = listed.boxes.filter((candidate) => candidate?.id === found.id).length !== 1;
|
|
835
836
|
if (duplicateId) {
|
|
836
|
-
throw Object.assign(new Error("
|
|
837
|
+
throw Object.assign(new Error("boat.dev returned a conflicting cloud computer identity"), { status: 503 });
|
|
837
838
|
}
|
|
838
839
|
if (found.name === legacyName)
|
|
839
840
|
adoptResolvedBox(botId, found.id);
|
|
@@ -871,14 +872,14 @@ export async function verifyToken(token) {
|
|
|
871
872
|
return {
|
|
872
873
|
ok: false,
|
|
873
874
|
message: token.startsWith("box_")
|
|
874
|
-
? "
|
|
875
|
-
: "That doesn't look like a box API key: they start with box_. Copy the API key from your
|
|
875
|
+
? "boat.dev rejected that token — it may have been revoked or expired. Copy a fresh one from your boat.dev account."
|
|
876
|
+
: "That doesn't look like a box API key: they start with box_. Copy the API key from your boat.dev account (an account or session token won't work here).",
|
|
876
877
|
};
|
|
877
878
|
}
|
|
878
|
-
return { ok: false, message: `
|
|
879
|
+
return { ok: false, message: `boat.dev returned ${res.status} for that token — try again in a moment.` };
|
|
879
880
|
}
|
|
880
881
|
catch {
|
|
881
|
-
return { ok: false, message: "Couldn't reach
|
|
882
|
+
return { ok: false, message: "Couldn't reach boat.dev to check that token — check your connection and retry." };
|
|
882
883
|
}
|
|
883
884
|
}
|
|
884
885
|
/** Turn a provider refusal into something a person can act on. The
|
|
@@ -890,17 +891,17 @@ export function boxErrorMessage(status, what, body) {
|
|
|
890
891
|
const link = typeof body?.error?.details?.billingUrl === "string" ? body.error.details.billingUrl : "";
|
|
891
892
|
if (status === 402) {
|
|
892
893
|
// e.g. "Start the $20/month Box plan to create sandboxes."
|
|
893
|
-
return [theirs || "
|
|
894
|
+
return [theirs || "boat.dev needs a paid Box plan before it will create a computer.", link].filter(Boolean).join(" ");
|
|
894
895
|
}
|
|
895
896
|
if (status === 401 || status === 403) {
|
|
896
|
-
return "your box token was rejected by
|
|
897
|
+
return "your box token was rejected by boat.dev — open App Settings and paste a current token (it starts with box_)";
|
|
897
898
|
}
|
|
898
899
|
if (status === 429) {
|
|
899
|
-
return theirs || "
|
|
900
|
+
return theirs || "boat.dev is rate-limiting this account — wait a minute and try again";
|
|
900
901
|
}
|
|
901
902
|
return theirs ? `${what} failed: ${theirs}` : `${what} failed (${status})`;
|
|
902
903
|
}
|
|
903
|
-
/**
|
|
904
|
+
/** boat.dev trial accounts reject the normal eight-hour auto-stop with a
|
|
904
905
|
* structured `trial_auto_stop_required` refusal. Retry that one condition
|
|
905
906
|
* once at the provider's advertised maximum (or the documented two-hour
|
|
906
907
|
* trial ceiling). Other create failures must retain their original error. */
|
|
@@ -921,7 +922,7 @@ function idempotentCreateInProgress(result) {
|
|
|
921
922
|
}
|
|
922
923
|
/** The keys this OpenMausBot already holds, as the environment its bots'
|
|
923
924
|
* agents read on the box. The box is created with `noEnv: true`, so the
|
|
924
|
-
*
|
|
925
|
+
* boat.dev account's own logins never land in the guest: the box has exactly
|
|
925
926
|
* these and nothing else (see "Whose keys" in the Box integrated-agents docs). */
|
|
926
927
|
export function boxCredentialEnv(cfg, env = process.env) {
|
|
927
928
|
const out = {};
|
|
@@ -990,7 +991,7 @@ async function requestBoxCreate(cfg, botId, ttlSeconds, env) {
|
|
|
990
991
|
});
|
|
991
992
|
}
|
|
992
993
|
catch (error) {
|
|
993
|
-
// A dropped response is ambiguous:
|
|
994
|
+
// A dropped response is ambiguous: boat.dev may already have created
|
|
994
995
|
// the Box. One retry with the same key recovers it safely.
|
|
995
996
|
if (ambiguousRetries++ === 0)
|
|
996
997
|
continue;
|
|
@@ -1083,7 +1084,7 @@ export async function provisionBox(cfg, botId, _botName) {
|
|
|
1083
1084
|
assertBotBoxNotDeleting(botId);
|
|
1084
1085
|
// Provider-side backstop: archives itself (billing pauses, disk
|
|
1085
1086
|
// survives) if every stop path dies. Trial accounts get one narrower
|
|
1086
|
-
// retry when
|
|
1087
|
+
// retry when boat.dev reports their shorter TTL ceiling.
|
|
1087
1088
|
const createRes = await createBox(cfg, botId, credentialEnv);
|
|
1088
1089
|
if (!createRes.ok || !createRes.body?.box?.id) {
|
|
1089
1090
|
throw new Error(boxErrorMessage(createRes.status, "box create", createRes.body));
|
|
@@ -1133,13 +1134,13 @@ export async function provisionBox(cfg, botId, _botName) {
|
|
|
1133
1134
|
}
|
|
1134
1135
|
catch (cleanupError) {
|
|
1135
1136
|
const cleanupMessage = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
1136
|
-
throw new Error(`${originalMessage}. The new computer's deletion was not confirmed: ${cleanupMessage}. Check box ${box.id} in
|
|
1137
|
+
throw new Error(`${originalMessage}. The new computer's deletion was not confirmed: ${cleanupMessage}. Check box ${box.id} in boat.dev.`);
|
|
1137
1138
|
}
|
|
1138
1139
|
if (cleanupConfirmation === "confirmed")
|
|
1139
1140
|
throw error;
|
|
1140
1141
|
boxIdCache.delete(botId);
|
|
1141
|
-
throw Object.assign(new Error(`${originalMessage}.
|
|
1142
|
-
+ `its recovery record was kept, along with its deletion fence. Check box ${box.id} in
|
|
1142
|
+
throw Object.assign(new Error(`${originalMessage}. boat.dev accepted deletion of the new computer, but it is still pending; `
|
|
1143
|
+
+ `its recovery record was kept, along with its deletion fence. Check box ${box.id} in boat.dev.`, { cause: error }), { status: 503 });
|
|
1143
1144
|
}
|
|
1144
1145
|
}
|
|
1145
1146
|
/** Wake the bot's box and return a FRESH desktop URL. */
|
|
@@ -1215,16 +1216,16 @@ export const PANEL_FRAME_QUALITY = 85;
|
|
|
1215
1216
|
// ffmpeg's -q:v runs 2 (best) to 31; 3 lands near JPEG quality 85.
|
|
1216
1217
|
const PANEL_FRAME_FFMPEG_Q = 3;
|
|
1217
1218
|
/** The shell that captures one panel frame on the box. Exported for tests. */
|
|
1218
|
-
export function panelShotCommand({ width = PANEL_FRAME_WIDTH, quality = PANEL_FRAME_QUALITY } = {}) {
|
|
1219
|
+
export function panelShotCommand({ width = PANEL_FRAME_WIDTH, quality = PANEL_FRAME_QUALITY, framePath = PANEL_PATH, nativeSize = false } = {}) {
|
|
1219
1220
|
return [
|
|
1220
1221
|
"export DISPLAY=${DISPLAY:-:0}",
|
|
1221
|
-
`f=${
|
|
1222
|
+
`f=${shellQuote(framePath)}`,
|
|
1222
1223
|
// a stale frame must not pass `test -s` when every capture tool fails
|
|
1223
1224
|
'rm -f "$f"',
|
|
1224
1225
|
'w=$(xdotool getdisplaygeometry 2>/dev/null | cut -d" " -f1)',
|
|
1225
1226
|
'case "$w" in ""|*[!0-9]*) w=0;; esac',
|
|
1226
1227
|
`scrot -o -p -q ${quality} "$f" 2>/dev/null || import -window root -quality ${quality} "$f" 2>/dev/null || ffmpeg -y -f x11grab -draw_mouse 1 -i "$DISPLAY" -frames:v 1 -q:v ${PANEL_FRAME_FFMPEG_Q} "$f" >/dev/null 2>&1`,
|
|
1227
|
-
`if [ "$w" -gt ${width} ] 2>/dev/null && command -v convert >/dev/null 2>&1; then convert "$f" -resize ${width}x -quality ${quality} "$f" 2>/dev/null || true; fi
|
|
1228
|
+
...(nativeSize ? [] : [`if [ "$w" -gt ${width} ] 2>/dev/null && command -v convert >/dev/null 2>&1; then convert "$f" -resize ${width}x -quality ${quality} "$f" 2>/dev/null || true; fi`]),
|
|
1228
1229
|
'test -s "$f" && echo captured',
|
|
1229
1230
|
].join("; ");
|
|
1230
1231
|
}
|
|
@@ -1236,11 +1237,11 @@ const MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
|
1236
1237
|
const FRAME_TOO_LARGE = "the box frame exceeds the 8 MB limit";
|
|
1237
1238
|
/** Read a file off the box as base64 — raw artifact bytes when the API
|
|
1238
1239
|
* supports it (33% less transfer, no JSON envelope), else the files API. */
|
|
1239
|
-
async function readFileBase64(cfg, boxId, path) {
|
|
1240
|
+
async function readFileBase64(cfg, boxId, path, signal) {
|
|
1240
1241
|
let bytes = null;
|
|
1241
1242
|
let tooLarge = false;
|
|
1242
1243
|
try {
|
|
1243
|
-
const res = await boxFetch(cfg, `/boxes/${boxId}/artifacts?path=${encodeURIComponent(path)}
|
|
1244
|
+
const res = await boxFetch(cfg, `/boxes/${boxId}/artifacts?path=${encodeURIComponent(path)}`, { signal });
|
|
1244
1245
|
if (res.ok) {
|
|
1245
1246
|
const declaredLength = res.headers.get("content-length");
|
|
1246
1247
|
if (declaredLength !== null && Number(declaredLength) > MAX_FRAME_BYTES)
|
|
@@ -1257,7 +1258,8 @@ async function readFileBase64(cfg, boxId, path) {
|
|
|
1257
1258
|
}
|
|
1258
1259
|
if (bytes?.length)
|
|
1259
1260
|
return bytes.toString("base64");
|
|
1260
|
-
|
|
1261
|
+
signal?.throwIfAborted();
|
|
1262
|
+
const { ok, body } = await boxJson(cfg, `/boxes/${boxId}/files?path=${encodeURIComponent(path)}&encoding=base64`, { signal });
|
|
1261
1263
|
const content = body?.content;
|
|
1262
1264
|
if (ok && typeof content === "string" && content) {
|
|
1263
1265
|
if (Buffer.byteLength(content, "base64") > MAX_FRAME_BYTES)
|
|
@@ -1268,7 +1270,7 @@ async function readFileBase64(cfg, boxId, path) {
|
|
|
1268
1270
|
}
|
|
1269
1271
|
/** `knownBoxId` skips box resolution entirely — the screen poller holds
|
|
1270
1272
|
* the id for the whole turn and must not re-resolve it every frame. */
|
|
1271
|
-
export async function screenshotBox(cfg, botId, knownBoxId) {
|
|
1273
|
+
export async function screenshotBox(cfg, botId, knownBoxId, options) {
|
|
1272
1274
|
cfg = snapshotBoxConfig(cfg);
|
|
1273
1275
|
let boxId = knownBoxId;
|
|
1274
1276
|
if (!boxId) {
|
|
@@ -1279,11 +1281,12 @@ export async function screenshotBox(cfg, botId, knownBoxId) {
|
|
|
1279
1281
|
throw new Error(`box is ${box.state}`);
|
|
1280
1282
|
boxId = box.id;
|
|
1281
1283
|
}
|
|
1282
|
-
const
|
|
1284
|
+
const framePath = options?.nativeSize ? PANEL_PATH + ".model.jpg" : PANEL_PATH;
|
|
1285
|
+
const out = await runCommand(cfg, boxId, options?.nativeSize ? panelShotCommand({ framePath, nativeSize: true }) : SHOT_CMD, { timeoutMs: 60_000, signal: options?.signal });
|
|
1283
1286
|
if (!/captured/.test(out.stdout)) {
|
|
1284
1287
|
throw new Error(out.stderr.slice(0, 200) || "screen capture failed on the box");
|
|
1285
1288
|
}
|
|
1286
|
-
const data = await readFileBase64(cfg, boxId,
|
|
1289
|
+
const data = await readFileBase64(cfg, boxId, framePath, options?.signal);
|
|
1287
1290
|
if (!data)
|
|
1288
1291
|
throw new Error("could not read the frame back from the box");
|
|
1289
1292
|
return { png: data, format: "jpeg" };
|
|
@@ -69,8 +69,9 @@ export async function browserProxyRequest(frame, connection) {
|
|
|
69
69
|
return { jsonrpc: "2.0", id, result: payload.result };
|
|
70
70
|
}
|
|
71
71
|
catch (error) {
|
|
72
|
-
|
|
73
|
-
? error.message : "Browser connection was interrupted. Do not repeat the action until its state is checked."
|
|
72
|
+
const detail = error instanceof Error && !/fetch failed|abort|timeout/i.test(error.message)
|
|
73
|
+
? error.message : "Browser connection was interrupted. Do not repeat the action until its state is checked.";
|
|
74
|
+
return failure(id, message.method, detail + " This failure does not establish that all browsers are unavailable. Inspect available connections with select_computer if that tool is present. Keep the requested computer and account; do not bypass a permission refusal or human takeover, and do not repeat an uncertain action without checking its result.");
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
function run() {
|
|
@@ -13,7 +13,7 @@ export function browserRuntimeEnv(overrides) {
|
|
|
13
13
|
const env = Object.fromEntries(HOST_ENV.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]));
|
|
14
14
|
return { ...env, ...overrides };
|
|
15
15
|
}
|
|
16
|
-
class TransportError extends Error {
|
|
16
|
+
export class TransportError extends Error {
|
|
17
17
|
}
|
|
18
18
|
/** A server-owned JSONL client. Neither child stderr nor its environment is
|
|
19
19
|
* returned to the agent. Reuse the existing cross-platform spawn/kill rules. */
|
|
@@ -279,11 +279,47 @@ export class BrowserRuntime {
|
|
|
279
279
|
// The model sees slimmed schemas and text-only, bounded results; the
|
|
280
280
|
// launch/session parameters OMB owns never reach the engine from a call.
|
|
281
281
|
const request = method === "tools/call" ? stripHarnessOwnedArguments(params) : params;
|
|
282
|
-
|
|
282
|
+
let result = await entry.client.rpc(method, request);
|
|
283
283
|
beforeDispatch?.(); // A turn revoked while the tool ran receives no result.
|
|
284
284
|
if (method === "tools/list")
|
|
285
285
|
return slimBrowserToolList(result);
|
|
286
286
|
const toolName = request && typeof request === "object" && typeof request.name === "string" ? request.name : undefined;
|
|
287
|
+
if (toolName === "agent_browser_open" && result && typeof result === "object" &&
|
|
288
|
+
result.isError !== true) {
|
|
289
|
+
// Navigation alone does not prove that the requested page loaded.
|
|
290
|
+
// Observe in the same scoped session, without replaying the action.
|
|
291
|
+
beforeDispatch?.();
|
|
292
|
+
if (this.gate(session).owner !== null)
|
|
293
|
+
throw new Error(BROWSER_CONTROL_REFUSAL);
|
|
294
|
+
let observation;
|
|
295
|
+
try {
|
|
296
|
+
observation = await entry.client.rpc("tools/call", {
|
|
297
|
+
name: "agent_browser_snapshot", arguments: { compact: true },
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
// A tool-level refusal does not undo the completed navigation.
|
|
302
|
+
// Transport failures still engage the uncertainty/recovery gate.
|
|
303
|
+
if (error instanceof TransportError)
|
|
304
|
+
throw error;
|
|
305
|
+
observation = { isError: true, content: [] };
|
|
306
|
+
}
|
|
307
|
+
beforeDispatch?.();
|
|
308
|
+
const navigation = result;
|
|
309
|
+
const page = observation;
|
|
310
|
+
const observed = page?.isError !== true && Array.isArray(page?.content) && page.content.some((item) => item && typeof item === "object" && item.type === "text" &&
|
|
311
|
+
typeof item.text === "string" && item.text.trim().length > 0);
|
|
312
|
+
result = {
|
|
313
|
+
content: [
|
|
314
|
+
...(Array.isArray(navigation.content) ? navigation.content : []),
|
|
315
|
+
{ type: "text", text: !observed
|
|
316
|
+
? "Navigation returned, but page verification failed. Do not claim the requested page loaded and do not blindly repeat navigation."
|
|
317
|
+
: "Page observed after navigation. Check this result for redirects, sign-in requirements or page errors before reporting task success:" },
|
|
318
|
+
...(Array.isArray(page?.content) ? page.content : []),
|
|
319
|
+
],
|
|
320
|
+
...(!observed ? { isError: true } : {}),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
287
323
|
return shapeBrowserToolResult(result, { toolName, budget: this.options.resultBudget });
|
|
288
324
|
}
|
|
289
325
|
catch (error) {
|
|
@@ -31,6 +31,8 @@ export function queueChannelMessage(groupId, threadId, text, options = {}) {
|
|
|
31
31
|
sendId: options.sendId,
|
|
32
32
|
mode: options.mode ?? "chat",
|
|
33
33
|
via: options.via,
|
|
34
|
+
sender: options.sender,
|
|
35
|
+
trigger: options.trigger,
|
|
34
36
|
};
|
|
35
37
|
saveChatFollowup({ id: item.id, kind: "channel", ownerId: groupId, threadId, payload: item });
|
|
36
38
|
entry.items.push(item);
|
|
@@ -9,10 +9,32 @@ export class SetupCancelled extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
function displayText(value, multiline = false) {
|
|
12
|
-
|
|
12
|
+
// OSC 8 hyperlinks slip past stripVTControlCharacters on supported Node
|
|
13
|
+
// versions whenever the URI carries a character RFC 3986 allows unencoded
|
|
14
|
+
// ((, ), +, and friends): the failed match strands most of the URI and its
|
|
15
|
+
// BEL as printable text (nodejs/node#64313). Strip whole OSC sequences
|
|
16
|
+
// here, from the raw value while their openers are still intact; the
|
|
17
|
+
// label between a link's opening and closing sequences survives, and an
|
|
18
|
+
// unterminated sequence is removed outright. Both introducer forms
|
|
19
|
+
// match: ESC ] and the lone C1 byte, which carries no closing bracket.
|
|
20
|
+
// eslint-disable-next-line no-control-regex
|
|
21
|
+
const withoutOsc = value.replace(/(?:\u001b\]|\u009d)[^\u0007\u001b\u009c]*(?:\u0007|\u001b\\|\u009c)?/g, "");
|
|
22
|
+
const plain = stripVTControlCharacters(withoutOsc);
|
|
13
23
|
// Provider-supplied labels must not issue terminal control commands.
|
|
24
|
+
// stripVTControlCharacters glues a trailing BEL onto the escape sequence
|
|
25
|
+
// before it on some Node versions and leaves it bare on others, so the
|
|
26
|
+
// fallback below must be version-independent: controls that render no
|
|
27
|
+
// glyph (BEL, DEL, C1, …) are removed outright — padding them would
|
|
28
|
+
// counterfeit whitespace — while tab and carriage return collapse to one
|
|
29
|
+
// space and a newline survives only in multiline mode.
|
|
14
30
|
// eslint-disable-next-line no-control-regex
|
|
15
|
-
const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) =>
|
|
31
|
+
const withoutControls = plain.replace(/[\u0000-\u001f\u007f-\u009f]/g, (character) => {
|
|
32
|
+
if (character === "\n")
|
|
33
|
+
return multiline ? "\n" : " ";
|
|
34
|
+
if (character === "\t" || character === "\r")
|
|
35
|
+
return " ";
|
|
36
|
+
return "";
|
|
37
|
+
});
|
|
16
38
|
// Log lines must not end in trailing spaces; question prompts keep spacing like "Key: ".
|
|
17
39
|
return multiline ? withoutControls.replace(/[ \t]+(?=\n|$)/g, "") : withoutControls;
|
|
18
40
|
}
|