fix: tune remote CDP timeouts
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
- Browser: prefer stable Chrome for auto-detect, with Brave/Edge fallbacks and updated docs. (#983) — thanks @cpojer.
|
||||
- Browser: fix `tab not found` for extension relay snapshots/actions when Playwright blocks `newCDPSession` (use the single available Page).
|
||||
- Browser: upgrade `ws` → `wss` when remote CDP uses `https` (fixes Browserless handshake).
|
||||
- Browser: increase remote CDP reachability timeouts + add `remoteCdpTimeoutMs`/`remoteCdpHandshakeTimeoutMs`.
|
||||
- Browser: preserve auth/query tokens for remote CDP endpoints and pass Basic auth for CDP HTTP/WS. (#895) — thanks @mukhtharcm.
|
||||
- Telegram: add bidirectional reaction support with configurable notifications and agent guidance. (#964) — thanks @bohdanpodvirnyi.
|
||||
- Telegram: allow custom commands in the bot menu (merged with native; conflicts ignored). (#860) — thanks @nachoiacovino.
|
||||
|
||||
@@ -59,6 +59,8 @@ Browser settings live in `~/.clawdbot/clawdbot.json`.
|
||||
enabled: true, // default: true
|
||||
controlUrl: "http://127.0.0.1:18791",
|
||||
cdpUrl: "http://127.0.0.1:18792", // defaults to controlUrl + 1
|
||||
remoteCdpTimeoutMs: 1500, // remote CDP HTTP timeout (ms)
|
||||
remoteCdpHandshakeTimeoutMs: 3000, // remote CDP WebSocket handshake timeout (ms)
|
||||
defaultProfile: "chrome",
|
||||
color: "#FF4500",
|
||||
headless: false,
|
||||
@@ -79,6 +81,8 @@ Notes:
|
||||
- If you override the Gateway port (`gateway.port` or `CLAWDBOT_GATEWAY_PORT`),
|
||||
the default browser ports shift to stay in the same “family” (control = gateway + 2).
|
||||
- `cdpUrl` defaults to `controlUrl + 1` when unset.
|
||||
- `remoteCdpTimeoutMs` applies to remote (non-loopback) CDP reachability checks.
|
||||
- `remoteCdpHandshakeTimeoutMs` applies to remote CDP WebSocket reachability checks.
|
||||
- `attachOnly: true` means “never launch a local browser; only attach if it is already running.”
|
||||
- `color` + per-profile `color` tint the browser UI so you can see which profile is active.
|
||||
- Default profile is `chrome` (extension relay). Use `defaultProfile: "clawd"` for the managed browser.
|
||||
|
||||
@@ -19,6 +19,8 @@ describe("browser config", () => {
|
||||
expect(clawd?.driver).toBe("clawd");
|
||||
expect(clawd?.cdpPort).toBe(18800);
|
||||
expect(clawd?.cdpUrl).toBe("http://127.0.0.1:18800");
|
||||
expect(resolved.remoteCdpTimeoutMs).toBe(1500);
|
||||
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(3000);
|
||||
});
|
||||
|
||||
it("derives default ports from CLAWDBOT_GATEWAY_PORT when unset", () => {
|
||||
@@ -52,6 +54,16 @@ describe("browser config", () => {
|
||||
expect(resolved.color).toBe("#FF4500");
|
||||
});
|
||||
|
||||
it("supports custom remote CDP timeouts", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
controlUrl: "http://127.0.0.1:18791",
|
||||
remoteCdpTimeoutMs: 2200,
|
||||
remoteCdpHandshakeTimeoutMs: 5000,
|
||||
});
|
||||
expect(resolved.remoteCdpTimeoutMs).toBe(2200);
|
||||
expect(resolved.remoteCdpHandshakeTimeoutMs).toBe(5000);
|
||||
});
|
||||
|
||||
it("falls back to default color for invalid hex", () => {
|
||||
const resolved = resolveBrowserConfig({
|
||||
controlUrl: "http://localhost:18791",
|
||||
|
||||
@@ -21,6 +21,8 @@ export type ResolvedBrowserConfig = {
|
||||
cdpProtocol: "http" | "https";
|
||||
cdpHost: string;
|
||||
cdpIsLoopback: boolean;
|
||||
remoteCdpTimeoutMs: number;
|
||||
remoteCdpHandshakeTimeoutMs: number;
|
||||
color: string;
|
||||
executablePath?: string;
|
||||
headless: boolean;
|
||||
@@ -61,6 +63,11 @@ function normalizeHexColor(raw: string | undefined) {
|
||||
return normalized.toUpperCase();
|
||||
}
|
||||
|
||||
function normalizeTimeoutMs(raw: number | undefined, fallback: number) {
|
||||
const value = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
|
||||
return value < 0 ? fallback : value;
|
||||
}
|
||||
|
||||
export function parseHttpUrl(raw: string, label: string) {
|
||||
const trimmed = raw.trim();
|
||||
const parsed = new URL(trimmed);
|
||||
@@ -149,6 +156,11 @@ export function resolveBrowserConfig(cfg: BrowserConfig | undefined): ResolvedBr
|
||||
);
|
||||
const controlPort = controlInfo.port;
|
||||
const defaultColor = normalizeHexColor(cfg?.color);
|
||||
const remoteCdpTimeoutMs = normalizeTimeoutMs(cfg?.remoteCdpTimeoutMs, 1500);
|
||||
const remoteCdpHandshakeTimeoutMs = normalizeTimeoutMs(
|
||||
cfg?.remoteCdpHandshakeTimeoutMs,
|
||||
Math.max(2000, remoteCdpTimeoutMs * 2),
|
||||
);
|
||||
|
||||
const derivedCdpRange = deriveDefaultBrowserCdpPortRange(controlPort);
|
||||
|
||||
@@ -206,6 +218,8 @@ export function resolveBrowserConfig(cfg: BrowserConfig | undefined): ResolvedBr
|
||||
cdpProtocol,
|
||||
cdpHost: cdpInfo.parsed.hostname,
|
||||
cdpIsLoopback: isLoopbackHost(cdpInfo.parsed.hostname),
|
||||
remoteCdpTimeoutMs,
|
||||
remoteCdpHandshakeTimeoutMs,
|
||||
color: defaultColor,
|
||||
executablePath,
|
||||
headless,
|
||||
|
||||
@@ -187,13 +187,36 @@ function createProfileContext(
|
||||
};
|
||||
};
|
||||
|
||||
const isReachable = async (timeoutMs = 300) => {
|
||||
const wsTimeout = Math.max(200, Math.min(2000, timeoutMs * 2));
|
||||
return await isChromeCdpReady(profile.cdpUrl, timeoutMs, wsTimeout);
|
||||
const resolveRemoteHttpTimeout = (timeoutMs: number | undefined) => {
|
||||
if (profile.cdpIsLoopback) return timeoutMs ?? 300;
|
||||
const resolved = state().resolved;
|
||||
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
|
||||
return Math.max(Math.floor(timeoutMs), resolved.remoteCdpTimeoutMs);
|
||||
}
|
||||
return resolved.remoteCdpTimeoutMs;
|
||||
};
|
||||
|
||||
const isHttpReachable = async (timeoutMs = 300) => {
|
||||
return await isChromeReachable(profile.cdpUrl, timeoutMs);
|
||||
const resolveRemoteWsTimeout = (timeoutMs: number | undefined) => {
|
||||
if (profile.cdpIsLoopback) {
|
||||
const base = timeoutMs ?? 300;
|
||||
return Math.max(200, Math.min(2000, base * 2));
|
||||
}
|
||||
const resolved = state().resolved;
|
||||
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) {
|
||||
return Math.max(Math.floor(timeoutMs) * 2, resolved.remoteCdpHandshakeTimeoutMs);
|
||||
}
|
||||
return resolved.remoteCdpHandshakeTimeoutMs;
|
||||
};
|
||||
|
||||
const isReachable = async (timeoutMs?: number) => {
|
||||
const httpTimeout = resolveRemoteHttpTimeout(timeoutMs);
|
||||
const wsTimeout = resolveRemoteWsTimeout(timeoutMs);
|
||||
return await isChromeCdpReady(profile.cdpUrl, httpTimeout, wsTimeout);
|
||||
};
|
||||
|
||||
const isHttpReachable = async (timeoutMs?: number) => {
|
||||
const httpTimeout = resolveRemoteHttpTimeout(timeoutMs);
|
||||
return await isChromeReachable(profile.cdpUrl, httpTimeout);
|
||||
};
|
||||
|
||||
const attachRunning = (running: NonNullable<ProfileRuntimeState["running"]>) => {
|
||||
|
||||
@@ -167,6 +167,8 @@ const FIELD_LABELS: Record<string, string> = {
|
||||
"commands.useAccessGroups": "Use Access Groups",
|
||||
"ui.seamColor": "Accent Color",
|
||||
"browser.controlUrl": "Browser Control URL",
|
||||
"browser.remoteCdpTimeoutMs": "Remote CDP Timeout (ms)",
|
||||
"browser.remoteCdpHandshakeTimeoutMs": "Remote CDP Handshake Timeout (ms)",
|
||||
"session.dmScope": "DM Session Scope",
|
||||
"session.agentToAgent.maxPingPongTurns": "Agent-to-Agent Ping-Pong Turns",
|
||||
"messages.ackReaction": "Ack Reaction Emoji",
|
||||
|
||||
@@ -21,6 +21,10 @@ export type BrowserConfig = {
|
||||
controlToken?: string;
|
||||
/** Base URL of the CDP endpoint. Default: controlUrl with port + 1. */
|
||||
cdpUrl?: string;
|
||||
/** Remote CDP HTTP timeout (ms). Default: 1500. */
|
||||
remoteCdpTimeoutMs?: number;
|
||||
/** Remote CDP WebSocket handshake timeout (ms). Default: max(remoteCdpTimeoutMs * 2, 2000). */
|
||||
remoteCdpHandshakeTimeoutMs?: number;
|
||||
/** Accent color for the clawd browser profile (hex). Default: #FF4500 */
|
||||
color?: string;
|
||||
/** Override the browser executable path (all platforms). */
|
||||
|
||||
@@ -67,6 +67,8 @@ export const ClawdbotSchema = z
|
||||
controlUrl: z.string().optional(),
|
||||
controlToken: z.string().optional(),
|
||||
cdpUrl: z.string().optional(),
|
||||
remoteCdpTimeoutMs: z.number().int().nonnegative().optional(),
|
||||
remoteCdpHandshakeTimeoutMs: z.number().int().nonnegative().optional(),
|
||||
color: z.string().optional(),
|
||||
executablePath: z.string().optional(),
|
||||
headless: z.boolean().optional(),
|
||||
|
||||
Reference in New Issue
Block a user