openmausbot 0.1.96 → 0.1.98
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/dist/assets/ActivityPanel-H6D3SD39.js +1 -0
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +7 -0
- package/dist/assets/CloudBackendPicker-iezpJFpF.js +1 -0
- package/dist/assets/ComputerPanel-DZPj-IWu.js +1 -0
- package/dist/assets/DesktopViewer-utONms1p.js +5 -0
- package/dist/assets/InspectorPanel-OVaBVrzC.js +5 -0
- package/dist/assets/KeyboardShortcutsModal-gk9mvHj_.js +1 -0
- package/dist/assets/LocalVmWorkspace-DLIvd2kC.js +1 -0
- package/dist/assets/MacLocalControl-bJv8yiPB.js +1 -0
- package/dist/assets/NewBotDialog-1qGOAdUM.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +1 -0
- package/dist/assets/RoutinesPage-BM2D4qGi.js +1 -0
- package/dist/assets/RoutinesSection-Dny9ra-c.js +1 -0
- package/dist/assets/SettingsModal-gC6XJJmt.js +11 -0
- package/dist/assets/TeamMapPage-B6fuu4r_.js +8 -0
- package/dist/assets/TriggersPanel-j0qTP2CO.js +1 -0
- package/dist/assets/VisibilitySection-DT0FqPvu.js +12 -0
- package/dist/assets/WebhooksPanel--YQyCjKO.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-Dkbd2hcj.js → abnfDiagram-O67JEVCF-BsjcFX0-.js} +1 -1
- package/dist/assets/activity-BxOkayvD.js +1 -0
- package/dist/assets/{arc-Duj5Coy2.js → arc-CJNA94c6.js} +1 -1
- package/dist/assets/architectureDiagram-NJMV4G6O-dz2ud7lP.js +36 -0
- package/dist/assets/{blockDiagram-BEXU5L5S-CWDlTpXe.js → blockDiagram-BEXU5L5S-ChBEALoR.js} +1 -1
- package/dist/assets/brain-CFZ7PEXc.js +1 -0
- package/dist/assets/{c4Diagram-YGBWAQC7-B0t15PDc.js → c4Diagram-YGBWAQC7-KZNRKDQe.js} +1 -1
- package/dist/assets/channel-WhFyujhZ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BJSCL-HV.js → chunk-2Q5K7J3B-BLLwmCET.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D3_ILccd.js → chunk-3FUC2YCW-CQxyreBZ.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-D3dmwT_S.js → chunk-5VM5RSS4-D6sFMW0u.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Br73HFPD.js → chunk-GTNCS2PH-BaZ1YgLF.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-uSsjxubC.js → chunk-GWA4HPMP-CcalphmC.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C40VWFyr.js → chunk-JWPE2WC7-CSk8R3ve.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-D6WNNYwU.js → chunk-NETBCI7D-CTmLjA57.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-DOEhq64P.js → chunk-WEXAMYUT-CM9Z77Ne.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BqJEi6Pi.js → chunk-XXDRQBXY-B1fxpMS2.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-ByU_GnxT.js → classDiagram-v2-NBCMYWYE-DCCyoLpj.js} +1 -1
- package/dist/assets/computer-control-NLov352p.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-KyImBKvE.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-CZWO7aYj.js → cynefin-OW5HDTMX-fsZc6MGa.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C70-w0la.js → cynefinDiagram-VND7K2PF-CGmHsShB.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-CnhjhExB.js → dagre-6A5THRUB-Dah1h-CH.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-bnjWCNb4.js → diagram-22UHCM2B-BIu71OyV.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-A4uTWoFV.js → diagram-3UASUU5V-gxhUB_Mu.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Zi6m3NUl.js → diagram-ATOU4E4O-CBhusUBn.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtCcmd5k.js → diagram-CDSNMT55-BdPrc86x.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DofuETS3.js → diagram-MLGK6HIB-Clp110R_.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-Dw2T9PuU.js → diagram-MPIPVDR6-DJKfbugw.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DbrwPiem.js → ebnfDiagram-ZINNZB2B-DnsltoBu.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BvCt1dCb.js → elk-276RUBZZ-jWdq6gqH.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-Y-QKqPnU.js → erDiagram-OPXOYQCR-DArPVf8u.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-CDkL1B5w.js → flowDiagram-KWPJA3E3-D2HZ-2f7.js} +1 -1
- package/dist/assets/ganttDiagram-FUAMR5RP--o0KIxEo.js +292 -0
- package/dist/assets/{gitGraphDiagram-X574FWY7-nL9iFTCv.js → gitGraphDiagram-X574FWY7-CsoXr2D7.js} +1 -1
- package/dist/assets/image-plus-BbVjGHin.js +1 -0
- package/dist/assets/index-BrPW5HDO.css +1 -0
- package/dist/assets/index-C3NqfdVU.js +585 -0
- package/dist/assets/{index-DreDnjOu.js → index-CJTp9JEp.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-C7aHjzsf.js → infoDiagram-VRGFBTTK-g-6lNp7r.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-KUZsNBTh.js → ishikawaDiagram-OU5B5YK6-B1d6qVTV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CGUuodFl.js → journeyDiagram-ZHPQQLJL-BTPuiU6T.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-Cy6r868Z.js → kanban-definition-PNTS6WVX-bDDuRJvP.js} +1 -1
- package/dist/assets/{linear-Bl669148.js → linear-BVVKbEMy.js} +1 -1
- package/dist/assets/{mermaid.core-86Xqz5Mr.js → mermaid.core-JJdoKyKB.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-G4DzHLrd.js → mindmap-definition-NLK3R4M7-B4aE3T0q.js} +1 -1
- package/dist/assets/module-DCUsy2BQ.js +3 -0
- package/dist/assets/page-visible-BBo_Zm8g.js +1 -0
- package/dist/assets/{pegDiagram-GJSIUBJH-K4eruzY0.js → pegDiagram-GJSIUBJH-DFKQWpR7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DYdR9PPq.js → pieDiagram-5QR66LMP-Iv7OKVJi.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BCAh0nEy.js → quadrantDiagram-O4NWA36T-U2YnfwBk.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-p0dGRc4g.js → railroadDiagram-XR7U4H2S-CuPy6RqM.js} +1 -1
- package/dist/assets/remote-desktop-panel-BNEj7tUx.js +1 -0
- package/dist/assets/{requirementDiagram-PLB6GJNP-tLeBG0aL.js → requirementDiagram-PLB6GJNP-bMI5AK7T.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-QRuN16rV.js → sankeyDiagram-IPEJSGJF-BKcFE2WQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-ueT1nVoa.js → sequenceDiagram-PO4LG4MO-Bn9q88kl.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-wRS2zWg2.js → sizeCapture-INFHLROL-Cvmdq9HD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CYzA_hE1.js → stateDiagram-v2-GCMORJYK-JJQYw3eQ.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-kDnv6LMc.js → swimlanes-2SLR337P-BkSCBEwp.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +8 -0
- package/dist/assets/table-file.worker-KkMx-SNx.js +10 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-D1y2UbbH.js → timeline-definition-EJHVYXUP-DsaM4GHr.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-IqB4v9HX.js → usecaseDiagram-POWQR4AR-3FOwLq7B.js} +1 -1
- package/dist/assets/user-DcZBnAuv.js +1 -0
- package/dist/assets/{vennDiagram-UO4OBE2U-BEi7KYVF.js → vennDiagram-UO4OBE2U-DyNjJsKt.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-Dkybo-Yf.js → wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BAPFJLpg.js → xychartDiagram-PMCCYNJV-BXZpg955.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/cloud-home-start.js +20 -9
- package/dist-server/container-mcp.js +8 -0
- package/dist-server/desktop-entry.mjs +54 -0
- package/dist-server/drivers/agents-proxy.js +26 -9
- package/dist-server/index.js +3734 -2360
- package/dist-server/openmausbot.js +1664 -1553
- package/dist-server/pair-cli.js +1664 -1553
- package/dist-server/permission-proxy.js +2 -4
- package/dist-server/server/atomic.js +70 -2
- package/dist-server/server/auto-approve.js +1 -1
- package/dist-server/server/auto-vm-claims.js +13 -1
- package/dist-server/server/automatic-recovery.js +1 -2
- package/dist-server/server/boat.js +68 -84
- package/dist-server/server/bot-overview.js +1 -1
- package/dist-server/server/browser-engine.js +38 -1
- package/dist-server/server/browser-live.js +112 -25
- package/dist-server/server/browser-runtime.js +31 -1
- package/dist-server/server/checkpoints.js +275 -44
- package/dist-server/server/cli-setup.js +3 -3
- package/dist-server/server/cloud-computer-tools.js +11 -4
- package/dist-server/server/cloud-home-start.js +4 -4
- package/dist-server/server/cloud-home.js +3 -9
- package/dist-server/server/cloud-owner.js +1 -1
- package/dist-server/server/computer-engine-removal.js +70 -0
- package/dist-server/server/computer-selection.js +18 -0
- package/dist-server/server/config.js +29 -12
- package/dist-server/server/connector-card-text.js +15 -0
- package/dist-server/server/connector-scope-hint.js +83 -0
- package/dist-server/server/default-model-selection.js +23 -14
- package/dist-server/server/delegations.js +11 -5
- package/dist-server/server/delta-context.js +6 -1
- package/dist-server/server/drivers/acp/core.js +11 -1
- package/dist-server/server/drivers/acp/grok.js +47 -5
- package/dist-server/server/drivers/acp/opencode-go.js +16 -21
- package/dist-server/server/drivers/agents-call.js +1 -1
- package/dist-server/server/drivers/agents-catalog.js +27 -7
- package/dist-server/server/drivers/agents-proxy.js +1 -0
- package/dist-server/server/drivers/builtIn.js +0 -2
- package/dist-server/server/drivers/chat-mcp-tools.js +5 -3
- package/dist-server/server/drivers/claude-login-auth.js +1 -1
- package/dist-server/server/drivers/claude.js +84 -41
- package/dist-server/server/drivers/codex-device-auth.js +27 -319
- package/dist-server/server/drivers/codex.js +2 -2
- package/dist-server/server/drivers/device-auth.js +342 -0
- package/dist-server/server/drivers/openai-chat.js +110 -10
- package/dist-server/server/drivers/pi.js +184 -49
- package/dist-server/server/fleet-agent.js +18 -5
- package/dist-server/server/harness/http.js +5 -2
- package/dist-server/server/harness/registry.js +19 -0
- package/dist-server/server/harness-capabilities.js +7 -4
- package/dist-server/server/hosted-models.js +3 -1
- package/dist-server/server/index.js +823 -333
- package/dist-server/server/key-rejections.js +57 -0
- package/dist-server/server/live-call.js +11 -6
- package/dist-server/server/managed-policy.js +1 -1
- package/dist-server/server/message-file.js +7 -4
- package/dist-server/server/model-catalog/presets.js +8 -0
- package/dist-server/server/peer-roster.js +10 -2
- package/dist-server/server/permission-proxy.js +4 -0
- package/dist-server/server/provider-key-check.js +5 -1
- package/dist-server/server/request-auth.js +1 -1
- package/dist-server/server/room-turn-end.js +15 -0
- package/dist-server/server/room-turn-timeout.js +28 -0
- package/dist-server/server/routes/live.js +3 -3
- package/dist-server/server/routes/thread-models.js +36 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +3 -3
- package/dist-server/server/screen-frame-gate.js +1 -3
- package/dist-server/server/skills.js +3 -2
- package/dist-server/server/store.js +273 -61
- package/dist-server/server/surface.js +29 -49
- package/dist-server/server/system-prompt.js +10 -10
- package/dist-server/server/team-backup.js +19 -4
- package/dist-server/server/thread-model.js +25 -0
- package/dist-server/server/turn-continuation.js +168 -0
- package/dist-server/server/webhook-ingress.js +8 -3
- package/dist-server/server/webhooks.js +40 -5
- package/dist-server/server/workspace.js +1 -1
- package/dist-server/shared/ask-question.js +0 -70
- package/dist-server/shared/cloud-computer.js +4 -6
- package/dist-server/shared/credential-request.js +1 -1
- package/dist-server/shared/device-sign-in.js +57 -0
- package/dist-server/shared/failed-turn.js +1 -4
- package/dist-server/shared/place-view.js +409 -0
- package/dist-server/shared/team-backup.js +2 -0
- package/dist-server/shared/thread-model.js +13 -0
- package/dist-server/shared/tool-scope-support.js +1 -2
- package/dist-server/vps-container-mcp.js +8 -0
- package/dist-server/workspace-backup.worker.js +41 -22
- package/package.json +1 -1
- package/dist/assets/DesktopViewer-DsnA23KM.js +0 -5
- package/dist/assets/architectureDiagram-NJMV4G6O-uYUsDawP.js +0 -36
- package/dist/assets/channel-BZxwC-Gw.js +0 -1
- package/dist/assets/cose-bilkent-JH36ORCC-D-Uglxqq.js +0 -1
- package/dist/assets/ganttDiagram-FUAMR5RP-CKV5D2F6.js +0 -292
- package/dist/assets/index-Du7o6Dsf.js +0 -610
- package/dist/assets/index-KxG4bWSr.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C7voMDle.js +0 -8
- package/dist-server/server/drivers/boatagent.js +0 -443
|
@@ -8,7 +8,6 @@ var MAX_OPTIONS = 12;
|
|
|
8
8
|
var MAX_QUESTION_TEXT = 8e3;
|
|
9
9
|
var MAX_LABEL = 120;
|
|
10
10
|
var MAX_DESCRIPTION = 400;
|
|
11
|
-
var MAX_CUSTOM_ANSWER = 2e3;
|
|
12
11
|
function text(value, limit) {
|
|
13
12
|
if (typeof value !== "string") return void 0;
|
|
14
13
|
const trimmed = value.trim();
|
|
@@ -60,9 +59,6 @@ function parseAskQuestions(input) {
|
|
|
60
59
|
}
|
|
61
60
|
return questions.length ? questions : null;
|
|
62
61
|
}
|
|
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;
|
|
66
62
|
function questionAnswersByQuestion(message, questions) {
|
|
67
63
|
return questionAnswersById(message, questions.map((question) => ({ id: question.question, question })));
|
|
68
64
|
}
|
|
@@ -161,6 +157,7 @@ var allowQuestion = allowsTool(selection.scope, { kind: "mcp", server: "ogb", na
|
|
|
161
157
|
var socketPath = process.argv[2] ?? "";
|
|
162
158
|
var waiting = /* @__PURE__ */ new Map();
|
|
163
159
|
var conn = connect(socketPath);
|
|
160
|
+
conn.setEncoding("utf8");
|
|
164
161
|
var dead = () => {
|
|
165
162
|
for (const resolve of waiting.values()) {
|
|
166
163
|
resolve({ behavior: "deny", message: "OpenMausBot: permission broker unavailable \u2014 skip this action" });
|
|
@@ -299,6 +296,7 @@ async function handle(msg) {
|
|
|
299
296
|
}
|
|
300
297
|
}
|
|
301
298
|
var inBuf = "";
|
|
299
|
+
process.stdin.setEncoding("utf8");
|
|
302
300
|
process.stdin.on("data", (chunk) => {
|
|
303
301
|
inBuf += chunk;
|
|
304
302
|
let nl;
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
// reader always sees either the complete old contents or the complete new
|
|
5
5
|
// ones. Without this, an interrupted writeFileSync produces half-written JSON
|
|
6
6
|
// that fails to parse on next boot and is silently treated as empty state.
|
|
7
|
+
//
|
|
8
|
+
// The fsync is the expensive part: on macOS it is F_FULLFSYNC, about 4 ms of
|
|
9
|
+
// blocked event loop per call. `durable: false` drops only the fsync, for
|
|
10
|
+
// files whose loss after a power cut costs nothing (a derived cache, a
|
|
11
|
+
// session-scoped token). The temp file and rename stay, so an app crash or
|
|
12
|
+
// kill -9 still never leaves a torn file; only a power loss or kernel panic
|
|
13
|
+
// can leave such a file empty.
|
|
7
14
|
import { randomUUID } from "node:crypto";
|
|
8
|
-
import { closeSync, fsyncSync, openSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { closeSync, constants, fstatSync, fsyncSync, lstatSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
9
16
|
/** Windows refuses a rename onto an existing path while anything else holds a
|
|
10
17
|
* handle to either file, and a virus scanner or the search indexer opening a
|
|
11
18
|
* just-closed file for a few milliseconds is enough. It surfaces as EPERM or
|
|
@@ -51,7 +58,8 @@ export function writeFileAtomic(path, data, options = {}) {
|
|
|
51
58
|
// config file visible between the write and a later chmod.
|
|
52
59
|
fd = openSync(tmp, "w", options.mode);
|
|
53
60
|
writeFileSync(fd, data);
|
|
54
|
-
|
|
61
|
+
if (options.durable !== false)
|
|
62
|
+
fsyncSync(fd);
|
|
55
63
|
closeSync(fd);
|
|
56
64
|
fd = null;
|
|
57
65
|
renameWithRetry(tmp, path);
|
|
@@ -74,3 +82,63 @@ export function writeFileAtomic(path, data, options = {}) {
|
|
|
74
82
|
throw e;
|
|
75
83
|
}
|
|
76
84
|
}
|
|
85
|
+
/** True only when `path` is already exactly `data`: a regular file (never a
|
|
86
|
+
* symlink, FIFO or directory), owned by this user, with permissions no
|
|
87
|
+
* broader than `mode`. Any doubt or error answers false, so the caller falls
|
|
88
|
+
* back to a full write — this check must never be the reason a save fails.
|
|
89
|
+
*
|
|
90
|
+
* It reads the file on disk rather than remembering the last write: a
|
|
91
|
+
* workspace restore or a hand edit can change the file underneath, and the
|
|
92
|
+
* next save has to put its own bytes back exactly as before. */
|
|
93
|
+
function holdsExactly(path, data, mode) {
|
|
94
|
+
let fd = null;
|
|
95
|
+
try {
|
|
96
|
+
if (process.platform === "win32") {
|
|
97
|
+
// No O_NOFOLLOW on Windows; a reparse point is replaced, not read.
|
|
98
|
+
if (!lstatSync(path).isFile())
|
|
99
|
+
return false;
|
|
100
|
+
fd = openSync(path, "r");
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Inspect and read the same inode the open landed on, so a swap
|
|
104
|
+
// between a stat and the read cannot be mistaken for a match.
|
|
105
|
+
// O_NONBLOCK keeps a FIFO planted at the path from hanging the open.
|
|
106
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
107
|
+
}
|
|
108
|
+
const stat = fstatSync(fd);
|
|
109
|
+
if (!stat.isFile() || stat.size !== Buffer.byteLength(data))
|
|
110
|
+
return false;
|
|
111
|
+
const uid = process.getuid?.();
|
|
112
|
+
if (uid !== undefined && stat.uid !== uid)
|
|
113
|
+
return false;
|
|
114
|
+
// Windows reports 0o666 for any writable file; its mode bits mean nothing.
|
|
115
|
+
if (mode !== undefined && process.platform !== "win32" && (stat.mode & 0o777 & ~mode) !== 0)
|
|
116
|
+
return false;
|
|
117
|
+
// Bytes, not strings: decoding and comparing a megabyte-sized registry as
|
|
118
|
+
// a JS string costs several times more than a memcmp of the raw bytes.
|
|
119
|
+
return readFileSync(fd).equals(Buffer.from(data));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (fd !== null) {
|
|
126
|
+
try {
|
|
127
|
+
closeSync(fd);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* best-effort cleanup */
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** writeFileAtomic for saves that often repeat what the file already holds.
|
|
136
|
+
* A save that changes nothing costs one open and read instead of a temp
|
|
137
|
+
* file, a rename and an fsync; a save whose size differs pays only an
|
|
138
|
+
* open and fstat before the normal write. Returns whether it wrote. */
|
|
139
|
+
export function writeFileAtomicIfChanged(path, data, options = {}) {
|
|
140
|
+
if (holdsExactly(path, data, options.mode))
|
|
141
|
+
return false;
|
|
142
|
+
writeFileAtomic(path, data, options);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
@@ -79,7 +79,7 @@ export function delegatedApprovalMode(input) {
|
|
|
79
79
|
// the permission path in permission-proxy). Approving it there does not
|
|
80
80
|
// produce an answer: the CLI runs the tool with none and the model is told
|
|
81
81
|
// "The user did not answer the questions." — a question silently lost.
|
|
82
|
-
const ASKS_A_PERSON = new Set(["askuserquestion", "ask_user"
|
|
82
|
+
const ASKS_A_PERSON = new Set(["askuserquestion", "ask_user"]);
|
|
83
83
|
// A web search is a low-level read. Approve for me allows these names and
|
|
84
84
|
// nothing wider: a bare search, a page fetch, and every other tool stay a
|
|
85
85
|
// prompt. The name is the same shape as a question — one mcp__<server>__
|
|
@@ -18,6 +18,18 @@ 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
|
+
slot.onRejected?.(slot.failure, error);
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
+
/** How long a computer call should wait on its thread's lazy claim, or null
|
|
25
|
+
* when it should not wait: the first call waits while the claim it fires
|
|
26
|
+
* lands; a slow claim (graceMs) is waited on by every call until it settles. */
|
|
27
|
+
export function lazyClaimWaitMs(slot, defaultGraceMs) {
|
|
28
|
+
if (!slot?.lazy)
|
|
29
|
+
return null;
|
|
30
|
+
if (!slot.begin)
|
|
31
|
+
return slot.graceMs ?? defaultGraceMs;
|
|
32
|
+
if (slot.graceMs !== undefined && !slot.claimed && !slot.failed)
|
|
33
|
+
return slot.graceMs;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
@@ -3,8 +3,7 @@ import { supportsWorkspaceFiles } from "./workspace.js";
|
|
|
3
3
|
* its filesystem to another machine. Approval and organisation policy are
|
|
4
4
|
* checked by the ordinary model-switch and turn-admission gates. */
|
|
5
5
|
export function recoveryCapabilityError(from, to) {
|
|
6
|
-
if (from.
|
|
7
|
-
supportsWorkspaceFiles(from.driverKind) !== supportsWorkspaceFiles(to.driverKind)) {
|
|
6
|
+
if (supportsWorkspaceFiles(from.driverKind) !== supportsWorkspaceFiles(to.driverKind)) {
|
|
8
7
|
return "The backup cannot use the same workspace.";
|
|
9
8
|
}
|
|
10
9
|
const tools = ["agentsMcp", "computerMcp", "composioMcp",
|
|
@@ -86,13 +86,12 @@ function scopedBoatPrefix() {
|
|
|
86
86
|
scopedBoatPrefixCache = `ogb-${scope}-`;
|
|
87
87
|
return scopedBoatPrefixCache;
|
|
88
88
|
}
|
|
89
|
-
/** Decide lifecycle work before a turn mounts Boat.
|
|
90
|
-
*
|
|
91
|
-
|
|
89
|
+
/** Decide lifecycle work before a turn mounts Boat. Only a turn whose place
|
|
90
|
+
* is Cloud gets here (Auto never reads the Boat account), so a missing Boat
|
|
91
|
+
* is created and a sleeping one woken. */
|
|
92
|
+
export function boatTurnLifecycleAction(state) {
|
|
92
93
|
if (state && READY.has(state))
|
|
93
94
|
return "attach";
|
|
94
|
-
if (!explicitCloud)
|
|
95
|
-
return "none";
|
|
96
95
|
return state ? "wake" : "provision";
|
|
97
96
|
}
|
|
98
97
|
/** Keep one provider account for the whole logical operation. Settings may
|
|
@@ -346,36 +345,46 @@ async function mintDesktopUrl(cfg, boxId, { vncBudgetMs = 60_000 } = {}) {
|
|
|
346
345
|
}
|
|
347
346
|
async function waitReady(cfg, boxId, budgetMs = 90_000) {
|
|
348
347
|
assertBoatNotDeleting(boxId);
|
|
349
|
-
const
|
|
348
|
+
const deadline = Date.now() + budgetMs;
|
|
349
|
+
// Every request ends with the budget: a relay that accepts the connection
|
|
350
|
+
// and then stalls must not hold a turn's start past it.
|
|
351
|
+
const untilDeadline = () => AbortSignal.timeout(Math.max(1, deadline - Date.now()));
|
|
352
|
+
const outOfTime = (error) => error instanceof Error && error.name === "TimeoutError";
|
|
350
353
|
// Boat's words for the last failed resume, if the wait runs out on them.
|
|
351
354
|
let resumeFailure = null;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
355
|
+
try {
|
|
356
|
+
while (Date.now() < deadline) {
|
|
357
|
+
assertBoatNotDeleting(boxId);
|
|
358
|
+
const { body } = await boatJson(cfg, `/boxes/${boxId}`, { signal: untilDeadline() });
|
|
359
|
+
const state = body?.box?.state;
|
|
360
|
+
if (READY.has(state))
|
|
361
|
+
return body.box;
|
|
362
|
+
if (state === "error")
|
|
363
|
+
return null;
|
|
364
|
+
// an archiving boat can't resume until the snapshot lands — nudge after.
|
|
365
|
+
// A refusal (a plan limit, say) is final: report it now. A server error
|
|
366
|
+
// is retried on the next poll, as Boat asks; 409 is a state race with a
|
|
367
|
+
// wake already under way.
|
|
368
|
+
if (state === "archived") {
|
|
369
|
+
const resumed = await boatJson(cfg, `/boxes/${boxId}/resume`, { method: "POST", signal: untilDeadline() });
|
|
370
|
+
if (resumed.ok)
|
|
371
|
+
resumeFailure = null;
|
|
372
|
+
else if (resumed.status !== 409) {
|
|
373
|
+
const refusal = boatRefusal(resumed.status, "waking the cloud computer", resumed.body, usesIncludedBoat(cfg));
|
|
374
|
+
if (resumed.status < 500)
|
|
375
|
+
throw refusal;
|
|
376
|
+
resumeFailure = refusal;
|
|
377
|
+
}
|
|
373
378
|
}
|
|
379
|
+
await new Promise((r) => setTimeout(r, Math.min(2500, Math.max(0, deadline - Date.now()))));
|
|
374
380
|
}
|
|
375
|
-
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
if (!outOfTime(error))
|
|
384
|
+
throw error;
|
|
376
385
|
}
|
|
377
386
|
if (resumeFailure)
|
|
378
|
-
throw
|
|
387
|
+
throw resumeFailure;
|
|
379
388
|
return null;
|
|
380
389
|
}
|
|
381
390
|
// Resolving a bot's boat means LISTing every boat in the account, so it is
|
|
@@ -948,6 +957,17 @@ export function boatErrorMessage(status, what, body, included = false) {
|
|
|
948
957
|
}
|
|
949
958
|
return theirs ? `${what} failed: ${theirs}` : `${what} failed (${status})`;
|
|
950
959
|
}
|
|
960
|
+
/** A refused start as an error that keeps the provider's status and code
|
|
961
|
+
* (the Admin's own, such as subscription_inactive), so a failed place is
|
|
962
|
+
* read from the code first and from the words only until every refusal
|
|
963
|
+
* has one (shared/place-view.ts cloudRefusal). Named apart from `status`,
|
|
964
|
+
* which a route would answer with. */
|
|
965
|
+
export function boatRefusal(status, what, body, included = false) {
|
|
966
|
+
const code = body?.error?.code ?? body?.code;
|
|
967
|
+
return Object.assign(new Error(boatErrorMessage(status, what, body, included)), {
|
|
968
|
+
boatStatus: status, ...(typeof code === "string" && /^[a-z0-9_]{1,64}$/.test(code) ? { boatCode: code } : {}),
|
|
969
|
+
});
|
|
970
|
+
}
|
|
951
971
|
/** boat.dev trial accounts reject the normal eight-hour auto-stop with a
|
|
952
972
|
* structured `trial_auto_stop_required` refusal. Retry that one condition
|
|
953
973
|
* once at the provider's advertised maximum (or the documented two-hour
|
|
@@ -967,44 +987,13 @@ function idempotentCreateInProgress(result) {
|
|
|
967
987
|
const code = result.body?.error?.code ?? result.body?.code;
|
|
968
988
|
return result.status === 409 && code === "idempotency_in_progress";
|
|
969
989
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
const put = (name, value) => {
|
|
977
|
-
if (typeof value === "string" && value.trim())
|
|
978
|
-
out[name] = value.trim();
|
|
979
|
-
};
|
|
980
|
-
// The workspace key only: an ANTHROPIC_API_KEY in the server's own env is
|
|
981
|
-
// never the workspace key (see loadConfig), so it is not forwarded either.
|
|
982
|
-
put("ANTHROPIC_API_KEY", cfg.anthropic?.key);
|
|
983
|
-
for (const name of BOAT_FORWARDED_CREDENTIAL_ENV)
|
|
984
|
-
put(name, env[name]);
|
|
985
|
-
return out;
|
|
986
|
-
}
|
|
987
|
-
/** Names the boat's agents read (Claude Code, Codex, pi, OpenCode, Prime
|
|
988
|
-
* Agent, Kimi), forwarded verbatim from this server's environment when set. */
|
|
989
|
-
const BOAT_FORWARDED_CREDENTIAL_ENV = [
|
|
990
|
-
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
991
|
-
"OPENAI_API_KEY",
|
|
992
|
-
"OPENROUTER_API_KEY",
|
|
993
|
-
"LLMGATEWAY_API_KEY",
|
|
994
|
-
"DEEPSEEK_API_KEY",
|
|
995
|
-
"MOONSHOT_API_KEY",
|
|
996
|
-
"KIMI_CODE_ACCESS_TOKEN",
|
|
997
|
-
"KIMI_CODE_REFRESH_TOKEN",
|
|
998
|
-
];
|
|
999
|
-
async function requestBoatCreate(cfg, botId, ttlSeconds, env) {
|
|
1000
|
-
// The computer needs the user's desktop session, not the account owner's
|
|
1001
|
-
// host credentials. Keep provider-side env injection off; the only keys the
|
|
1002
|
-
// guest ever has are the ones this OpenMausBot forwards (`env`), which its
|
|
1003
|
-
// agents need now that the turn runs on the boat. The idempotency identity
|
|
1004
|
-
// stays the secret-free part: a trial-TTL retry must receive a different
|
|
1005
|
-
// key, and the journal on disk never carries a credential.
|
|
990
|
+
async function requestBoatCreate(cfg, botId, ttlSeconds) {
|
|
991
|
+
// The computer is a desktop the bot's own engine drives from here, so it
|
|
992
|
+
// needs no AI sign-in of its own: provider-side env injection stays off and
|
|
993
|
+
// no key this OpenMausBot holds is ever sent to it. A trial-TTL retry must
|
|
994
|
+
// receive a different idempotency key, and the journal on disk never
|
|
995
|
+
// carries a credential.
|
|
1006
996
|
const body = JSON.stringify({ ttlSeconds, noEnv: true });
|
|
1007
|
-
const wireBody = JSON.stringify({ ttlSeconds, noEnv: true, ...(Object.keys(env).length ? { env } : {}) });
|
|
1008
997
|
let attempt = beginBoatCreate(botId, body);
|
|
1009
998
|
let request = attempt.request;
|
|
1010
999
|
let createdThisAttempt = attempt.startedNow;
|
|
@@ -1034,7 +1023,7 @@ async function requestBoatCreate(cfg, botId, ttlSeconds, env) {
|
|
|
1034
1023
|
method: "POST",
|
|
1035
1024
|
headers: { "Idempotency-Key": request.idempotencyKey },
|
|
1036
1025
|
signal: AbortSignal.timeout(45_000),
|
|
1037
|
-
body
|
|
1026
|
+
body,
|
|
1038
1027
|
});
|
|
1039
1028
|
}
|
|
1040
1029
|
catch (error) {
|
|
@@ -1067,12 +1056,12 @@ async function requestBoatCreate(cfg, botId, ttlSeconds, env) {
|
|
|
1067
1056
|
return { ...last, request, createdThisAttempt };
|
|
1068
1057
|
}
|
|
1069
1058
|
}
|
|
1070
|
-
async function createBoat(cfg, botId
|
|
1071
|
-
const first = await requestBoatCreate(cfg, botId, DEFAULT_BOAT_TTL_SECONDS
|
|
1059
|
+
async function createBoat(cfg, botId) {
|
|
1060
|
+
const first = await requestBoatCreate(cfg, botId, DEFAULT_BOAT_TTL_SECONDS);
|
|
1072
1061
|
if (first.ok)
|
|
1073
1062
|
return first;
|
|
1074
1063
|
const trialTtl = trialBoatTtlSeconds(first.body);
|
|
1075
|
-
return trialTtl === null ? first : requestBoatCreate(cfg, botId, trialTtl
|
|
1064
|
+
return trialTtl === null ? first : requestBoatCreate(cfg, botId, trialTtl);
|
|
1076
1065
|
}
|
|
1077
1066
|
/** A prior explicit delete always wins over provisioning. Reconcile/retry the
|
|
1078
1067
|
* old immutable target, then require a fresh provision request so one click
|
|
@@ -1111,10 +1100,9 @@ export async function boatStatus(cfg, botId) {
|
|
|
1111
1100
|
}
|
|
1112
1101
|
/**
|
|
1113
1102
|
* Find-or-create the bot's persistent boat, wait for ready, and mint a fresh
|
|
1114
|
-
* desktop URL. The boat ships its own computer-use driver
|
|
1103
|
+
* desktop URL. The boat ships its own computer-use driver.
|
|
1115
1104
|
*/
|
|
1116
1105
|
export async function provisionBoat(cfg, botId, _botName) {
|
|
1117
|
-
const credentialEnv = boatCredentialEnv(cfg);
|
|
1118
1106
|
cfg = snapshotBoatConfig(cfg);
|
|
1119
1107
|
if (!boatConfigured(cfg)) {
|
|
1120
1108
|
throw new Error('box provider not enabled — add {"box":{"token":"…"}} to ~/.openmausbot/config.json');
|
|
@@ -1132,9 +1120,9 @@ export async function provisionBoat(cfg, botId, _botName) {
|
|
|
1132
1120
|
// Provider-side backstop: archives itself (billing pauses, disk
|
|
1133
1121
|
// survives) if every stop path dies. Trial accounts get one narrower
|
|
1134
1122
|
// retry when boat.dev reports their shorter TTL ceiling.
|
|
1135
|
-
const createRes = await createBoat(cfg, botId
|
|
1123
|
+
const createRes = await createBoat(cfg, botId);
|
|
1136
1124
|
if (!createRes.ok || !createRes.body?.box?.id) {
|
|
1137
|
-
throw
|
|
1125
|
+
throw boatRefusal(createRes.status, "boat create", createRes.body, usesIncludedBoat(cfg));
|
|
1138
1126
|
}
|
|
1139
1127
|
boat = createRes.body.box;
|
|
1140
1128
|
createRequest = createRes.request;
|
|
@@ -1151,8 +1139,7 @@ export async function provisionBoat(cfg, botId, _botName) {
|
|
|
1151
1139
|
const ready = await waitReady(cfg, boat.id);
|
|
1152
1140
|
if (!ready)
|
|
1153
1141
|
throw new Error("box did not become ready within 90s — retry in a minute");
|
|
1154
|
-
// Nothing to install: every boat ships its own computer-use driver
|
|
1155
|
-
// registers it with every harness it runs.
|
|
1142
|
+
// Nothing to install: every boat ships its own computer-use driver.
|
|
1156
1143
|
const joinUrl = await mintDesktopUrl(cfg, boat.id);
|
|
1157
1144
|
if (!joinUrl)
|
|
1158
1145
|
throw new Error("box desktop link could not be created");
|
|
@@ -1249,14 +1236,11 @@ export async function execOnBoat(cfg, botId, command) {
|
|
|
1249
1236
|
//
|
|
1250
1237
|
// The frame is for a person: it fills the panel and opens in the chat's
|
|
1251
1238
|
// image viewer, so it keeps the desktop's native size up to 1080p and a
|
|
1252
|
-
// quality where page text stays legible.
|
|
1253
|
-
//
|
|
1254
|
-
//
|
|
1255
|
-
//
|
|
1256
|
-
//
|
|
1257
|
-
// pointer is drawn into the frame (scrot --pointer, ffmpeg -draw_mouse):
|
|
1258
|
-
// watching the bot work means seeing where its cursor is, and X11
|
|
1259
|
-
// captures leave it out by default.
|
|
1239
|
+
// quality where page text stays legible. Only wider displays are scaled
|
|
1240
|
+
// down, with -resize rather than -thumbnail so the resample is not the
|
|
1241
|
+
// fast-and-blurry kind meant for icons. The pointer is drawn into the frame
|
|
1242
|
+
// (scrot --pointer, ffmpeg -draw_mouse): watching the bot work means seeing
|
|
1243
|
+
// where its cursor is, and X11 captures leave it out by default.
|
|
1260
1244
|
const PANEL_PATH = "/tmp/ogb-panel.jpg";
|
|
1261
1245
|
export const PANEL_FRAME_WIDTH = 1920;
|
|
1262
1246
|
export const PANEL_FRAME_QUALITY = 85;
|
|
@@ -147,7 +147,7 @@ export function grantsSummary(connectorTools) {
|
|
|
147
147
|
function computerReach(computer, cloudHome = false) {
|
|
148
148
|
// A Cloud home never offers either; say so rather than list a preference
|
|
149
149
|
// that every task there refuses.
|
|
150
|
-
const unavailable = cloudHome ? ", which isn't available on
|
|
150
|
+
const unavailable = cloudHome ? ", which isn't available on My Cloud" : "";
|
|
151
151
|
switch (computer) {
|
|
152
152
|
case "cloud":
|
|
153
153
|
return "Computer preference: cloud computer.";
|
|
@@ -16,7 +16,7 @@ import { delimiter, dirname, join, resolve } from "node:path";
|
|
|
16
16
|
import { writeFileAtomic } from "./atomic.js";
|
|
17
17
|
import { DATA_DIR } from "./config.js";
|
|
18
18
|
import { browserBundlePaths } from "./browser-bundle-release.js";
|
|
19
|
-
import { browserRuntimeEnv } from "./browser-runtime.js";
|
|
19
|
+
import { BROWSER_VIEWPORT_ARGS, browserRuntimeEnv, ownsBrowserViewport } from "./browser-runtime.js";
|
|
20
20
|
import { SIGN_IN_PROMPT } from "./system-prompt.js";
|
|
21
21
|
import { AGENT_BROWSER_VERSION, agentBrowserReleaseUrl, agentBrowserReleaseVersion, resolveAgentBrowserReleaseAsset, } from "./browser-engine-release.js";
|
|
22
22
|
const ENGINE_DIR = "tools/agent-browser";
|
|
@@ -467,6 +467,43 @@ export function agentBrowserIntegration(input) {
|
|
|
467
467
|
}
|
|
468
468
|
return { command: input.binaryPath, args: ["mcp", "--tools", "core", "--no-webmcp"], env };
|
|
469
469
|
}
|
|
470
|
+
/** Give the session's browser the standard page size, launching it with the
|
|
471
|
+
* session's own launch environment if it is not running yet. Best effort:
|
|
472
|
+
* false (and the page keeps whatever size it had) on any failure. */
|
|
473
|
+
export function setBrowserViewport(binaryPath, env, timeoutMs = 30_000) {
|
|
474
|
+
if (!ownsBrowserViewport(env))
|
|
475
|
+
return Promise.resolve(true);
|
|
476
|
+
return new Promise((settle) => {
|
|
477
|
+
let child;
|
|
478
|
+
try {
|
|
479
|
+
child = spawn(binaryPath, [...BROWSER_VIEWPORT_ARGS, "--json", "--no-webmcp"], { env: browserRuntimeEnv(env), stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
settle(false);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
let output = "";
|
|
486
|
+
let settled = false;
|
|
487
|
+
const finish = (ok) => { if (!settled) {
|
|
488
|
+
settled = true;
|
|
489
|
+
clearTimeout(timer);
|
|
490
|
+
settle(ok);
|
|
491
|
+
} };
|
|
492
|
+
const timer = setTimeout(() => { child.kill("SIGKILL"); finish(false); }, timeoutMs);
|
|
493
|
+
timer.unref?.();
|
|
494
|
+
child.stdout?.on("data", (chunk) => { if (output.length < 65_536)
|
|
495
|
+
output += String(chunk); });
|
|
496
|
+
child.on("error", () => finish(false));
|
|
497
|
+
child.on("close", (code) => {
|
|
498
|
+
try {
|
|
499
|
+
finish(code === 0 && JSON.parse(output)?.success === true);
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
finish(false);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
}
|
|
470
507
|
/** How long a settled-frame capture may take before the turn gives up on it.
|
|
471
508
|
* The poller runs beside a live turn, so a hung browser must not hold the
|
|
472
509
|
* transcript open; a missing picture is better than a stuck fold. */
|