fix(google-meet): force English Meet UI via hl=en so automation works on any locale (#89671)

Summary:
- The branch adds `forceMeetEnglishUi()` for Google Meet URLs, applies it to join/create browser opens, canonicalizes browser-created meeting links, and updates focused Google Meet tests.
- PR surface: Source +18, Tests +19. Total +37 across 6 files.
- Reproducibility: yes. Current main opens raw Meet URLs while the browser automation matches English-only lab ... R body provides after-fix CDP output showing non-English pages render expected English labels with `hl=en`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: chore(tooling): add unused agent-cache-store files to knip optional a…
- PR branch already contained follow-up commit before automerge: fix(google-meet): repair non-Latin mic regex matching boundary
- PR branch already contained follow-up commit before automerge: revert(tooling): preserve current main deadcode allowlist guard
- PR branch already contained follow-up commit before automerge: revert(tooling): perfectly match origin/main deadcode list
- PR branch already contained follow-up commit before automerge: revert: match origin/main deadcode list exactly

Validation:
- ClawSweeper review passed for head 880a41b6f0.
- Required merge gates passed before the squash merge.

Prepared head SHA: 880a41b6f0
Review: https://github.com/openclaw/openclaw/pull/89671#issuecomment-4608725456

Co-authored-by: Chen Chia Yang <unayung@gmail.com>
Approved-by: hxy91819
This commit is contained in:
Chen Chia Yang
2026-07-03 23:00:34 +08:00
committed by GitHub
parent e272da5c55
commit 5342effebc
6 changed files with 46 additions and 10 deletions
+4 -2
View File
@@ -304,7 +304,7 @@ describe("google-meet create flow", () => {
return false;
}
const body = proxy.body as Record<string, unknown>;
return proxy.path === "/tabs/open" && body.url === "https://meet.google.com/new";
return proxy.path === "/tabs/open" && body.url === "https://meet.google.com/new?hl=en";
});
});
@@ -426,7 +426,9 @@ describe("google-meet create flow", () => {
payload: {
result: {
targetId:
proxy.body?.url === "https://meet.google.com/new" ? "create-tab" : "join-tab",
proxy.body?.url === "https://meet.google.com/new?hl=en"
? "create-tab"
: "join-tab",
title: "Meet",
url: proxy.body?.url,
},
+3 -3
View File
@@ -2294,7 +2294,7 @@ describe("google-meet plugin", () => {
method: "POST",
path: "/tabs/open",
timeoutMs: 30000,
body: { url: "https://meet.google.com/abc-defg-hij" },
body: { url: "https://meet.google.com/abc-defg-hij?hl=en" },
});
expect(openCall[3]).toEqual({ progress: false });
expect(
@@ -3105,7 +3105,7 @@ describe("google-meet plugin", () => {
method: "POST",
path: "/tabs/open",
timeoutMs: 30000,
body: { url: "https://meet.google.com/abc-defg-hij" },
body: { url: "https://meet.google.com/abc-defg-hij?hl=en" },
});
const startCall = nodesInvoke.mock.calls.find(([rawCall]) => {
const call = requireRecord(rawCall, "node invoke");
@@ -4105,7 +4105,7 @@ describe("google-meet plugin", () => {
const request = requireRecord(params, "local browser open request");
expect(request.method).toBe("POST");
expect(request.path).toBe("/tabs/open");
expect(request.body).toStrictEqual({ url: "https://meet.google.com/abc-defg-hij" });
expect(request.body).toStrictEqual({ url: "https://meet.google.com/abc-defg-hij?hl=en" });
expect(extra).toStrictEqual({ progress: false });
} finally {
Object.defineProperty(process, "platform", { value: originalPlatform });
@@ -2,7 +2,24 @@
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
import { describe, expect, it, vi } from "vitest";
import { callBrowserProxyOnNode } from "./chrome-browser-proxy.js";
import { callBrowserProxyOnNode, forceMeetEnglishUi } from "./chrome-browser-proxy.js";
describe("forceMeetEnglishUi", () => {
it("pins hl=en on Meet URLs", () => {
expect(forceMeetEnglishUi("https://meet.google.com/abc-defg-hij")).toBe(
"https://meet.google.com/abc-defg-hij?hl=en",
);
expect(forceMeetEnglishUi("https://meet.google.com/new")).toBe(
"https://meet.google.com/new?hl=en",
);
});
it("overrides an existing hl and keeps other params", () => {
expect(forceMeetEnglishUi("https://meet.google.com/abc-defg-hij?hl=zh-TW&authuser=1")).toBe(
"https://meet.google.com/abc-defg-hij?hl=en&authuser=1",
);
});
});
describe("Google Meet Chrome browser proxy", () => {
it("reports malformed node proxy payloadJSON with an owned error", async () => {
@@ -12,6 +12,19 @@ export type BrowserTab = {
url?: string;
};
// Meet automation scripts match English UI labels ("Join now", "Turn off microphone").
// hl=en pins the Meet page language regardless of account/browser locale; without it,
// non-English profiles render localized labels and every DOM matcher goes blind.
export function forceMeetEnglishUi(url: string): string {
try {
const parsed = new URL(url);
parsed.searchParams.set("hl", "en");
return parsed.toString();
} catch {
return url;
}
}
export function normalizeMeetUrlForReuse(url: string | undefined): string | undefined {
if (!url) {
return undefined;
@@ -5,6 +5,7 @@ import type { GoogleMeetConfig } from "../config.js";
import {
asBrowserTabs,
callBrowserProxyOnNode,
forceMeetEnglishUi,
readBrowserTab,
resolveChromeNode,
type BrowserTab,
@@ -199,7 +200,9 @@ export const CREATE_MEET_FROM_BROWSER_SCRIPT = `async () => {
}
const href = current();
if (meetUrlPattern.test(href)) {
return { meetingUri: href, browserUrl: href, browserTitle: document.title, notes };
// The /new redirect keeps the hl=en param we open with; strip query/hash so the
// meeting link handed to users stays canonical instead of forcing English on them.
return { meetingUri: href.split(/[?#]/)[0], browserUrl: href, browserTitle: document.title, notes };
}
const pageText = text(document.body);
if (clickButton(/\\buse microphone\\b/i, "Accepted Meet microphone prompt with browser automation.")) {
@@ -288,7 +291,7 @@ export async function createMeetWithBrowserProxyOnNode(params: {
nodeId,
method: "POST",
path: "/tabs/open",
body: { url: GOOGLE_MEET_NEW_URL },
body: { url: forceMeetEnglishUi(GOOGLE_MEET_NEW_URL) },
timeoutMs: stepTimeoutMs,
}),
);
@@ -23,6 +23,7 @@ import {
import {
asBrowserTabs,
callBrowserProxyOnNode,
forceMeetEnglishUi,
isSameMeetUrlForReuse,
normalizeMeetUrlForReuse,
readBrowserTab,
@@ -433,7 +434,7 @@ function meetStatusScript(params: {
notes.push("Muted Meet microphone for observe-only mode.");
}
const join = !readOnly && ${JSON.stringify(params.autoJoin)}
? findButton(/join now|ask to join/i)
? findButton(/join now|ask to join|join here too/i)
: null;
if (join) join.click();
const microphoneChoice = findButton(/\\buse microphone\\b/i);
@@ -690,7 +691,7 @@ async function openMeetWithBrowserRequest(params: {
await params.callBrowser({
method: "POST",
path: "/tabs/open",
body: { url: params.url },
body: { url: forceMeetEnglishUi(params.url) },
timeoutMs,
}),
);