mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:50:45 +00:00
fix(google-meet): surface chrome node readiness in setup
This commit is contained in:
@@ -451,6 +451,35 @@ describe("google-meet plugin", () => {
|
||||
expect(result.details.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("fails setup status when the configured Chrome node is not connected", async () => {
|
||||
const { tools } = setup(
|
||||
{
|
||||
defaultTransport: "chrome-node",
|
||||
chromeNode: { node: "parallels-macos" },
|
||||
},
|
||||
{ nodesListResult: { nodes: [] } },
|
||||
);
|
||||
const tool = tools[0] as {
|
||||
execute: (
|
||||
id: string,
|
||||
params: unknown,
|
||||
) => Promise<{ details: { ok?: boolean; checks?: unknown[] } }>;
|
||||
};
|
||||
|
||||
const result = await tool.execute("id", { action: "setup_status" });
|
||||
|
||||
expect(result.details.ok).toBe(false);
|
||||
expect(result.details.checks).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "chrome-node-connected",
|
||||
ok: false,
|
||||
message: expect.stringContaining("No connected Google Meet-capable node"),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("reports Twilio delegation readiness when voice-call is enabled", async () => {
|
||||
vi.stubEnv("TWILIO_ACCOUNT_SID", "AC123");
|
||||
vi.stubEnv("TWILIO_AUTH_TOKEN", "secret");
|
||||
@@ -547,7 +576,7 @@ describe("google-meet plugin", () => {
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
ensureRuntime: async () =>
|
||||
({
|
||||
setupStatus: () => ({
|
||||
setupStatus: async () => ({
|
||||
ok: true,
|
||||
checks: [
|
||||
{
|
||||
@@ -557,7 +586,7 @@ describe("google-meet plugin", () => {
|
||||
},
|
||||
],
|
||||
}),
|
||||
}) as GoogleMeetRuntime,
|
||||
}) as unknown as GoogleMeetRuntime,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -580,11 +609,11 @@ describe("google-meet plugin", () => {
|
||||
config: resolveGoogleMeetConfig({}),
|
||||
ensureRuntime: async () =>
|
||||
({
|
||||
setupStatus: () => ({
|
||||
setupStatus: async () => ({
|
||||
ok: false,
|
||||
checks: [{ id: "twilio-voice-call-plugin", ok: false, message: "missing" }],
|
||||
}),
|
||||
}) as GoogleMeetRuntime,
|
||||
}) as unknown as GoogleMeetRuntime,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
@@ -313,7 +313,7 @@ export default definePluginEntry({
|
||||
async ({ respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const rt = await ensureRuntime();
|
||||
respond(true, rt.setupStatus());
|
||||
respond(true, await rt.setupStatus());
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
@@ -430,7 +430,7 @@ export default definePluginEntry({
|
||||
}
|
||||
case "setup_status": {
|
||||
const rt = await ensureRuntime();
|
||||
return json(rt.setupStatus());
|
||||
return json(await rt.setupStatus());
|
||||
}
|
||||
case "resolve_space": {
|
||||
const { token: _token, ...result } = await resolveSpaceFromParams(config, raw);
|
||||
|
||||
@@ -95,7 +95,7 @@ function parseOptionalNumber(value: string | undefined): number | undefined {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function writeSetupStatus(status: ReturnType<GoogleMeetRuntime["setupStatus"]>): void {
|
||||
function writeSetupStatus(status: Awaited<ReturnType<GoogleMeetRuntime["setupStatus"]>>): void {
|
||||
writeStdoutLine("Google Meet setup: %s", status.ok ? "OK" : "needs attention");
|
||||
for (const check of status.checks) {
|
||||
writeStdoutLine("[%s] %s: %s", check.ok ? "ok" : "fail", check.id, check.message);
|
||||
@@ -485,7 +485,7 @@ export function registerGoogleMeetCli(params: {
|
||||
.option("--json", "Print JSON output", false)
|
||||
.action(async (options: SetupOptions) => {
|
||||
const rt = await params.ensureRuntime();
|
||||
const status = rt.setupStatus();
|
||||
const status = await rt.setupStatus();
|
||||
if (options.json) {
|
||||
writeStdoutJson(status);
|
||||
return;
|
||||
|
||||
@@ -4,7 +4,8 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { PluginRuntime, RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
||||
import { getGoogleMeetSetupStatus } from "./setup.js";
|
||||
import { addGoogleMeetSetupCheck, getGoogleMeetSetupStatus } from "./setup.js";
|
||||
import { resolveChromeNodeInfo } from "./transports/chrome-browser-proxy.js";
|
||||
import { createMeetWithBrowserProxyOnNode } from "./transports/chrome-create.js";
|
||||
import { launchChromeMeet, launchChromeMeetOnNode } from "./transports/chrome.js";
|
||||
import { buildMeetDtmfSequence, normalizeDialInNumber } from "./transports/twilio.js";
|
||||
@@ -81,8 +82,34 @@ export class GoogleMeetRuntime {
|
||||
return session ? { found: true, session } : { found: false };
|
||||
}
|
||||
|
||||
setupStatus() {
|
||||
return getGoogleMeetSetupStatus(this.params.config, { fullConfig: this.params.fullConfig });
|
||||
async setupStatus() {
|
||||
let status = getGoogleMeetSetupStatus(this.params.config, {
|
||||
fullConfig: this.params.fullConfig,
|
||||
});
|
||||
if (
|
||||
this.params.config.defaultTransport === "chrome-node" ||
|
||||
Boolean(this.params.config.chromeNode.node)
|
||||
) {
|
||||
try {
|
||||
const node = await resolveChromeNodeInfo({
|
||||
runtime: this.params.runtime,
|
||||
requestedNode: this.params.config.chromeNode.node,
|
||||
});
|
||||
const label = node.displayName ?? node.remoteIp ?? node.nodeId ?? "connected node";
|
||||
status = addGoogleMeetSetupCheck(status, {
|
||||
id: "chrome-node-connected",
|
||||
ok: true,
|
||||
message: `Connected Google Meet node ready: ${label}`,
|
||||
});
|
||||
} catch (error) {
|
||||
status = addGoogleMeetSetupCheck(status, {
|
||||
id: "chrome-node-connected",
|
||||
ok: false,
|
||||
message: formatErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
async createViaBrowser() {
|
||||
|
||||
@@ -3,12 +3,17 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { GoogleMeetConfig } from "./config.js";
|
||||
|
||||
type SetupCheck = {
|
||||
export type SetupCheck = {
|
||||
id: string;
|
||||
ok: boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type GoogleMeetSetupStatus = {
|
||||
ok: boolean;
|
||||
checks: SetupCheck[];
|
||||
};
|
||||
|
||||
function resolveUserPath(input: string): string {
|
||||
if (input === "~") {
|
||||
return os.homedir();
|
||||
@@ -177,6 +182,17 @@ export function getGoogleMeetSetupStatus(
|
||||
};
|
||||
}
|
||||
|
||||
export function addGoogleMeetSetupCheck(
|
||||
status: GoogleMeetSetupStatus,
|
||||
check: SetupCheck,
|
||||
): GoogleMeetSetupStatus {
|
||||
const checks = [...status.checks, check];
|
||||
return {
|
||||
ok: checks.every((item) => item.ok),
|
||||
checks,
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
|
||||
@@ -10,14 +10,16 @@ export type BrowserTab = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
function isGoogleMeetNode(node: {
|
||||
export type GoogleMeetNodeInfo = {
|
||||
caps?: string[];
|
||||
commands?: string[];
|
||||
connected?: boolean;
|
||||
nodeId?: string;
|
||||
displayName?: string;
|
||||
remoteIp?: string;
|
||||
}) {
|
||||
};
|
||||
|
||||
function isGoogleMeetNode(node: GoogleMeetNodeInfo) {
|
||||
const commands = Array.isArray(node.commands) ? node.commands : [];
|
||||
const caps = Array.isArray(node.caps) ? node.caps : [];
|
||||
return (
|
||||
@@ -27,10 +29,10 @@ function isGoogleMeetNode(node: {
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveChromeNode(params: {
|
||||
export async function resolveChromeNodeInfo(params: {
|
||||
runtime: PluginRuntime;
|
||||
requestedNode?: string;
|
||||
}): Promise<string> {
|
||||
}): Promise<GoogleMeetNodeInfo> {
|
||||
const list = await params.runtime.nodes.list({ connected: true });
|
||||
const nodes = list.nodes.filter(isGoogleMeetNode);
|
||||
if (nodes.length === 0) {
|
||||
@@ -44,18 +46,29 @@ export async function resolveChromeNode(params: {
|
||||
[node.nodeId, node.displayName, node.remoteIp].some((value) => value === requested),
|
||||
);
|
||||
if (matches.length === 1) {
|
||||
return matches[0].nodeId;
|
||||
return matches[0];
|
||||
}
|
||||
throw new Error(`Google Meet node not found or ambiguous: ${requested}`);
|
||||
}
|
||||
if (nodes.length === 1) {
|
||||
return nodes[0].nodeId;
|
||||
return nodes[0];
|
||||
}
|
||||
throw new Error(
|
||||
"Multiple Google Meet-capable nodes connected. Set plugins.entries.google-meet.config.chromeNode.node.",
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveChromeNode(params: {
|
||||
runtime: PluginRuntime;
|
||||
requestedNode?: string;
|
||||
}): Promise<string> {
|
||||
const node = await resolveChromeNodeInfo(params);
|
||||
if (!node.nodeId) {
|
||||
throw new Error("Google Meet node did not include a node id.");
|
||||
}
|
||||
return node.nodeId;
|
||||
}
|
||||
|
||||
function unwrapNodeInvokePayload(raw: unknown): unknown {
|
||||
const record = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
||||
if (typeof record.payloadJSON === "string" && record.payloadJSON.trim()) {
|
||||
|
||||
Reference in New Issue
Block a user