mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 08:52:12 +00:00
fix(browser): avoid cold mac chrome version timeouts (#85460)
This commit is contained in:
@@ -159,6 +159,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/agents: default new omitted-account bindings to all accounts when the channel has multiple configured accounts, and clarify account-scope docs. (#49769) Thanks @Gcaufy.
|
||||
- Codex app-server: let authorized `/codex` control commands such as `/codex detach` escape plugin-owned conversation bindings while keeping unknown or unauthorized slash text routed to the bound plugin. Fixes #85157. (#85188) Thanks @TurboTheTurtle.
|
||||
- Auto-reply/models: keep `/models` browse replies fast by sharing the bounded read-only catalog path with Gateway model listing. (#84735) Thanks @safrano9999.
|
||||
- Browser/Doctor: read macOS Chrome app bundle versions from `Info.plist` before spawning Chrome and extend the fallback version probe timeout, avoiding false cold-cache warnings from Gatekeeper latency. Fixes #85418. Thanks @davidcittadini.
|
||||
- Codex app-server: disable native Code Mode when the effective exec host is `node` and keep OpenClaw `exec`/`process` available, so `/exec host=node` routes shell commands through the selected node instead of the gateway. Fixes #85012. (#85090) Thanks @sahilsatralkar.
|
||||
- Agents: bound embedded auto-compaction session write-lock watchdogs to the compaction timeout instead of the full run timeout, so stuck compaction cannot hold the live session lock for the whole run window. (#84949) Thanks @luoyanglang.
|
||||
- Gateway/agents: return phase-aware `agent.wait` timeout attribution and only cool auth profiles on provider-started timeouts. Refs #65504. Thanks @100yenadmin.
|
||||
|
||||
@@ -15,6 +15,8 @@ export type BrowserExecutable = {
|
||||
|
||||
const CHROME_VERSION_RE = /\b(\d+)(?:\.\d+){1,3}\b/g;
|
||||
const PLAYWRIGHT_BROWSERS_PATH_ENV = "PLAYWRIGHT_BROWSERS_PATH";
|
||||
const BROWSER_VERSION_TIMEOUT_MS = 6000;
|
||||
const MAC_PLISTBUDDY_TIMEOUT_MS = 800;
|
||||
|
||||
const CHROMIUM_BUNDLE_IDS = new Set([
|
||||
"com.google.Chrome",
|
||||
@@ -732,13 +734,42 @@ export function resolveGoogleChromeExecutableForPlatform(
|
||||
}
|
||||
|
||||
export function readBrowserVersion(executablePath: string): string | null {
|
||||
const output = execText(executablePath, ["--version"], 2000);
|
||||
if (process.platform === "darwin") {
|
||||
const bundleVersion = readMacBundleBrowserVersion(executablePath);
|
||||
if (bundleVersion) {
|
||||
return bundleVersion;
|
||||
}
|
||||
}
|
||||
|
||||
const output = execText(executablePath, ["--version"], BROWSER_VERSION_TIMEOUT_MS);
|
||||
if (!output) {
|
||||
return null;
|
||||
}
|
||||
return output.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function readMacBundleBrowserVersion(executablePath: string): string | null {
|
||||
const appBundlePath = resolveMacAppBundlePath(executablePath);
|
||||
if (!appBundlePath) {
|
||||
return null;
|
||||
}
|
||||
const plistPath = path.join(appBundlePath, "Contents", "Info.plist");
|
||||
return execText(
|
||||
"/usr/libexec/PlistBuddy",
|
||||
["-c", "Print :CFBundleShortVersionString", plistPath],
|
||||
MAC_PLISTBUDDY_TIMEOUT_MS,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveMacAppBundlePath(executablePath: string): string | null {
|
||||
const parts = path.normalize(executablePath).split(path.sep);
|
||||
const appIndex = parts.findIndex((part) => part.endsWith(".app"));
|
||||
if (appIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
return parts.slice(0, appIndex + 1).join(path.sep) || path.sep;
|
||||
}
|
||||
|
||||
export function parseBrowserMajorVersion(rawVersion: string | null | undefined): number | null {
|
||||
const matches = [...(rawVersion ?? "").matchAll(CHROME_VERSION_RE)];
|
||||
const match = matches.at(-1);
|
||||
|
||||
86
extensions/browser/src/browser/chrome.version.test.ts
Normal file
86
extensions/browser/src/browser/chrome.version.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const execFileSyncMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("node:child_process", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
|
||||
return {
|
||||
...actual,
|
||||
execFileSync: (...args: unknown[]) => execFileSyncMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
import { readBrowserVersion } from "./chrome.executables.js";
|
||||
|
||||
function stubPlatform(platform: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
value: platform,
|
||||
});
|
||||
}
|
||||
|
||||
describe("readBrowserVersion", () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
afterEach(() => {
|
||||
stubPlatform(originalPlatform);
|
||||
execFileSyncMock.mockReset();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("reads macOS app bundle versions from Info.plist before spawning Chrome", () => {
|
||||
stubPlatform("darwin");
|
||||
execFileSyncMock.mockReturnValue("148.0.7778.179\n");
|
||||
|
||||
const version = readBrowserVersion(
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
);
|
||||
|
||||
expect(version).toBe("148.0.7778.179");
|
||||
expect(execFileSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(execFileSyncMock).toHaveBeenCalledWith(
|
||||
"/usr/libexec/PlistBuddy",
|
||||
[
|
||||
"-c",
|
||||
"Print :CFBundleShortVersionString",
|
||||
"/Applications/Google Chrome.app/Contents/Info.plist",
|
||||
],
|
||||
expect.objectContaining({ timeout: 800 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to a slower --version probe when macOS bundle metadata is unavailable", () => {
|
||||
stubPlatform("darwin");
|
||||
execFileSyncMock
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error("plist unavailable");
|
||||
})
|
||||
.mockReturnValueOnce("Google Chrome 148.0.7778.179\n");
|
||||
|
||||
const version = readBrowserVersion(
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
);
|
||||
|
||||
expect(version).toBe("Google Chrome 148.0.7778.179");
|
||||
expect(execFileSyncMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
["--version"],
|
||||
expect.objectContaining({ timeout: 6000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the slower --version probe for non-bundle paths", () => {
|
||||
stubPlatform("darwin");
|
||||
execFileSyncMock.mockReturnValue("Chromium 148.0.7778.179\n");
|
||||
|
||||
const version = readBrowserVersion("/opt/chromium/chrome");
|
||||
|
||||
expect(version).toBe("Chromium 148.0.7778.179");
|
||||
expect(execFileSyncMock).toHaveBeenCalledWith(
|
||||
"/opt/chromium/chrome",
|
||||
["--version"],
|
||||
expect.objectContaining({ timeout: 6000 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user