mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
feat(browser): configure local startup timeouts
This commit is contained in:
@@ -6,6 +6,9 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Browser/config: allow local managed Chrome launch discovery and post-launch
|
||||
CDP readiness timeouts to be raised for slower hosts such as Raspberry Pi.
|
||||
Fixes #66803. Thanks @beat843796.
|
||||
- Browser/CLI: add `openclaw browser start --headless` as a one-shot local managed browser launch override without rewriting persisted browser config. Thanks @BenediktSchackenberg.
|
||||
- CLI/Crestodian: open interactive Crestodian in the full OpenClaw TUI shell instead of a basic readline prompt.
|
||||
- CLI/Crestodian: shorten the startup greeting to the active planner/model, config state, Gateway probe result, and next debug action instead of dumping every discovered backend.
|
||||
|
||||
@@ -266,6 +266,10 @@ See [Plugins](/tools/plugin).
|
||||
- Local managed profiles can set `executablePath` to override the global
|
||||
`browser.executablePath` for that profile. Use this to run one profile in
|
||||
Chrome and another in Brave.
|
||||
- Local managed profiles use `browser.localLaunchTimeoutMs` for Chrome CDP HTTP
|
||||
discovery after process start and `browser.localCdpReadyTimeoutMs` for
|
||||
post-launch CDP websocket readiness. Raise them on slower hosts where Chrome
|
||||
starts successfully but readiness checks race startup.
|
||||
- Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
|
||||
- `browser.executablePath` accepts `~` for your OS home directory.
|
||||
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
|
||||
|
||||
@@ -125,15 +125,23 @@ curl -s http://127.0.0.1:18791/tabs
|
||||
|
||||
### Config Reference
|
||||
|
||||
| Option | Description | Default |
|
||||
| --------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `browser.enabled` | Enable browser control | `true` |
|
||||
| `browser.executablePath` | Path to a Chromium-based browser binary (Chrome/Brave/Edge/Chromium) | auto-detected (prefers default browser when Chromium-based) |
|
||||
| `browser.headless` | Run without GUI | `false` |
|
||||
| `OPENCLAW_BROWSER_HEADLESS` | Per-process override for local managed browser headless mode | unset |
|
||||
| `browser.noSandbox` | Add `--no-sandbox` flag (needed for some Linux setups) | `false` |
|
||||
| `browser.attachOnly` | Don't launch browser, only attach to existing | `false` |
|
||||
| `browser.cdpPort` | Chrome DevTools Protocol port | `18800` |
|
||||
| Option | Description | Default |
|
||||
| -------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `browser.enabled` | Enable browser control | `true` |
|
||||
| `browser.executablePath` | Path to a Chromium-based browser binary (Chrome/Brave/Edge/Chromium) | auto-detected (prefers default browser when Chromium-based) |
|
||||
| `browser.headless` | Run without GUI | `false` |
|
||||
| `OPENCLAW_BROWSER_HEADLESS` | Per-process override for local managed browser headless mode | unset |
|
||||
| `browser.noSandbox` | Add `--no-sandbox` flag (needed for some Linux setups) | `false` |
|
||||
| `browser.attachOnly` | Don't launch browser, only attach to existing | `false` |
|
||||
| `browser.cdpPort` | Chrome DevTools Protocol port | `18800` |
|
||||
| `browser.localLaunchTimeoutMs` | Local managed Chrome discovery timeout | `15000` |
|
||||
| `browser.localCdpReadyTimeoutMs` | Local managed post-launch CDP readiness timeout | `8000` |
|
||||
|
||||
On Raspberry Pi, older VPS hosts, or slow storage, raise
|
||||
`browser.localLaunchTimeoutMs` when Chrome needs more time to expose its CDP HTTP
|
||||
endpoint. Raise `browser.localCdpReadyTimeoutMs` when launch succeeds but
|
||||
`openclaw browser start` still reports `not reachable after start`. Values are
|
||||
capped at 120000 ms.
|
||||
|
||||
### Problem: "No Chrome tabs found for profile=\"user\""
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ Browser settings live in `~/.openclaw/openclaw.json`.
|
||||
// cdpUrl: "http://127.0.0.1:18792", // legacy single-profile override
|
||||
remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms)
|
||||
remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms)
|
||||
localLaunchTimeoutMs: 15000, // local managed Chrome discovery timeout (ms)
|
||||
localCdpReadyTimeoutMs: 8000, // local managed post-launch CDP readiness timeout (ms)
|
||||
actionTimeoutMs: 60000, // default browser act timeout (ms)
|
||||
tabCleanup: {
|
||||
enabled: true, // default: true
|
||||
@@ -174,6 +176,11 @@ Browser settings live in `~/.openclaw/openclaw.json`.
|
||||
- Control service binds to loopback on a port derived from `gateway.port` (default `18791` = gateway + 2). Overriding `gateway.port` or `OPENCLAW_GATEWAY_PORT` shifts the derived ports in the same family.
|
||||
- Local `openclaw` profiles auto-assign `cdpPort`/`cdpUrl`; set those only for remote CDP. `cdpUrl` defaults to the managed local CDP port when unset.
|
||||
- `remoteCdpTimeoutMs` applies to remote (non-loopback) CDP HTTP reachability checks; `remoteCdpHandshakeTimeoutMs` applies to remote CDP WebSocket handshakes.
|
||||
- `localLaunchTimeoutMs` is the budget for a locally launched managed Chrome
|
||||
process to expose its CDP HTTP endpoint. `localCdpReadyTimeoutMs` is the
|
||||
follow-up budget for CDP websocket readiness after the process is discovered.
|
||||
Raise these on Raspberry Pi, low-end VPS, or older hardware where Chromium
|
||||
starts slowly. Values are capped at 120000 ms.
|
||||
- `actionTimeoutMs` is the default budget for browser `act` requests when the caller does not pass `timeoutMs`. The client transport adds a small slack window so long waits can finish instead of timing out at the HTTP boundary.
|
||||
- `tabCleanup` is best-effort cleanup for tabs opened by primary-agent browser sessions. Subagent, cron, and ACP lifecycle cleanup still closes their explicit tracked tabs at session end; primary sessions keep active tabs reusable, then close idle or excess tracked tabs in the background.
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ function buildResolvedConfig(): ResolvedBrowserConfig {
|
||||
cdpIsLoopback: true,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
extraArgs: [],
|
||||
color: DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||
executablePath: undefined,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS } from "./constants.js";
|
||||
|
||||
export const CDP_HTTP_REQUEST_TIMEOUT_MS = 1500;
|
||||
export const CDP_WS_HANDSHAKE_TIMEOUT_MS = 5000;
|
||||
export const CDP_JSON_NEW_TIMEOUT_MS = 1500;
|
||||
@@ -8,7 +10,7 @@ export const CHROME_BOOTSTRAP_PREFS_TIMEOUT_MS = 10_000;
|
||||
export const CHROME_BOOTSTRAP_PREFS_POLL_MS = 100;
|
||||
export const CHROME_BOOTSTRAP_EXIT_TIMEOUT_MS = 5000;
|
||||
export const CHROME_BOOTSTRAP_EXIT_POLL_MS = 50;
|
||||
export const CHROME_LAUNCH_READY_WINDOW_MS = 15_000;
|
||||
export const CHROME_LAUNCH_READY_WINDOW_MS = DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS;
|
||||
export const CHROME_LAUNCH_READY_POLL_MS = 200;
|
||||
export const CHROME_STOP_TIMEOUT_MS = 2500;
|
||||
export const CHROME_STOP_PROBE_TIMEOUT_MS = 200;
|
||||
|
||||
@@ -374,6 +374,8 @@ describe("chrome.ts internal", () => {
|
||||
headless: true,
|
||||
noSandbox: true,
|
||||
extraArgs: [],
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
}) as unknown as ResolvedBrowserConfig;
|
||||
|
||||
it("rejects a remote profile before attempting to spawn", async () => {
|
||||
@@ -544,7 +546,11 @@ describe("chrome.ts internal", () => {
|
||||
try {
|
||||
vi.spyOn(fs, "existsSync").mockImplementation((p) => {
|
||||
const s = String(p);
|
||||
if (s.includes("google-chrome")) {
|
||||
if (
|
||||
s.includes("Google Chrome") ||
|
||||
s.includes("google-chrome") ||
|
||||
s.includes("/usr/bin/chromium")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (s.endsWith("Local State") || s.endsWith("Preferences")) {
|
||||
@@ -574,6 +580,41 @@ describe("chrome.ts internal", () => {
|
||||
Object.defineProperty(process, "platform", { value: originalPlatform });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses the configured local launch timeout while waiting for CDP discovery", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const executablePath = path.join(tmpDir, "chrome");
|
||||
await fsp.writeFile(executablePath, "");
|
||||
const existsSync = fs.existsSync.bind(fs);
|
||||
vi.spyOn(fs, "existsSync").mockImplementation((p) => {
|
||||
const s = String(p);
|
||||
if (s.endsWith("Local State") || s.endsWith("Preferences")) {
|
||||
return true;
|
||||
}
|
||||
return existsSync(p);
|
||||
});
|
||||
const fakeProc = makeFakeProc();
|
||||
spawnMock.mockReturnValue(fakeProc);
|
||||
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("ECONNREFUSED")));
|
||||
|
||||
const resolved = {
|
||||
...makeResolved(),
|
||||
executablePath,
|
||||
localLaunchTimeoutMs: 1,
|
||||
};
|
||||
const profile = makeProfile(55556);
|
||||
const rejection = expect(launchOpenClawChrome(resolved, profile)).rejects.toThrow(
|
||||
/Failed to start Chrome CDP/,
|
||||
);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
await rejection;
|
||||
expect(fakeProc.kill).toHaveBeenCalledWith("SIGKILL");
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("stopOpenClawChrome SIGKILL fallback", () => {
|
||||
|
||||
@@ -768,6 +768,8 @@ describe("browser chrome launch args", () => {
|
||||
evaluateEnabled: false,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
actionTimeoutMs: 60_000,
|
||||
extraArgs: [],
|
||||
color: "#FF4500",
|
||||
|
||||
@@ -520,7 +520,8 @@ export async function launchOpenClawChrome(
|
||||
proc.stderr?.on("data", onStderr);
|
||||
|
||||
try {
|
||||
const readyDeadline = Date.now() + CHROME_LAUNCH_READY_WINDOW_MS;
|
||||
const readyDeadline =
|
||||
Date.now() + (resolved.localLaunchTimeoutMs ?? CHROME_LAUNCH_READY_WINDOW_MS);
|
||||
while (Date.now() < readyDeadline) {
|
||||
if (await isChromeReachable(profile.cdpUrl)) {
|
||||
break;
|
||||
|
||||
@@ -445,6 +445,35 @@ describe("browser config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed browser startup timeouts", () => {
|
||||
it("uses defaults for local launch and post-launch readiness windows", () => {
|
||||
const resolved = resolveBrowserConfig({});
|
||||
|
||||
expect(resolved.localLaunchTimeoutMs).toBe(15_000);
|
||||
expect(resolved.localCdpReadyTimeoutMs).toBe(8_000);
|
||||
});
|
||||
|
||||
it("accepts custom local startup timeout values", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
localLaunchTimeoutMs: 45_000,
|
||||
localCdpReadyTimeoutMs: 30_000,
|
||||
});
|
||||
|
||||
expect(resolved.localLaunchTimeoutMs).toBe(45_000);
|
||||
expect(resolved.localCdpReadyTimeoutMs).toBe(30_000);
|
||||
});
|
||||
|
||||
it("clamps oversized local startup timeout values", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
localLaunchTimeoutMs: 999_999,
|
||||
localCdpReadyTimeoutMs: 999_999,
|
||||
});
|
||||
|
||||
expect(resolved.localLaunchTimeoutMs).toBe(120_000);
|
||||
expect(resolved.localCdpReadyTimeoutMs).toBe(120_000);
|
||||
});
|
||||
});
|
||||
|
||||
it("inherits executablePath from global browser config when profile override is not set", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
executablePath: "~/bin/chrome-global",
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
|
||||
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
|
||||
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||
DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS,
|
||||
DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS,
|
||||
DEFAULT_BROWSER_TAB_CLEANUP_IDLE_MINUTES,
|
||||
DEFAULT_BROWSER_TAB_CLEANUP_MAX_TABS_PER_SESSION,
|
||||
DEFAULT_BROWSER_TAB_CLEANUP_SWEEP_MINUTES,
|
||||
@@ -39,6 +41,8 @@ export {
|
||||
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
|
||||
DEFAULT_BROWSER_DEFAULT_PROFILE_NAME,
|
||||
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||
DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS,
|
||||
DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS,
|
||||
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||
DEFAULT_OPENCLAW_BROWSER_ENABLED,
|
||||
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
||||
@@ -69,6 +73,8 @@ export type ResolvedBrowserConfig = {
|
||||
cdpIsLoopback: boolean;
|
||||
remoteCdpTimeoutMs: number;
|
||||
remoteCdpHandshakeTimeoutMs: number;
|
||||
localLaunchTimeoutMs: number;
|
||||
localCdpReadyTimeoutMs: number;
|
||||
actionTimeoutMs: number;
|
||||
color: string;
|
||||
executablePath?: string;
|
||||
@@ -106,6 +112,7 @@ export type ResolvedBrowserProfile = {
|
||||
};
|
||||
|
||||
const DEFAULT_BROWSER_CDP_PORT_RANGE_START = 18800;
|
||||
const MAX_BROWSER_STARTUP_TIMEOUT_MS = 120_000;
|
||||
export const OPENCLAW_BROWSER_HEADLESS_ENV = "OPENCLAW_BROWSER_HEADLESS";
|
||||
|
||||
export type ManagedBrowserHeadlessSource =
|
||||
@@ -144,6 +151,14 @@ function normalizeTimeoutMs(raw: number | undefined, fallback: number): number {
|
||||
return value < 0 ? fallback : value;
|
||||
}
|
||||
|
||||
function normalizeStartupTimeoutMs(raw: number | undefined, fallback: number): number {
|
||||
const value = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
|
||||
if (value <= 0) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.min(value, MAX_BROWSER_STARTUP_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function normalizeNonNegativeInteger(raw: number | undefined, fallback: number): number {
|
||||
const value = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
|
||||
return value < 0 ? fallback : value;
|
||||
@@ -297,6 +312,14 @@ export function resolveBrowserConfig(
|
||||
cfg?.remoteCdpHandshakeTimeoutMs,
|
||||
Math.max(2000, remoteCdpTimeoutMs * 2),
|
||||
);
|
||||
const localLaunchTimeoutMs = normalizeStartupTimeoutMs(
|
||||
cfg?.localLaunchTimeoutMs,
|
||||
DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS,
|
||||
);
|
||||
const localCdpReadyTimeoutMs = normalizeStartupTimeoutMs(
|
||||
cfg?.localCdpReadyTimeoutMs,
|
||||
DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS,
|
||||
);
|
||||
const actionTimeoutMs = normalizeTimeoutMs(
|
||||
cfg?.actionTimeoutMs,
|
||||
DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
|
||||
@@ -382,6 +405,8 @@ export function resolveBrowserConfig(
|
||||
cdpIsLoopback: isLoopbackHost(cdpInfo.parsed.hostname),
|
||||
remoteCdpTimeoutMs,
|
||||
remoteCdpHandshakeTimeoutMs,
|
||||
localLaunchTimeoutMs,
|
||||
localCdpReadyTimeoutMs,
|
||||
actionTimeoutMs,
|
||||
color: defaultColor,
|
||||
executablePath,
|
||||
|
||||
@@ -4,6 +4,8 @@ export const DEFAULT_OPENCLAW_BROWSER_COLOR = "#FF4500";
|
||||
export const DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME = "openclaw";
|
||||
export const DEFAULT_BROWSER_DEFAULT_PROFILE_NAME = "openclaw";
|
||||
export const DEFAULT_BROWSER_ACTION_TIMEOUT_MS = 60_000;
|
||||
export const DEFAULT_BROWSER_LOCAL_LAUNCH_TIMEOUT_MS = 15_000;
|
||||
export const DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS = 8_000;
|
||||
export const DEFAULT_BROWSER_SCREENSHOT_TIMEOUT_MS = 20_000;
|
||||
export const DEFAULT_BROWSER_TAB_CLEANUP_IDLE_MINUTES = 120;
|
||||
export const DEFAULT_BROWSER_TAB_CLEANUP_MAX_TABS_PER_SESSION = 8;
|
||||
|
||||
@@ -191,7 +191,8 @@ export function createProfileAvailability({
|
||||
const waitForCdpReadyAfterLaunch = async (): Promise<void> => {
|
||||
// launchOpenClawChrome() can return before Chrome is fully ready to serve /json/version + CDP WS.
|
||||
// If a follow-up call races ahead, we can hit PortInUseError trying to launch again on the same port.
|
||||
const deadlineMs = Date.now() + CDP_READY_AFTER_LAUNCH_WINDOW_MS;
|
||||
const deadlineMs =
|
||||
Date.now() + (state().resolved.localCdpReadyTimeoutMs ?? CDP_READY_AFTER_LAUNCH_WINDOW_MS);
|
||||
while (Date.now() < deadlineMs) {
|
||||
const remainingMs = Math.max(0, deadlineMs - Date.now());
|
||||
// Keep each attempt short; loopback profiles derive a WS timeout from this value.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS } from "./constants.js";
|
||||
|
||||
export const MANAGED_BROWSER_PAGE_TAB_LIMIT = 8;
|
||||
|
||||
export const OPEN_TAB_DISCOVERY_WINDOW_MS = 2000;
|
||||
export const OPEN_TAB_DISCOVERY_POLL_MS = 100;
|
||||
|
||||
export const CDP_READY_AFTER_LAUNCH_WINDOW_MS = 8000;
|
||||
export const CDP_READY_AFTER_LAUNCH_WINDOW_MS = DEFAULT_BROWSER_LOCAL_CDP_READY_TIMEOUT_MS;
|
||||
export const CDP_READY_AFTER_LAUNCH_POLL_MS = 100;
|
||||
export const CDP_READY_AFTER_LAUNCH_MIN_TIMEOUT_MS = 75;
|
||||
export const CDP_READY_AFTER_LAUNCH_MAX_TIMEOUT_MS = 250;
|
||||
|
||||
@@ -90,6 +90,22 @@ describe("browser server-context ensureBrowserAvailable", () => {
|
||||
expect(stopOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("uses configured local CDP readiness timeout after launching", async () => {
|
||||
const { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile, state } =
|
||||
setupEnsureBrowserAvailableHarness();
|
||||
state.resolved.localCdpReadyTimeoutMs = 250;
|
||||
isChromeCdpReady.mockResolvedValue(false);
|
||||
mockLaunchedChrome(launchOpenClawChrome, 322);
|
||||
|
||||
const promise = profile.ensureBrowserAvailable();
|
||||
const rejected = expect(promise).rejects.toThrow("not reachable after start");
|
||||
await vi.advanceTimersByTimeAsync(300);
|
||||
await rejected;
|
||||
|
||||
expect(launchOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
expect(stopOpenClawChrome).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("deduplicates concurrent lazy-start calls to prevent PortInUseError", async () => {
|
||||
const { launchOpenClawChrome, stopOpenClawChrome, isChromeCdpReady, profile } =
|
||||
setupEnsureBrowserAvailableHarness();
|
||||
|
||||
@@ -44,6 +44,8 @@ function makeState(): BrowserServerState {
|
||||
cdpIsLoopback: true,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
actionTimeoutMs: 60_000,
|
||||
color: "#FF4500",
|
||||
headless: false,
|
||||
|
||||
@@ -24,6 +24,8 @@ export function makeState(
|
||||
cdpIsLoopback: profile !== "remote",
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
actionTimeoutMs: 60_000,
|
||||
evaluateEnabled: false,
|
||||
extraArgs: [],
|
||||
|
||||
@@ -37,6 +37,8 @@ export function makeBrowserServerState(params?: {
|
||||
evaluateEnabled: false,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
actionTimeoutMs: 60_000,
|
||||
extraArgs: [],
|
||||
color: profile.color,
|
||||
|
||||
@@ -245,6 +245,8 @@ describe("ensureSandboxBrowser create args", () => {
|
||||
cdpPortRangeEnd: 18899,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
color: "#FF4500",
|
||||
headless: false,
|
||||
noSandbox: false,
|
||||
|
||||
@@ -97,6 +97,8 @@ function buildSandboxBrowserResolvedConfig(params: {
|
||||
cdpPortRangeEnd: cdpPortRange.end,
|
||||
remoteCdpTimeoutMs: 1500,
|
||||
remoteCdpHandshakeTimeoutMs: 3000,
|
||||
localLaunchTimeoutMs: 15_000,
|
||||
localCdpReadyTimeoutMs: 8_000,
|
||||
actionTimeoutMs: DEFAULT_BROWSER_ACTION_TIMEOUT_MS,
|
||||
color: DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||
executablePath: undefined,
|
||||
|
||||
@@ -215,6 +215,28 @@ describe("config schema regressions", () => {
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts browser local startup timeout settings", () => {
|
||||
const res = validateConfigObject({
|
||||
browser: {
|
||||
localLaunchTimeoutMs: 45_000,
|
||||
localCdpReadyTimeoutMs: 30_000,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects out-of-range browser local startup timeout settings", () => {
|
||||
const res = validateConfigObject({
|
||||
browser: {
|
||||
localLaunchTimeoutMs: 120_001,
|
||||
localCdpReadyTimeoutMs: 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects browser.extraArgs with non-array value", () => {
|
||||
const res = validateConfigObject({
|
||||
browser: {
|
||||
|
||||
@@ -634,6 +634,22 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
description:
|
||||
"Timeout in milliseconds for post-connect CDP handshake readiness checks against remote browser targets. Raise this for slow-start remote browsers and lower to fail fast in automation loops.",
|
||||
},
|
||||
localLaunchTimeoutMs: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 120000,
|
||||
title: "Browser Local Launch Timeout (ms)",
|
||||
description:
|
||||
"Timeout in milliseconds for locally launched managed Chrome to expose its CDP HTTP endpoint after process start. Raise this on slow single-board computers or older hosts.",
|
||||
},
|
||||
localCdpReadyTimeoutMs: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
maximum: 120000,
|
||||
title: "Browser Local CDP Ready Timeout (ms)",
|
||||
description:
|
||||
"Timeout in milliseconds for a locally launched managed browser to finish CDP websocket readiness after the process is discovered. Raise this when Chrome starts but browser start still reports CDP not reachable.",
|
||||
},
|
||||
actionTimeoutMs: {
|
||||
type: "integer",
|
||||
exclusiveMinimum: 0,
|
||||
@@ -23977,6 +23993,16 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = {
|
||||
help: "Default timeout in milliseconds for browser act requests before the client gives up waiting. Raise this when healthy waits or UI interactions exceed the default request budget.",
|
||||
tags: ["performance"],
|
||||
},
|
||||
"browser.localLaunchTimeoutMs": {
|
||||
label: "Browser Local Launch Timeout (ms)",
|
||||
help: "Timeout in milliseconds for locally launched managed Chrome to expose its CDP HTTP endpoint after process start. Raise this on slow single-board computers or older hosts.",
|
||||
tags: ["performance"],
|
||||
},
|
||||
"browser.localCdpReadyTimeoutMs": {
|
||||
label: "Browser Local CDP Ready Timeout (ms)",
|
||||
help: "Timeout in milliseconds for a locally launched managed browser to finish CDP websocket readiness after the process is discovered. Raise this when Chrome starts but browser start still reports CDP not reachable.",
|
||||
tags: ["performance"],
|
||||
},
|
||||
"browser.color": {
|
||||
label: "Browser Accent Color",
|
||||
help: "Default accent color used for browser profile/UI cues where colored identity hints are displayed. Use consistent colors to help operators identify active browser profile context quickly.",
|
||||
|
||||
@@ -260,6 +260,10 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Remote CDP websocket URL used to attach to an externally managed browser instance. Use this for centralized browser hosts and keep URL access restricted to trusted network paths.",
|
||||
"browser.actionTimeoutMs":
|
||||
"Default timeout in milliseconds for browser act requests before the client gives up waiting. Raise this when healthy waits or UI interactions exceed the default request budget.",
|
||||
"browser.localLaunchTimeoutMs":
|
||||
"Timeout in milliseconds for locally launched managed Chrome to expose its CDP HTTP endpoint after process start. Raise this on slow single-board computers or older hosts.",
|
||||
"browser.localCdpReadyTimeoutMs":
|
||||
"Timeout in milliseconds for a locally launched managed browser to finish CDP websocket readiness after the process is discovered. Raise this when Chrome starts but browser start still reports CDP not reachable.",
|
||||
"browser.color":
|
||||
"Default accent color used for browser profile/UI cues where colored identity hints are displayed. Use consistent colors to help operators identify active browser profile context quickly.",
|
||||
"browser.executablePath":
|
||||
|
||||
@@ -141,6 +141,8 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"browser.enabled": "Browser Enabled",
|
||||
"browser.cdpUrl": "Browser CDP URL",
|
||||
"browser.actionTimeoutMs": "Browser Action Timeout (ms)",
|
||||
"browser.localLaunchTimeoutMs": "Browser Local Launch Timeout (ms)",
|
||||
"browser.localCdpReadyTimeoutMs": "Browser Local CDP Ready Timeout (ms)",
|
||||
"browser.color": "Browser Accent Color",
|
||||
"browser.executablePath": "Browser Executable Path",
|
||||
"browser.headless": "Browser Headless Mode",
|
||||
|
||||
@@ -54,6 +54,10 @@ export type BrowserConfig = {
|
||||
remoteCdpTimeoutMs?: number;
|
||||
/** Remote CDP WebSocket handshake timeout (ms). Default: max(remoteCdpTimeoutMs * 2, 2000). */
|
||||
remoteCdpHandshakeTimeoutMs?: number;
|
||||
/** Local managed browser launch discovery timeout (ms). Default: 15000. */
|
||||
localLaunchTimeoutMs?: number;
|
||||
/** Local managed browser post-launch CDP readiness timeout (ms). Default: 8000. */
|
||||
localCdpReadyTimeoutMs?: number;
|
||||
/** Default browser act timeout (ms). Default: 60000. */
|
||||
actionTimeoutMs?: number;
|
||||
/** Accent color for the openclaw browser profile (hex). Default: #FF4500 */
|
||||
|
||||
@@ -396,6 +396,8 @@ export const OpenClawSchema = z
|
||||
cdpUrl: z.string().optional(),
|
||||
remoteCdpTimeoutMs: z.number().int().nonnegative().optional(),
|
||||
remoteCdpHandshakeTimeoutMs: z.number().int().nonnegative().optional(),
|
||||
localLaunchTimeoutMs: z.number().int().positive().max(120_000).optional(),
|
||||
localCdpReadyTimeoutMs: z.number().int().positive().max(120_000).optional(),
|
||||
actionTimeoutMs: z.number().int().positive().optional(),
|
||||
color: z.string().optional(),
|
||||
executablePath: z.string().optional(),
|
||||
|
||||
@@ -31,6 +31,8 @@ export type ResolvedBrowserConfig = {
|
||||
cdpIsLoopback: boolean;
|
||||
remoteCdpTimeoutMs: number;
|
||||
remoteCdpHandshakeTimeoutMs: number;
|
||||
localLaunchTimeoutMs: number;
|
||||
localCdpReadyTimeoutMs: number;
|
||||
actionTimeoutMs: number;
|
||||
color: string;
|
||||
executablePath?: string;
|
||||
|
||||
Reference in New Issue
Block a user