mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:30:45 +00:00
fix(google-meet): guard linux chrome realtime tool actions
This commit is contained in:
@@ -62,6 +62,7 @@ function setup(
|
||||
unknown
|
||||
>,
|
||||
);
|
||||
googleMeetPluginTesting.setPlatformForTests(() => options?.registerPlatform ?? "darwin");
|
||||
return harness;
|
||||
}
|
||||
|
||||
@@ -106,6 +107,7 @@ describe("google-meet create flow", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
googleMeetPluginTesting.setCallGatewayFromCliForTests();
|
||||
googleMeetPluginTesting.setPlatformForTests();
|
||||
});
|
||||
|
||||
it("CLI create can configure API-created space access", async () => {
|
||||
|
||||
@@ -86,6 +86,7 @@ function setup(
|
||||
unknown
|
||||
>,
|
||||
);
|
||||
googleMeetPluginTesting.setPlatformForTests(() => options?.registerPlatform ?? "darwin");
|
||||
return harness;
|
||||
}
|
||||
|
||||
@@ -303,6 +304,7 @@ describe("google-meet plugin", () => {
|
||||
vi.unstubAllGlobals();
|
||||
chromeTransportTesting.setDepsForTest(null);
|
||||
googleMeetPluginTesting.setCallGatewayFromCliForTests();
|
||||
googleMeetPluginTesting.setPlatformForTests();
|
||||
});
|
||||
|
||||
it("defaults to chrome realtime with safe read-only tools", () => {
|
||||
@@ -507,6 +509,42 @@ describe("google-meet plugin", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the agent tool visible on non-macOS hosts but blocks local Chrome realtime joins", async () => {
|
||||
const { cliRegistrations, methods, tools } = setup(undefined, { registerPlatform: "linux" });
|
||||
const tool = tools[0] as {
|
||||
execute: (id: string, params: unknown) => Promise<{ isError?: boolean; content: unknown }>;
|
||||
};
|
||||
|
||||
expect(tools).toHaveLength(1);
|
||||
expect(cliRegistrations).toHaveLength(1);
|
||||
expect(methods.has("googlemeet.setup")).toBe(true);
|
||||
expect(
|
||||
googleMeetPluginTesting.isGoogleMeetAgentToolActionUnsupportedOnHost({
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
raw: { action: "join" },
|
||||
platform: "linux",
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
const blocked = await tool.execute("id", { action: "join" });
|
||||
expect(JSON.stringify(blocked)).toContain("local Chrome realtime audio is macOS-only");
|
||||
|
||||
expect(
|
||||
googleMeetPluginTesting.isGoogleMeetAgentToolActionUnsupportedOnHost({
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
raw: { action: "join", mode: "transcribe" },
|
||||
platform: "linux",
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
googleMeetPluginTesting.isGoogleMeetAgentToolActionUnsupportedOnHost({
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
raw: { action: "join", transport: "chrome-node" },
|
||||
platform: "linux",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns structured gateway errors for missing session ids", async () => {
|
||||
const { methods } = setup();
|
||||
for (const method of ["googlemeet.leave", "googlemeet.speak"]) {
|
||||
|
||||
@@ -345,12 +345,17 @@ function shouldJoinCreatedMeet(raw: Record<string, unknown>): boolean {
|
||||
|
||||
const googleMeetToolDeps = {
|
||||
callGatewayFromCli,
|
||||
platform: () => process.platform,
|
||||
};
|
||||
|
||||
export const __testing = {
|
||||
setCallGatewayFromCliForTests(next?: typeof callGatewayFromCli): void {
|
||||
googleMeetToolDeps.callGatewayFromCli = next ?? callGatewayFromCli;
|
||||
},
|
||||
setPlatformForTests(next?: () => NodeJS.Platform): void {
|
||||
googleMeetToolDeps.platform = next ?? (() => process.platform);
|
||||
},
|
||||
isGoogleMeetAgentToolActionUnsupportedOnHost,
|
||||
};
|
||||
|
||||
type GoogleMeetGatewayToolAction =
|
||||
@@ -382,6 +387,43 @@ function googleMeetGatewayMethodForToolAction(action: GoogleMeetGatewayToolActio
|
||||
}
|
||||
}
|
||||
|
||||
function isGoogleMeetAgentToolActionUnsupportedOnHost(params: {
|
||||
config: GoogleMeetConfig;
|
||||
raw: Record<string, unknown>;
|
||||
platform?: NodeJS.Platform;
|
||||
}): boolean {
|
||||
const platform = params.platform ?? googleMeetToolDeps.platform();
|
||||
if (platform === "darwin") {
|
||||
return false;
|
||||
}
|
||||
const action = params.raw.action;
|
||||
if (
|
||||
action !== "join" &&
|
||||
action !== "test_speech" &&
|
||||
!(action === "create" && shouldJoinCreatedMeet(params.raw))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const transport = normalizeTransport(params.raw.transport) ?? params.config.defaultTransport;
|
||||
const mode =
|
||||
action === "test_speech"
|
||||
? "realtime"
|
||||
: (normalizeMode(params.raw.mode) ?? params.config.defaultMode);
|
||||
return transport === "chrome" && mode === "realtime";
|
||||
}
|
||||
|
||||
function assertGoogleMeetAgentToolActionSupported(params: {
|
||||
config: GoogleMeetConfig;
|
||||
raw: Record<string, unknown>;
|
||||
}): void {
|
||||
if (!isGoogleMeetAgentToolActionUnsupportedOnHost(params)) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
"Google Meet local Chrome realtime audio is macOS-only. On this host, use mode: transcribe, transport: twilio, or transport: chrome-node backed by a macOS node.",
|
||||
);
|
||||
}
|
||||
|
||||
function resolveGoogleMeetToolGatewayTimeoutMs(config: GoogleMeetConfig): number {
|
||||
return Math.max(
|
||||
60_000,
|
||||
@@ -944,11 +986,12 @@ export default definePluginEntry({
|
||||
name: "google_meet",
|
||||
label: "Google Meet",
|
||||
description:
|
||||
"Join and track Google Meet sessions through Chrome or Twilio. Call setup_status before join/create/test_listen/test_speech; if it reports a Chrome node offline or local audio missing, surface that blocker instead of retrying or switching transports. Offline nodes are diagnostics only, not usable candidates. If a Meet tab is already open after a timeout, call recover_current_tab before retrying join to report login, permission, or admission blockers without opening another tab.",
|
||||
"Join and track Google Meet sessions through Chrome or Twilio. Call setup_status before join/create/test_listen/test_speech; if it reports a Chrome node offline or local audio missing, surface that blocker instead of retrying or switching transports. Offline nodes are diagnostics only, not usable candidates. If local Chrome realtime audio is unsupported on this OS, use mode=transcribe, transport=twilio, or a macOS chrome-node for realtime Chrome. If a Meet tab is already open after a timeout, call recover_current_tab before retrying join to report login, permission, or admission blockers without opening another tab.",
|
||||
parameters: GoogleMeetToolSchema,
|
||||
async execute(_toolCallId, params) {
|
||||
const raw = asParamRecord(params);
|
||||
try {
|
||||
assertGoogleMeetAgentToolActionSupported({ config, raw });
|
||||
switch (raw.action) {
|
||||
case "join": {
|
||||
return json(await callGoogleMeetGatewayFromTool({ config, action: "join", raw }));
|
||||
|
||||
@@ -60,6 +60,7 @@ export function setupGoogleMeetPlugin(
|
||||
argv: string[],
|
||||
options?: { timeoutMs?: number },
|
||||
) => Promise<CommandResult>;
|
||||
registerPlatform?: NodeJS.Platform;
|
||||
} = {},
|
||||
) {
|
||||
const methods = new Map<string, unknown>();
|
||||
@@ -157,7 +158,16 @@ export function setupGoogleMeetPlugin(
|
||||
registerCli: (_registrar: unknown, opts: unknown) => cliRegistrations.push(opts),
|
||||
registerNodeHostCommand: (command: unknown) => nodeHostCommands.push(command),
|
||||
});
|
||||
plugin.register(api);
|
||||
const originalPlatform = process.platform;
|
||||
Object.defineProperty(process, "platform", {
|
||||
configurable: true,
|
||||
value: options.registerPlatform ?? "darwin",
|
||||
});
|
||||
try {
|
||||
plugin.register(api);
|
||||
} finally {
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: originalPlatform });
|
||||
}
|
||||
return {
|
||||
cliRegistrations,
|
||||
methods,
|
||||
|
||||
Reference in New Issue
Block a user