From 5342effebcf7588ea0df5c4b402459cc189ef740 Mon Sep 17 00:00:00 2001 From: Chen Chia Yang Date: Fri, 3 Jul 2026 23:00:34 +0800 Subject: [PATCH] fix(google-meet): force English Meet UI via hl=en so automation works on any locale (#89671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 880a41b6f0debed9361359be2c98d18b7a18c04c. - Required merge gates passed before the squash merge. Prepared head SHA: 880a41b6f0debed9361359be2c98d18b7a18c04c Review: https://github.com/openclaw/openclaw/pull/89671#issuecomment-4608725456 Co-authored-by: Chen Chia Yang Approved-by: hxy91819 --- extensions/google-meet/index.create.test.ts | 6 ++++-- extensions/google-meet/index.test.ts | 6 +++--- .../transports/chrome-browser-proxy.test.ts | 19 ++++++++++++++++++- .../src/transports/chrome-browser-proxy.ts | 13 +++++++++++++ .../src/transports/chrome-create.ts | 7 +++++-- .../google-meet/src/transports/chrome.ts | 5 +++-- 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/extensions/google-meet/index.create.test.ts b/extensions/google-meet/index.create.test.ts index 1502710f6fc0..e5792652dee0 100644 --- a/extensions/google-meet/index.create.test.ts +++ b/extensions/google-meet/index.create.test.ts @@ -304,7 +304,7 @@ describe("google-meet create flow", () => { return false; } const body = proxy.body as Record; - 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, }, diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 10df44180968..468088c30a94 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -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 }); diff --git a/extensions/google-meet/src/transports/chrome-browser-proxy.test.ts b/extensions/google-meet/src/transports/chrome-browser-proxy.test.ts index 9a79b5a83e20..2ffc19bdb38b 100644 --- a/extensions/google-meet/src/transports/chrome-browser-proxy.test.ts +++ b/extensions/google-meet/src/transports/chrome-browser-proxy.test.ts @@ -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 () => { diff --git a/extensions/google-meet/src/transports/chrome-browser-proxy.ts b/extensions/google-meet/src/transports/chrome-browser-proxy.ts index b8eb1d7ee05a..6a8d01bc4ee8 100644 --- a/extensions/google-meet/src/transports/chrome-browser-proxy.ts +++ b/extensions/google-meet/src/transports/chrome-browser-proxy.ts @@ -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; diff --git a/extensions/google-meet/src/transports/chrome-create.ts b/extensions/google-meet/src/transports/chrome-create.ts index 0805f6c33483..934958d539bd 100644 --- a/extensions/google-meet/src/transports/chrome-create.ts +++ b/extensions/google-meet/src/transports/chrome-create.ts @@ -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, }), ); diff --git a/extensions/google-meet/src/transports/chrome.ts b/extensions/google-meet/src/transports/chrome.ts index 6faeb6e39c2b..24a1968927e4 100644 --- a/extensions/google-meet/src/transports/chrome.ts +++ b/extensions/google-meet/src/transports/chrome.ts @@ -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, }), );