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
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
|
-
import { browserRuntimeEnv } from "./browser-runtime.js";
|
|
4
|
+
import { BROWSER_VIEWPORT_ARGS, browserRuntimeEnv, ownsBrowserViewport } from "./browser-runtime.js";
|
|
5
5
|
import { closeBrowserSession } from "./browser-engine.js";
|
|
6
6
|
const execute = promisify(execFile);
|
|
7
7
|
const MAX_FRAME = 3 * 1024 * 1024;
|
|
8
8
|
const MAX_BUFFER = 4 * 1024 * 1024;
|
|
9
9
|
const HEARTBEAT_MS = 10_000;
|
|
10
|
+
const COMMAND_TIMEOUT_MS = 30_000;
|
|
11
|
+
const VIEWPORT_TIMEOUT_MS = 5_000;
|
|
12
|
+
// Opening a view while the bot's own command holds the session: each stream
|
|
13
|
+
// probe waits up to STATUS_PROBE_MS (it may also have to launch the browser),
|
|
14
|
+
// then the view shows that it is waiting and probes again with a bounded
|
|
15
|
+
// backoff for as long as the viewer stays, up to a limit. A probe that is
|
|
16
|
+
// waiting in line answers as soon as the bot's command finishes.
|
|
17
|
+
const STATUS_PROBE_MS = 10_000;
|
|
18
|
+
const BUSY_RETRY_MS = [1_000, 2_000, 5_000];
|
|
19
|
+
const BUSY_LIMIT_MS = 10 * 60_000;
|
|
10
20
|
// The native press resolver supplies the virtual key codes and Enter/Tab text
|
|
11
21
|
// that its raw input_keyboard relay omits. Keep unknown keys literal.
|
|
12
22
|
const DISCRETE_KEYS = new Set(["Backspace", "Enter", "Tab", "Escape", "Delete", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"]);
|
|
@@ -17,6 +27,20 @@ export class BrowserLiveError extends Error {
|
|
|
17
27
|
status;
|
|
18
28
|
constructor(message, status = 400) { super(message); this.name = "BrowserLiveError"; this.status = status; }
|
|
19
29
|
}
|
|
30
|
+
/** The engine is there but did not answer in time: another command, usually
|
|
31
|
+
* the bot's own, holds the same browser session. Not a missing engine. */
|
|
32
|
+
export class BrowserBusyError extends BrowserLiveError {
|
|
33
|
+
constructor(message = "The browser is busy with another action and did not answer in time. Try again shortly.") {
|
|
34
|
+
super(message, 503);
|
|
35
|
+
this.name = "BrowserBusyError";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** execFile's own timeout: it killed a command that had not exited, as
|
|
39
|
+
* opposed to a missing binary, a failed command, an abort or an overflow. */
|
|
40
|
+
function timedOut(error) {
|
|
41
|
+
const failure = error;
|
|
42
|
+
return failure?.killed === true && failure.signal === "SIGKILL" && failure.code == null;
|
|
43
|
+
}
|
|
20
44
|
function object(value) {
|
|
21
45
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
22
46
|
}
|
|
@@ -183,6 +207,8 @@ export class BrowserLive {
|
|
|
183
207
|
}
|
|
184
208
|
if (!viewer.res.headersSent)
|
|
185
209
|
return; // A peer can change control while this viewer is still starting.
|
|
210
|
+
if (viewer.waiting && message.type !== "waiting" && message.type !== "error")
|
|
211
|
+
return;
|
|
186
212
|
if (viewer.res.writableLength > MAX_BUFFER) {
|
|
187
213
|
this.close(viewer);
|
|
188
214
|
return;
|
|
@@ -279,6 +305,7 @@ export class BrowserLive {
|
|
|
279
305
|
if (viewer.closed)
|
|
280
306
|
return;
|
|
281
307
|
viewer.closed = true;
|
|
308
|
+
viewer.abort.abort();
|
|
282
309
|
this.viewers.delete(viewer.id);
|
|
283
310
|
clearTimeout(viewer.heartbeat);
|
|
284
311
|
clearTimeout(viewer.drainTimer);
|
|
@@ -303,13 +330,14 @@ export class BrowserLive {
|
|
|
303
330
|
if (viewer.pressedKeys.size || viewer.pressedButtons.size)
|
|
304
331
|
this.runtime.abandonHumanInput(viewer.session, viewer.id);
|
|
305
332
|
}
|
|
306
|
-
async command(viewer, args) {
|
|
333
|
+
async command(viewer, args, timeout = COMMAND_TIMEOUT_MS) {
|
|
307
334
|
if (!this.current(viewer))
|
|
308
335
|
throw new BrowserLiveError("This browser view is no longer available.", 409);
|
|
309
336
|
const env = browserRuntimeEnv({ ...viewer.spec.env, AGENT_BROWSER_SESSION: viewer.session });
|
|
310
337
|
try {
|
|
311
338
|
const { stdout } = await execute(viewer.spec.command, [...args, "--json", "--no-webmcp"], {
|
|
312
|
-
env, timeout
|
|
339
|
+
env, timeout, killSignal: "SIGKILL", maxBuffer: 1024 * 1024, encoding: "utf8", windowsHide: true,
|
|
340
|
+
signal: viewer.abort.signal,
|
|
313
341
|
});
|
|
314
342
|
if (!this.current(viewer))
|
|
315
343
|
throw new Error("stale viewer");
|
|
@@ -324,6 +352,8 @@ export class BrowserLive {
|
|
|
324
352
|
return data;
|
|
325
353
|
}
|
|
326
354
|
catch (error) {
|
|
355
|
+
if (timedOut(error))
|
|
356
|
+
throw new BrowserBusyError();
|
|
327
357
|
console.warn("browser-live:", error);
|
|
328
358
|
if (browserLaunchFailure(error)) {
|
|
329
359
|
// The daemon is alive but holding a launch configuration Chrome
|
|
@@ -349,6 +379,73 @@ export class BrowserLive {
|
|
|
349
379
|
throw new BrowserLiveError("The browser could not complete this action. Check that the browser engine is installed, then reconnect.", 503);
|
|
350
380
|
}
|
|
351
381
|
}
|
|
382
|
+
/** Find (or start) the session's stream and make sure its browser runs. */
|
|
383
|
+
async connect(viewer) {
|
|
384
|
+
// A failed launch already started this session's daemon teardown; wait
|
|
385
|
+
// for it so the status probe meets a fresh daemon, not the stuck one.
|
|
386
|
+
// Checked on every attempt: a reset can start while the view waits.
|
|
387
|
+
const resetting = this.sessionResets.get(viewer.session);
|
|
388
|
+
if (resetting)
|
|
389
|
+
await resetting;
|
|
390
|
+
let status = await this.command(viewer, ["stream", "status"], STATUS_PROBE_MS);
|
|
391
|
+
if (status.enabled !== true) {
|
|
392
|
+
try {
|
|
393
|
+
status = await this.command(viewer, ["stream", "enable"]);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
if (error instanceof BrowserBusyError)
|
|
397
|
+
throw error;
|
|
398
|
+
status = await this.command(viewer, ["stream", "status"]); // Another view may have enabled the same session.
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const port = browserStreamPort(status);
|
|
402
|
+
// `open` without a URL is an idempotent launch, never navigation away from the bot's page.
|
|
403
|
+
if (status.connected !== true)
|
|
404
|
+
await this.command(viewer, ["open"]);
|
|
405
|
+
// The status probe itself may have launched the browser. Give its page the
|
|
406
|
+
// standard size, as the bot's runtime does, so the page, the frames and
|
|
407
|
+
// the clicks share one geometry; at that size this changes nothing. Best
|
|
408
|
+
// effort and brief: a failure or a busy session keeps the current size.
|
|
409
|
+
if (ownsBrowserViewport(viewer.spec.env ?? {})) {
|
|
410
|
+
await this.command(viewer, [...BROWSER_VIEWPORT_ARGS], VIEWPORT_TIMEOUT_MS).catch(() => undefined);
|
|
411
|
+
}
|
|
412
|
+
return port;
|
|
413
|
+
}
|
|
414
|
+
/** While another command holds the session, keep the view open and waiting
|
|
415
|
+
* instead of failing it: start the event stream, say so, and retry with a
|
|
416
|
+
* bounded backoff until the browser answers, the viewer leaves, or the
|
|
417
|
+
* limit passes. */
|
|
418
|
+
async connectWhenFree(viewer) {
|
|
419
|
+
const deadline = Date.now() + BUSY_LIMIT_MS;
|
|
420
|
+
for (let attempt = 0;; attempt++) {
|
|
421
|
+
try {
|
|
422
|
+
return await this.connect(viewer);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
if (!(error instanceof BrowserBusyError) || !this.current(viewer))
|
|
426
|
+
throw error;
|
|
427
|
+
if (Date.now() >= deadline)
|
|
428
|
+
throw new BrowserBusyError("The bot is still using this browser. Reconnect when it has finished.");
|
|
429
|
+
}
|
|
430
|
+
viewer.waiting = true;
|
|
431
|
+
this.startEvents(viewer);
|
|
432
|
+
this.send(viewer, { type: "waiting", reason: "busy" });
|
|
433
|
+
await new Promise((resolve) => {
|
|
434
|
+
const done = () => { clearTimeout(timer); viewer.abort.signal.removeEventListener("abort", done); resolve(); };
|
|
435
|
+
const timer = setTimeout(done, BUSY_RETRY_MS[Math.min(attempt, BUSY_RETRY_MS.length - 1)]);
|
|
436
|
+
timer.unref?.();
|
|
437
|
+
viewer.abort.signal.addEventListener("abort", done, { once: true });
|
|
438
|
+
});
|
|
439
|
+
if (!this.current(viewer))
|
|
440
|
+
throw new BrowserLiveError("This browser view is no longer available.", 409);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
startEvents(viewer) {
|
|
444
|
+
if (viewer.res.headersSent)
|
|
445
|
+
return;
|
|
446
|
+
viewer.res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-store", "X-Accel-Buffering": "no", Connection: "keep-alive" });
|
|
447
|
+
viewer.res.flushHeaders();
|
|
448
|
+
}
|
|
352
449
|
async input(viewer, message) {
|
|
353
450
|
if (!viewer.port)
|
|
354
451
|
throw new BrowserLiveError("The browser stream is not connected.", 409);
|
|
@@ -415,36 +512,23 @@ export class BrowserLive {
|
|
|
415
512
|
if (this.viewers.size >= 8 || [...this.viewers.values()].filter((v) => v.session === options.session).length >= 2) {
|
|
416
513
|
throw new BrowserLiveError("Too many browser views are open. Close another browser panel first.", 429);
|
|
417
514
|
}
|
|
418
|
-
const viewer = { ...options, id: randomUUID(), closed: false, blocked: false, pendingActions: 0, restarting: false, pressedKeys: new Set(), pressedButtons: new Set() };
|
|
515
|
+
const viewer = { ...options, id: randomUUID(), closed: false, blocked: false, pendingActions: 0, restarting: false, pressedKeys: new Set(), pressedButtons: new Set(), waiting: false, abort: new AbortController() };
|
|
419
516
|
if (!this.current(viewer))
|
|
420
517
|
throw new BrowserLiveError("This browser view is no longer available.", 409);
|
|
421
518
|
this.viewers.set(viewer.id, viewer);
|
|
422
519
|
options.res.once("close", () => this.close(viewer));
|
|
423
520
|
options.res.once("error", () => this.close(viewer));
|
|
424
521
|
try {
|
|
425
|
-
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
let status = await this.command(viewer, ["stream", "status"]);
|
|
431
|
-
if (status.enabled !== true) {
|
|
432
|
-
try {
|
|
433
|
-
status = await this.command(viewer, ["stream", "enable"]);
|
|
434
|
-
}
|
|
435
|
-
catch {
|
|
436
|
-
status = await this.command(viewer, ["stream", "status"]);
|
|
437
|
-
} // Another view may have enabled the same session.
|
|
438
|
-
}
|
|
439
|
-
const port = browserStreamPort(status);
|
|
522
|
+
const port = await this.connectWhenFree(viewer);
|
|
523
|
+
// Best-effort steps of the startup swallow their own failures, an abort
|
|
524
|
+
// by close() included; a viewer closed meanwhile gets no socket.
|
|
525
|
+
if (!this.current(viewer))
|
|
526
|
+
throw new BrowserLiveError("This browser view is no longer available.", 409);
|
|
440
527
|
viewer.port = port;
|
|
441
|
-
// `open` without a URL is an idempotent launch, never navigation away from the bot's page.
|
|
442
|
-
if (status.connected !== true)
|
|
443
|
-
await this.command(viewer, ["open"]);
|
|
444
528
|
const socket = new WebSocket(`ws://127.0.0.1:${port}/?pacing=ack&maxFps=15`);
|
|
445
529
|
viewer.socket = socket;
|
|
446
|
-
|
|
447
|
-
|
|
530
|
+
this.startEvents(viewer);
|
|
531
|
+
viewer.waiting = false;
|
|
448
532
|
this.send(viewer, { type: "ready", viewerId: viewer.id });
|
|
449
533
|
this.control(viewer.session);
|
|
450
534
|
socket.addEventListener("message", (event) => {
|
|
@@ -511,7 +595,10 @@ export class BrowserLive {
|
|
|
511
595
|
catch (error) {
|
|
512
596
|
console.warn("browser-live:", error);
|
|
513
597
|
if (options.res.headersSent) {
|
|
514
|
-
|
|
598
|
+
// A view that waited out a busy browser gets the reason, and no
|
|
599
|
+
// automatic retry loop on top of the wait it already did.
|
|
600
|
+
const busy = error instanceof BrowserBusyError;
|
|
601
|
+
this.send(viewer, { type: "error", retryable: !busy, message: busy ? error.message : "The browser stream could not start." });
|
|
515
602
|
this.close(viewer);
|
|
516
603
|
return;
|
|
517
604
|
}
|
|
@@ -23,6 +23,24 @@ export function browserRuntimeEnv(overrides) {
|
|
|
23
23
|
const env = Object.fromEntries(HOST_ENV.flatMap((key) => process.env[key] === undefined ? [] : [[key, process.env[key]]]));
|
|
24
24
|
return { ...env, ...overrides };
|
|
25
25
|
}
|
|
26
|
+
/** The page size of a headless browser OMB launches, in CSS pixels.
|
|
27
|
+
*
|
|
28
|
+
* agent-browser sizes the Chrome *window* (`--window-size=1280,720`). Full
|
|
29
|
+
* Chrome in headless mode (Chrome for Testing, as the Docker image installs)
|
|
30
|
+
* takes its emulated browser UI out of that window, so the page itself comes
|
|
31
|
+
* out 1280×577 while the screencast metadata still reports 1280×720, and the
|
|
32
|
+
* live view aimed clicks at the wrong place. chrome-headless-shell has no such
|
|
33
|
+
* UI, so there this changes nothing. agent-browser has no launch option for
|
|
34
|
+
* the page size; `set viewport` sets it and resizes the window's content area,
|
|
35
|
+
* which the browser's later tabs inherit. */
|
|
36
|
+
export const BROWSER_VIEWPORT = { width: 1280, height: 720 };
|
|
37
|
+
export const BROWSER_VIEWPORT_ARGS = ["set", "viewport", String(BROWSER_VIEWPORT.width), String(BROWSER_VIEWPORT.height)];
|
|
38
|
+
/** Only a headless browser this server launches is OMB's to size. A browser
|
|
39
|
+
* attached over CDP is someone's own Chrome, and a headed window is the
|
|
40
|
+
* user's to size. */
|
|
41
|
+
export function ownsBrowserViewport(env) {
|
|
42
|
+
return env.AGENT_BROWSER_HEADLESS === "1" && !env.AGENT_BROWSER_CDP;
|
|
43
|
+
}
|
|
26
44
|
export class TransportError extends Error {
|
|
27
45
|
}
|
|
28
46
|
/** A server-owned JSONL client. Neither child stderr nor its environment is
|
|
@@ -215,9 +233,11 @@ export class BrowserRuntime {
|
|
|
215
233
|
toolLists = new Map();
|
|
216
234
|
options;
|
|
217
235
|
closeBrowser;
|
|
218
|
-
|
|
236
|
+
applyViewport;
|
|
237
|
+
constructor({ closeBrowser, applyViewport, ...options } = {}) {
|
|
219
238
|
this.options = { requestTimeoutMs: 120_000, takeoverTimeoutMs: 15_000, idleMs: 60_000, maxPending: 16, resultBudget: DEFAULT_BROWSER_RESULT_BUDGET, ...options };
|
|
220
239
|
this.closeBrowser = closeBrowser ?? (async () => false);
|
|
240
|
+
this.applyViewport = applyViewport;
|
|
221
241
|
}
|
|
222
242
|
gate(session) {
|
|
223
243
|
let gate = this.gates.get(session);
|
|
@@ -300,6 +320,16 @@ export class BrowserRuntime {
|
|
|
300
320
|
beforeDispatch?.();
|
|
301
321
|
if (method === "tools/call" && this.gate(session).owner !== null)
|
|
302
322
|
throw new Error(BROWSER_CONTROL_REFUSAL);
|
|
323
|
+
if (method === "tools/call" && this.applyViewport) {
|
|
324
|
+
// The bot's first call on a transport may launch the browser. Size its
|
|
325
|
+
// page first, once per transport, so the page, the bot's screenshots
|
|
326
|
+
// and the live view agree. Failure only leaves the engine's default.
|
|
327
|
+
entry.viewport ??= this.applyViewport(spec).catch(() => undefined);
|
|
328
|
+
await entry.viewport;
|
|
329
|
+
beforeDispatch?.();
|
|
330
|
+
if (this.gate(session).owner !== null)
|
|
331
|
+
throw new Error(BROWSER_CONTROL_REFUSAL);
|
|
332
|
+
}
|
|
303
333
|
try {
|
|
304
334
|
// The model sees slimmed schemas and text-only, bounded results; the
|
|
305
335
|
// launch/session parameters OMB owns never reach the engine from a call.
|