import { EventEmitter } from
"node:events" ;
import { PassThrough, Writable } from
"node:stream" ;
import type { RealtimeVoiceProviderPlugin } from
"openclaw/plugin-sdk/realtime-voice" ;
import { afterEach, beforeEach, describe, expect, it, vi } from
"vitest" ;
import plugin from
"./index.js" ;
import { resolveGoogleMeetConfig, resolveGoogleMeetConfigWithEnv } from
"./src/config.js" ;
import {
buildGoogleMeetPreflightReport,
createGoogleMeetSpace,
fetchGoogleMeetArtifacts,
fetchGoogleMeetAttendance,
fetchLatestGoogleMeetConferenceRecord,
fetchGoogleMeetSpace,
normalizeGoogleMeetSpaceName,
} from
"./src/meet.js" ;
import { startNodeRealtimeAudioBridge } from
"./src/realtime-node.js" ;
import { startCommandRealtimeAudioBridge } from
"./src/realtime.js" ;
import { normalizeMeetUrl } from
"./src/runtime.js" ;
import { noopLogger, setupGoogleMeetPlugin } from
"./src/test-support/plugin-harness.js" ;
import { buildMeetDtmfSequence, normalizeDialInNumber } from
"./src/transports/twilio.js" ;
const voiceCallMocks = vi.hoisted(() => ({
joinMeetViaVoiceCallGateway: vi.fn(async () => ({ callId:
"call-1" , dtmfSent:
true })),
endMeetVoiceCallGatewayCall: vi.fn(async () => {}),
}));
const fetchGuardMocks = vi.hoisted(() => ({
fetchWithSsrFGuard: vi.fn(
async (params: {
url: string;
init?: RequestInit;
}): Promise<{
response: Response;
release: () => Promise<
void >;
}> => ({
response: await fetch(params.url, params.init),
release: vi.fn(async () => {}),
}),
),
}));
vi.mock(
"openclaw/plugin-sdk/ssrf-runtime" , () => ({
fetchWithSsrFGuard: fetchGuardMocks.fetchWithSsrFGuard,
}));
vi.mock(
"./src/voice-call-gateway.js" , () => ({
joinMeetViaVoiceCallGateway: voiceCallMocks.joinMeetViaVoiceCallGateway,
endMeetVoiceCallGatewayCall: voiceCallMocks.endMeetVoiceCallGatewayCall,
}));
function setup(
config?: Parameters<
typeof setupGoogleMeetPlugin>[
1 ],
options?: Parameters<
typeof setupGoogleMeetPlugin>[
2 ],
) {
return setupGoogleMeetPlugin(plugin, config, options);
}
function jsonResponse(value: unknown): Response {
return new Response(JSON.stringify(value), {
status:
200 ,
headers: {
"Content-Type" :
"application/json" },
});
}
function requestUrl(input: RequestInfo | URL): URL {
if (
typeof input ===
"string" ) {
return new URL(input);
}
if (input
instanceof URL) {
return input;
}
return new URL(input.url);
}
function stubMeetArtifactsApi() {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = requestUrl(input);
if (url.pathname ===
"/v2/spaces/abc-defg-hij" ) {
return jsonResponse({
name:
"spaces/abc-defg-hij" ,
meetingCode:
"abc-defg-hij" ,
meetingUri:
"https://meet.google.com/abc-defg-hij ",
});
}
if (url.pathname ===
"/v2/conferenceRecords" ) {
return jsonResponse({
conferenceRecords: [
{
name:
"conferenceRecords/rec-1" ,
space:
"spaces/abc-defg-hij" ,
startTime:
"2026-04-25T10:00:00Z" ,
endTime:
"2026-04-25T10:30:00Z" ,
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1" ) {
return jsonResponse({
name:
"conferenceRecords/rec-1" ,
space:
"spaces/abc-defg-hij" ,
startTime:
"2026-04-25T10:00:00Z" ,
endTime:
"2026-04-25T10:30:00Z" ,
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/participants" ) {
return jsonResponse({
participants: [
{
name:
"conferenceRecords/rec-1/participants/p1" ,
earliestStartTime:
"2026-04-25T10:00:00Z" ,
latestEndTime:
"2026-04-25T10:30:00Z" ,
signedinUser: { user:
"users/alice" , displayName:
"Alice" },
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/participants/p1/participantSessions" ) {
return jsonResponse({
participantSessions: [
{
name:
"conferenceRecords/rec-1/participants/p1/participantSessions/s1" ,
startTime:
"2026-04-25T10:00:00Z" ,
endTime:
"2026-04-25T10:30:00Z" ,
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/recordings" ) {
return jsonResponse({
recordings: [
{
name:
"conferenceRecords/rec-1/recordings/r1" ,
driveDestination: { file:
"drive/file-1" },
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/transcripts" ) {
return jsonResponse({
transcripts: [
{
name:
"conferenceRecords/rec-1/transcripts/t1" ,
docsDestination: { document:
"docs/doc-1" },
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/transcripts/t1/entries" ) {
return jsonResponse({
transcriptEntries: [
{
name:
"conferenceRecords/rec-1/transcripts/t1/entries/e1" ,
participant:
"conferenceRecords/rec-1/participants/p1" ,
text:
"Hello from the transcript." ,
languageCode:
"en-US" ,
startTime:
"2026-04-25T10:01:00Z" ,
endTime:
"2026-04-25T10:01:05Z" ,
},
],
});
}
if (url.pathname ===
"/v2/conferenceRecords/rec-1/smartNotes" ) {
return jsonResponse({
smartNotes: [
{
name:
"conferenceRecords/rec-1/smartNotes/sn1" ,
docsDestination: { document:
"docs/doc-2" },
},
],
});
}
return new Response(`unexpected ${url.pathname}`, { status:
404 });
});
vi.stubGlobal(
"fetch" , fetchMock);
return fetchMock;
}
type TestBridgeProcess = {
stdin?: { write(chunk: unknown): unknown } |
null ;
stdout?: { on(event:
"data" , listener: (chunk: unknown) =>
void ): unknown } |
null ;
stderr: PassThrough;
killed:
boolean ;
kill: ReturnType<
typeof vi.fn>;
on: EventEmitter[
"on" ];
};
describe(
"google-meet plugin" , () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.unstubAllGlobals();
});
it(
"defaults to chrome realtime with safe read-only tools" , () => {
expect(resolveGoogleMeetConfig({})).toMatchObject({
enabled:
true ,
defaults: {},
preview: { enrollmentAcknowledged:
false },
defaultTransport:
"chrome" ,
defaultMode:
"realtime" ,
chrome: {
audioBackend:
"blackhole-2ch" ,
launch:
true ,
guestName:
"OpenClaw Agent" ,
reuseExistingTab:
true ,
autoJoin:
true ,
waitForInCallMs:
20000 ,
audioInputCommand: [
"rec" ,
"-q" ,
"-t" ,
"raw" ,
"-r" ,
"8000" ,
"-c" ,
"1" ,
"-e" ,
"mu-law" ,
"-b" ,
"8" ,
"-" ,
],
audioOutputCommand: [
"play" ,
"-q" ,
"-t" ,
"raw" ,
"-r" ,
"8000" ,
"-c" ,
"1" ,
"-e" ,
"mu-law" ,
"-b" ,
"8" ,
"-" ,
],
},
voiceCall: { enabled:
true , requestTimeoutMs:
30000 , dtmfDelayMs:
2500 },
realtime: {
provider:
"openai" ,
introMessage:
"Say exactly: I'm here and listening." ,
toolPolicy:
"safe-read-only" ,
},
oauth: {},
auth: { provider:
"google-oauth" },
});
expect(resolveGoogleMeetConfig({}).realtime.instructions).toContain(
"openclaw_agent_consult" );
});
it(
"uses env fallbacks for OAuth, preview, and default meeting values" , () => {
expect(
resolveGoogleMeetConfigWithEnv(
{},
{
OPENCLAW_GOOGLE_MEET_CLIENT_ID:
"client-id" ,
GOOGLE_MEET_CLIENT_SECRET:
"client-secret" ,
OPENCLAW_GOOGLE_MEET_REFRESH_TOKEN:
"refresh-token" ,
GOOGLE_MEET_ACCESS_TOKEN:
"access-token" ,
OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT:
"123456" ,
GOOGLE_MEET_DEFAULT_MEETING:
"https://meet.google.com/abc-defg-hij ",
OPENCLAW_GOOGLE_MEET_PREVIEW_ACK:
"true" ,
},
),
).toMatchObject({
defaults: { meeting:
"https://meet.google.com/abc-defg-hij " },
preview: { enrollmentAcknowledged:
true },
oauth: {
clientId:
"client-id" ,
clientSecret:
"client-secret" ,
refreshToken:
"refresh-token" ,
accessToken:
"access-token" ,
expiresAt:
123456 ,
},
});
});
it(
"requires explicit Meet URLs" , () => {
expect(normalizeMeetUrl(
"https://meet.google.com/abc-defg-hij ")).toBe(
"https://meet.google.com/abc-defg-hij ",
);
expect(() => normalizeMeetUrl(
"https://example.com/abc-defg-hij ")).toThrow("meet.google.com");
});
it(
"advertises only the googlemeet CLI descriptor" , () => {
const { cliRegistrations } = setup();
expect(cliRegistrations).toContainEqual({
commands: [
"googlemeet" ],
descriptors: [
{
name:
"googlemeet" ,
description:
"Join and manage Google Meet calls" ,
hasSubcommands:
true ,
},
],
});
});
it(
"registers the node-host command used by chrome-node transport" , () => {
const { nodeHostCommands } = setup();
expect(nodeHostCommands).toContainEqual(
expect.objectContaining({
command:
"googlemeet.chrome" ,
cap:
"google-meet" ,
handle: expect.any(
Function ),
}),
);
});
it(
"uses a provider-safe flat tool parameter schema" , () => {
const { tools } = setup();
const tool = tools[
0 ] as { description?: string; parameters: unknown };
expect(tool.description).toContain(
"recover_current_tab" );
expect(JSON.stringify(tool.parameters)).not.toContain(
"anyOf" );
expect(tool.parameters).toMatchObject({
type:
"object" ,
properties: {
action: {
type:
"string" ,
enum : [
"join" ,
"create" ,
"status" ,
"setup_status" ,
"resolve_space" ,
"preflight" ,
"latest" ,
"artifacts" ,
"attendance" ,
"recover_current_tab" ,
"leave" ,
"speak" ,
"test_speech" ,
],
description: expect.stringContaining(
"recover_current_tab" ),
},
transport: { type:
"string" ,
enum : [
"chrome" ,
"chrome-node" ,
"twilio" ] },
mode: { type:
"string" ,
enum : [
"realtime" ,
"transcribe" ] },
},
});
});
it(
"normalizes Meet URLs, codes, and space names for the Meet API" , () => {
expect(normalizeGoogleMeetSpaceName(
"spaces/abc-defg-hij" )).toBe(
"spaces/abc-defg-hij" );
expect(normalizeGoogleMeetSpaceName(
"abc-defg-hij" )).toBe(
"spaces/abc-defg-hij" )
;
expect(normalizeGoogleMeetSpaceName("https://meet.google.com/abc-defg-hij ")).toBe(
"spaces/abc-defg-hij" ,
);
expect(() => normalizeGoogleMeetSpaceName("https://example.com/abc-defg-hij ")).toThrow(
"meet.google.com" ,
);
});
it("fetches Meet spaces without percent-encoding the spaces path separator" , async () => {
const fetchMock = vi.fn(async () => {
return new Response(
JSON.stringify({
name: "spaces/abc-defg-hij" ,
meetingCode: "abc-defg-hij" ,
meetingUri: "https://meet.google.com/abc-defg-hij ",
}),
{ status: 200 , headers: { "Content-Type" : "application/json" } },
);
});
vi.stubGlobal("fetch" , fetchMock);
await expect(
fetchGoogleMeetSpace({
accessToken: "token" ,
meeting: "spaces/abc-defg-hij" ,
}),
).resolves.toMatchObject({ name: "spaces/abc-defg-hij" });
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://meet.googleapis.com/v2/spaces/abc-defg-hij ",
init: expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer token" }),
}),
policy: { allowedHostnames: ["meet.googleapis.com" ] },
auditContext: "google-meet.spaces.get" ,
}),
);
expect(fetchMock).toHaveBeenCalledWith(
"https://meet.googleapis.com/v2/spaces/abc-defg-hij ",
expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer token" }),
}),
);
});
it("creates Meet spaces and returns the meeting URL" , async () => {
const fetchMock = vi.fn(async () => {
return new Response(
JSON.stringify({
name: "spaces/new-space" ,
meetingCode: "new-abcd-xyz" ,
meetingUri: "https://meet.google.com/new-abcd-xyz ",
}),
{ status: 200 , headers: { "Content-Type" : "application/json" } },
);
});
vi.stubGlobal("fetch" , fetchMock);
await expect(createGoogleMeetSpace({ accessToken: "token" })).resolves.toMatchObject({
meetingUri: "https://meet.google.com/new-abcd-xyz ",
space: { name: "spaces/new-space" },
});
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://meet.googleapis.com/v2/spaces ",
init: expect.objectContaining({
method: "POST" ,
headers: expect.objectContaining({ Authorization: "Bearer token" }),
body: "{}" ,
}),
policy: { allowedHostnames: ["meet.googleapis.com" ] },
auditContext: "google-meet.spaces.create" ,
}),
);
});
it("lists Meet artifact metadata for the latest conference record by default" , async () => {
const fetchMock = stubMeetArtifactsApi();
await expect(
fetchGoogleMeetArtifacts({
accessToken: "token" ,
meeting: "abc-defg-hij" ,
pageSize: 2 ,
}),
).resolves.toMatchObject({
input: "abc-defg-hij" ,
space: { name: "spaces/abc-defg-hij" },
conferenceRecords: [{ name: "conferenceRecords/rec-1" }],
artifacts: [
{
conferenceRecord: { name: "conferenceRecords/rec-1" },
participants: [{ name: "conferenceRecords/rec-1/participants/p1" }],
recordings: [{ name: "conferenceRecords/rec-1/recordings/r1" }],
transcripts: [{ name: "conferenceRecords/rec-1/transcripts/t1" }],
transcriptEntries: [
{
transcript: "conferenceRecords/rec-1/transcripts/t1" ,
entries: [
{
name: "conferenceRecords/rec-1/transcripts/t1/entries/e1" ,
text: "Hello from the transcript." ,
},
],
},
],
smartNotes: [{ name: "conferenceRecords/rec-1/smartNotes/sn1" }],
},
],
});
const listCall = fetchMock.mock.calls.find(([input]) => {
const url = requestUrl(input);
return url.pathname === "/v2/conferenceRecords" ;
});
if (!listCall) {
throw new Error("Expected conferenceRecords.list fetch call" );
}
const listUrl = requestUrl(listCall[0 ]);
expect(listUrl.searchParams.get("filter" )).toBe('space.name = "spaces/abc-defg-hij"' );
expect(listUrl.searchParams.get("pageSize" )).toBe("1" );
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://meet.googleapis.com/v2/conferenceRecords/rec-1/smartNotes?pageSize=2 ",
auditContext: "google-meet.conferenceRecords.smartNotes.list" ,
}),
);
expect(fetchGuardMocks.fetchWithSsrFGuard).toHaveBeenCalledWith(
expect.objectContaining({
url: "https://meet.googleapis.com/v2/conferenceRecords/rec-1/transcripts/t1/entries?pageSize=2 ",
auditContext: "google-meet.conferenceRecords.transcripts.entries.list" ,
}),
);
});
it("keeps all conference records available when requested" , async () => {
const fetchMock = stubMeetArtifactsApi();
await fetchGoogleMeetArtifacts({
accessToken: "token" ,
meeting: "abc-defg-hij" ,
pageSize: 2 ,
allConferenceRecords: true ,
});
const listCall = fetchMock.mock.calls.find(([input]) => {
const url = requestUrl(input);
return url.pathname === "/v2/conferenceRecords" ;
});
if (!listCall) {
throw new Error("Expected conferenceRecords.list fetch call" );
}
const listUrl = requestUrl(listCall[0 ]);
expect(listUrl.searchParams.get("pageSize" )).toBe("2" );
expect(listUrl.searchParams.get("filter" )).toBe('space.name = "spaces/abc-defg-hij"' );
});
it("fetches only the latest Meet conference record for a meeting" , async () => {
const fetchMock = stubMeetArtifactsApi();
await expect(
fetchLatestGoogleMeetConferenceRecord({
accessToken: "token" ,
meeting: "abc-defg-hij" ,
}),
).resolves.toMatchObject({
input: "abc-defg-hij" ,
space: { name: "spaces/abc-defg-hij" },
conferenceRecord: { name: "conferenceRecords/rec-1" },
});
const listCall = fetchMock.mock.calls.find(([input]) => {
const url = requestUrl(input);
return url.pathname === "/v2/conferenceRecords" ;
});
if (!listCall) {
throw new Error("Expected conferenceRecords.list fetch call" );
}
const listUrl = requestUrl(listCall[0 ]);
expect(listUrl.searchParams.get("pageSize" )).toBe("1" );
expect(listUrl.searchParams.get("filter" )).toBe('space.name = "spaces/abc-defg-hij"' );
});
it("lists Meet attendance rows with participant sessions" , async () => {
const fetchMock = stubMeetArtifactsApi();
await expect(
fetchGoogleMeetAttendance({
accessToken: "token" ,
conferenceRecord: "rec-1" ,
pageSize: 3 ,
}),
).resolves.toMatchObject({
input: "rec-1" ,
conferenceRecords: [{ name: "conferenceRecords/rec-1" }],
attendance: [
{
conferenceRecord: "conferenceRecords/rec-1" ,
participant: "conferenceRecords/rec-1/participants/p1" ,
displayName: "Alice" ,
user: "users/alice" ,
sessions: [
{
name: "conferenceRecords/rec-1/participants/p1/participantSessions/s1" ,
},
],
},
],
});
expect(fetchMock).toHaveBeenCalledWith(
"https://meet.googleapis.com/v2/conferenceRecords/rec-1 ",
expect.objectContaining({
headers: expect.objectContaining({ Authorization: "Bearer token" }),
}),
);
});
it("surfaces Developer Preview acknowledgment blockers in preflight reports" , () => {
expect(
buildGoogleMeetPreflightReport({
input: "abc-defg-hij" ,
space: { name: "spaces/abc-defg-hij" },
previewAcknowledged: false ,
tokenSource: "cached-access-token" ,
}),
).toMatchObject({
resolvedSpaceName: "spaces/abc-defg-hij" ,
previewAcknowledged: false ,
blockers: [expect.stringContaining("Developer Preview Program" )],
});
});
it("builds Twilio dial plans from a PIN" , () => {
expect(normalizeDialInNumber("+1 (555) 123-4567" )).toBe("+15551234567" );
expect(buildMeetDtmfSequence({ pin: "123 456" })).toBe("123456#" );
expect(buildMeetDtmfSequence({ dtmfSequence: "ww123#" })).toBe("ww123#" );
});
it("joins a Twilio session through the tool without page parsing" , async () => {
const { tools } = setup({ defaultTransport: "twilio" });
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { session: unknown } }>;
};
const result = await tool.execute("id" , {
action: "join" ,
url: "https://meet.google.com/abc-defg-hij ",
dialInNumber: "+15551234567" ,
pin: "123456" ,
});
expect(result.details.session).toMatchObject({
transport: "twilio" ,
mode: "realtime" ,
twilio: {
dialInNumber: "+15551234567" ,
pinProvided: true ,
dtmfSequence: "123456#" ,
voiceCallId: "call-1" ,
dtmfSent: true ,
},
});
expect(voiceCallMocks.joinMeetViaVoiceCallGateway).toHaveBeenCalledWith({
config: expect.objectContaining({ defaultTransport: "twilio" }),
dialInNumber: "+15551234567" ,
dtmfSequence: "123456#" ,
});
});
it("hangs up delegated Twilio calls on leave" , async () => {
const { tools } = setup({ defaultTransport: "twilio" });
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { session: { id: string } } }>;
};
const joined = await tool.execute("id" , {
action: "join" ,
url: "https://meet.google.com/abc-defg-hij ",
dialInNumber: "+15551234567" ,
pin: "123456" ,
});
await tool.execute("id" , { action: "leave" , sessionId: joined.details.session.id });
expect(voiceCallMocks.endMeetVoiceCallGatewayCall).toHaveBeenCalledWith({
config: expect.objectContaining({ defaultTransport: "twilio" }),
callId: "call-1" ,
});
});
it("reports setup status through the tool" , async () => {
const { tools } = setup({
chrome: {
audioInputCommand: ["openclaw-audio-bridge" , "capture" ],
audioOutputCommand: ["openclaw-audio-bridge" , "play" ],
},
});
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { ok?: boolean } }>;
};
const result = await tool.execute("id" , { action: "setup_status" });
expect(result.details.ok).toBe(true );
});
it("reports attendance through the tool" , async () => {
stubMeetArtifactsApi();
const { tools } = setup();
const tool = tools[0 ] as {
execute: (
id: string,
params: unknown,
) => Promise<{ details: { attendance?: Array<{ displayName?: string }> } }>;
};
const result = await tool.execute("id" , {
action: "attendance" ,
accessToken: "token" ,
expiresAt: Date.now() + 120 _000 ,
conferenceRecord: "rec-1" ,
pageSize: 3 ,
});
expect(result.details.attendance).toEqual([expect.objectContaining({ displayName: "Alice" })]);
});
it("reports the latest conference record through the tool" , async () => {
stubMeetArtifactsApi();
const { tools } = setup();
const tool = tools[0 ] as {
execute: (
id: string,
params: unknown,
) => Promise<{ details: { conferenceRecord?: { name?: string } } }>;
};
const result = await tool.execute("id" , {
action: "latest" ,
accessToken: "token" ,
expiresAt: Date.now() + 120 _000 ,
meeting: "abc-defg-hij" ,
});
expect(result.details.conferenceRecord).toMatchObject({ name: "conferenceRecords/rec-1" });
});
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" );
vi.stubEnv("TWILIO_FROM_NUMBER" , "+15550001234" );
const { tools } = setup(
{
defaultTransport: "chrome-node" ,
chromeNode: { node: "parallels-macos" },
},
{
fullConfig: {
plugins: {
allow: ["google-meet" , "voice-call" ],
entries: {
"voice-call" : {
enabled: true ,
config: { provider: "twilio" },
},
},
},
},
},
);
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(true );
expect(result.details.checks).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "twilio-voice-call-plugin" ,
ok: true ,
}),
expect.objectContaining({
id: "twilio-voice-call-credentials" ,
ok: true ,
}),
]),
);
});
it("reports missing voice-call wiring for Twilio transport" , async () => {
vi.stubEnv("TWILIO_ACCOUNT_SID" , "" );
vi.stubEnv("TWILIO_AUTH_TOKEN" , "" );
vi.stubEnv("TWILIO_FROM_NUMBER" , "" );
const { tools } = setup(
{ defaultTransport: "twilio" },
{
fullConfig: {
plugins: {
allow: ["google-meet" ],
entries: {
"voice-call" : { enabled: false },
},
},
},
},
);
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: "twilio-voice-call-plugin" ,
ok: false ,
}),
expect.objectContaining({
id: "twilio-voice-call-credentials" ,
ok: false ,
}),
]),
);
});
it("launches Chrome after the BlackHole check" , async () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform" , { value: "darwin" });
try {
const { methods, runCommandWithTimeout } = setup({
defaultMode: "transcribe" ,
});
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const respond = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond,
});
expect(respond.mock.calls[0 ]?.[0 ]).toBe(true );
expect(runCommandWithTimeout).toHaveBeenNthCalledWith(
1 ,
["/usr/sbin/system_profiler" , "SPAudioDataType" ],
{ timeoutMs: 10000 },
);
expect(runCommandWithTimeout).toHaveBeenNthCalledWith(
2 ,
["open" , "-a" , "Google Chrome" , "https://meet.google.com/abc-defg-hij "],
{ timeoutMs: 30000 },
);
} finally {
Object.defineProperty(process, "platform" , { value: originalPlatform });
}
});
it("joins Chrome on a paired node without local Chrome or BlackHole" , async () => {
const { methods, nodesList, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
chromeNode: { node: "parallels-macos" },
},
{
nodesInvokeResult: { payload: { launched: true } },
},
);
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const respond = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond,
});
expect(respond.mock.calls[0 ]?.[0 ]).toBe(true );
expect(nodesList).toHaveBeenCalledWith({ connected: true });
expect(nodesInvoke).toHaveBeenCalledWith(
expect.objectContaining({
nodeId: "node-1" ,
command: "browser.proxy" ,
params: expect.objectContaining({
path: "/tabs/open" ,
body: { url: "https://meet.google.com/abc-defg-hij " },
}),
}),
);
expect(nodesInvoke).toHaveBeenCalledWith(
expect.objectContaining({
nodeId: "node-1" ,
command: "googlemeet.chrome" ,
params: expect.objectContaining({
action: "start" ,
url: "https://meet.google.com/abc-defg-hij ",
mode: "transcribe" ,
launch: false ,
}),
}),
);
expect(respond.mock.calls[0 ]?.[1 ]).toMatchObject({
session: {
transport: "chrome-node" ,
chrome: {
nodeId: "node-1" ,
launched: true ,
},
},
});
});
it("reuses an active Meet session for the same URL and transport" , async () => {
const { methods, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
},
{
nodesInvokeResult: {
payload: {
launched: true ,
browser: { inCall: true , micMuted: false },
},
},
},
);
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const first = vi.fn();
const second = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond: first,
});
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond: second,
});
expect(
nodesInvoke.mock.calls.filter(([call]) => call.command === "googlemeet.chrome" ),
).toHaveLength(1 );
expect(second.mock.calls[0 ]?.[1 ]).toMatchObject({
session: {
chrome: { health: { inCall: true , micMuted: false } },
notes: expect.arrayContaining(["Reused existing active Meet session." ]),
},
});
});
it("reuses active Meet sessions across URL query differences" , async () => {
const { methods, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
},
{
nodesInvokeResult: {
payload: {
launched: true ,
browser: { inCall: true , micMuted: false },
},
},
},
);
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const first = vi.fn();
const second = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij?authuser=me@example.com " },
respond: first,
});
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond: second,
});
expect(
nodesInvoke.mock.calls.filter(([call]) => call.command === "googlemeet.chrome" ),
).toHaveLength(1 );
expect(second.mock.calls[0 ]?.[1 ]).toMatchObject({
session: {
notes: expect.arrayContaining(["Reused existing active Meet session." ]),
},
});
});
it("reuses existing Meet browser tabs across URL query differences" , async () => {
const { methods, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
},
{
nodesInvokeHandler: async (params) => {
if (params.command !== "browser.proxy" ) {
return { payload: { launched: true } };
}
const proxy = params.params as {
path?: string;
body?: { targetId?: string; url?: string };
};
if (proxy.path === "/tabs" ) {
return {
payload: {
result: {
running: true ,
tabs: [
{
targetId: "existing-meet-tab" ,
title: "Meet" ,
url: "https://meet.google.com/abc-defg-hij?authuser=me@example.com ",
},
],
},
},
};
}
if (proxy.path === "/tabs/focus" ) {
return { payload: { result: { ok: true } } };
}
if (proxy.path === "/act" ) {
return {
payload: {
result: {
result: JSON.stringify({
inCall: true ,
title: "Meet" ,
url: "https://meet.google.com/abc-defg-hij?authuser=me@example.com ",
}),
},
},
};
}
throw new Error(`unexpected browser proxy path ${proxy.path}`);
},
},
);
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const respond = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond,
});
expect(nodesInvoke).toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({
path: "/tabs/focus" ,
body: { targetId: "existing-meet-tab" },
}),
}),
);
expect(nodesInvoke).not.toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({ path: "/tabs/open" }),
}),
);
});
it("recovers and inspects an existing Meet tab without opening a new one" , async () => {
const { tools, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
},
{
nodesInvokeHandler: async (params) => {
if (params.command !== "browser.proxy" ) {
throw new Error(`unexpected command ${params.command}`);
}
const proxy = params.params as { path?: string; body?: { targetId?: string } };
if (proxy.path === "/tabs" ) {
return {
payload: {
result: {
tabs: [
{
targetId: "existing-meet-tab" ,
title: "Meet" ,
url: "https://meet.google.com/abc-defg-hij?authuser=me@example.com ",
},
],
},
},
};
}
if (proxy.path === "/tabs/focus" ) {
return { payload: { result: { ok: true } } };
}
if (proxy.path === "/act" ) {
return {
payload: {
result: {
result: JSON.stringify({
inCall: false ,
manualActionRequired: true ,
manualActionReason: "meet-admission-required" ,
manualActionMessage: "Admit the OpenClaw browser participant in Google Meet." ,
title: "Meet" ,
url: "https://meet.google.com/abc-defg-hij?authuser=me@example.com ",
}),
},
},
};
}
throw new Error(`unexpected browser proxy path ${proxy.path}`);
},
},
);
const tool = tools[0 ] as {
execute: (
id: string,
params: unknown,
) => Promise<{ details: { found?: boolean ; browser?: unknown } }>;
};
const result = await tool.execute("id" , {
action: "recover_current_tab" ,
url: "https://meet.google.com/abc-defg-hij ",
});
expect(result.details).toMatchObject({
found: true ,
targetId: "existing-meet-tab" ,
browser: {
manualActionRequired: true ,
manualActionReason: "meet-admission-required" ,
},
});
expect(nodesInvoke).toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({
path: "/tabs/focus" ,
body: { targetId: "existing-meet-tab" },
}),
}),
);
expect(nodesInvoke).not.toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({ path: "/tabs/open" }),
}),
);
});
it("exposes a test-speech action that joins the requested meeting" , async () => {
const { tools, nodesInvoke } = setup(
{
defaultTransport: "chrome-node" ,
},
{
nodesInvokeResult: {
payload: {
launched: true ,
browser: { inCall: true },
},
},
},
);
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { createdSession?: boolean } }>;
};
const result = await tool.execute("id" , {
action: "test_speech" ,
url: "https://meet.google.com/abc-defg-hij ",
message: "Say exactly: hello." ,
});
expect(nodesInvoke).toHaveBeenCalledWith(
expect.objectContaining({
command: "googlemeet.chrome" ,
params: expect.objectContaining({ action: "start" }),
}),
);
expect(result.details).toMatchObject({ createdSession: true });
});
it("reports manual action when the browser profile needs Google login" , async () => {
const { tools } = setup(
{
defaultTransport: "chrome-node" ,
},
{
browserActResult: {
inCall: false ,
manualActionRequired: true ,
manualActionReason: "google-login-required" ,
manualActionMessage:
"Sign in to Google in the OpenClaw browser profile, then retry the Meet join." ,
title: "Sign in - Google Accounts" ,
url: "https://accounts.google.com/signin ",
},
nodesInvokeResult: {
payload: {
launched: true ,
},
},
},
);
const tool = tools[0 ] as {
execute: (
id: string,
params: unknown,
) => Promise<{
details: {
manualActionRequired?: boolean ;
manualActionReason?: string;
session?: { chrome?: { health?: { manualActionRequired?: boolean } } };
};
}>;
};
const result = await tool.execute("id" , {
action: "test_speech" ,
url: "https://meet.google.com/abc-defg-hij ",
message: "Say exactly: hello." ,
});
expect(result.details).toMatchObject({
manualActionRequired: true ,
manualActionReason: "google-login-required" ,
session: {
chrome: {
health: {
manualActionRequired: true ,
manualActionReason: "google-login-required" ,
},
},
},
});
});
it("explains when chrome-node has no capable paired node" , async () => {
const { tools } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
},
{
nodesListResult: { nodes: [] },
},
);
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { error?: string } }>;
};
const result = await tool.execute("id" , {
action: "join" ,
url: "https://meet.google.com/abc-defg-hij ",
});
expect(result.details.error).toContain("No connected Google Meet-capable node" );
expect(result.details.error).toContain("openclaw node run" );
});
it("requires chromeNode.node when multiple capable nodes are connected" , async () => {
const { tools } = setup(
{
defaultTransport: "chrome-node" ,
defaultMode: "transcribe" ,
},
{
nodesListResult: {
nodes: [
{
nodeId: "node-1" ,
displayName: "parallels-macos" ,
connected: true ,
caps: ["browser" ],
commands: ["browser.proxy" , "googlemeet.chrome" ],
},
{
nodeId: "node-2" ,
displayName: "mac-studio-vm" ,
connected: true ,
caps: ["browser" ],
commands: ["browser.proxy" , "googlemeet.chrome" ],
},
],
},
},
);
const tool = tools[0 ] as {
execute: (id: string, params: unknown) => Promise<{ details: { error?: string } }>;
};
const result = await tool.execute("id" , {
action: "join" ,
url: "https://meet.google.com/abc-defg-hij ",
});
expect(result.details.error).toContain("Multiple Google Meet-capable nodes connected" );
expect(result.details.error).toContain("chromeNode.node" );
});
it("runs configured Chrome audio bridge commands before launch" , async () => {
const originalPlatform = process.platform;
Object.defineProperty(process, "platform" , { value: "darwin" });
try {
const { methods, runCommandWithTimeout } = setup({
chrome: {
audioBridgeHealthCommand: ["bridge" , "status" ],
audioBridgeCommand: ["bridge" , "start" ],
},
});
const handler = methods.get("googlemeet.join" ) as
| ((ctx: {
params: Record<string, unknown>;
respond: ReturnType<typeof vi.fn>;
}) => Promise<void >)
| undefined;
const respond = vi.fn();
await handler?.({
params: { url: "https://meet.google.com/abc-defg-hij " },
respond,
});
expect(respond.mock.calls[0 ]?.[0 ]).toBe(true );
expect(runCommandWithTimeout).toHaveBeenNthCalledWith(2 , ["bridge" , "status" ], {
timeoutMs: 30000 ,
});
expect(runCommandWithTimeout).toHaveBeenNthCalledWith(3 , ["bridge" , "start" ], {
timeoutMs: 30000 ,
});
} finally {
Object.defineProperty(process, "platform" , { value: originalPlatform });
}
});
it("pipes Chrome command-pair audio through the realtime provider" , async () => {
let callbacks:
| {
onAudio: (audio: Buffer) => void ;
onMark?: (markName: string) => void ;
onToolCall?: (event: {
itemId: string;
callId: string;
name: string;
args: unknown;
}) => void ;
onReady?: () => void ;
tools?: unknown[];
}
| undefined;
const sendAudio = vi.fn();
const bridge = {
connect: vi.fn(async () => {}),
sendAudio,
setMediaTimestamp: vi.fn(),
submitToolResult: vi.fn(),
acknowledgeMark: vi.fn(),
close: vi.fn(),
triggerGreeting: vi.fn(),
isConnected: vi.fn(() => true ),
};
const provider: RealtimeVoiceProviderPlugin = {
id: "openai" ,
label: "OpenAI" ,
autoSelectOrder: 1 ,
resolveConfig: ({ rawConfig }) => rawConfig,
isConfigured: () => true ,
createBridge: (req) => {
callbacks = req;
return bridge;
},
};
const inputStdout = new PassThrough();
const outputStdinWrites: Buffer[] = [];
const makeProcess = (stdio: {
stdin?: { write(chunk: unknown): unknown } | null ;
stdout?: { on(event: "data" , listener: (chunk: unknown) => void ): unknown } | null ;
}): TestBridgeProcess => {
const proc = new EventEmitter() as unknown as TestBridgeProcess;
proc.stdin = stdio.stdin;
proc.stdout = stdio.stdout;
proc.stderr = new PassThrough();
proc.killed = false ;
proc.kill = vi.fn(() => {
proc.killed = true ;
return true ;
});
return proc;
};
const outputStdin = new Writable({
write(chunk, _encoding, done) {
outputStdinWrites.push(Buffer.from(chunk));
done();
},
});
const inputProcess = makeProcess({ stdout: inputStdout, stdin: null });
const outputProcess = makeProcess({ stdin: outputStdin, stdout: null });
const spawnMock = vi.fn().mockReturnValueOnce(outputProcess).mockReturnValueOnce(inputProcess);
const sessionStore: Record<string, unknown> = {};
const runtime = {
agent: {
resolveAgentDir: vi.fn(() => "/tmp/agent" ),
resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace" ),
ensureAgentWorkspace: vi.fn(async () => {}),
session: {
resolveStorePath: vi.fn(() => "/tmp/sessions.json" ),
loadSessionStore: vi.fn(() => sessionStore),
saveSessionStore: vi.fn(async () => {}),
resolveSessionFilePath: vi.fn(() => "/tmp/session.json" ),
},
runEmbeddedPiAgent: vi.fn(async () => ({
payloads: [{ text: "Use the Portugal launch data." }],
meta: {},
})),
resolveAgentTimeoutMs: vi.fn(() => 1000 ),
},
};
const handle = await startCommandRealtimeAudioBridge({
config: resolveGoogleMeetConfig({
realtime: { provider: "openai" , model: "gpt-realtime" },
}),
fullConfig: {} as never,
runtime: runtime as never,
meetingSessionId: "meet-1" ,
inputCommand: ["capture-meet" ],
outputCommand: ["play-meet" ],
logger: noopLogger,
providers: [provider],
spawn: spawnMock,
});
inputStdout.write(Buffer.from([1 , 2 , 3 ]));
callbacks?.onAudio(Buffer.from([4 , 5 ]));
callbacks?.onMark?.("mark-1" );
callbacks?.onReady?.();
callbacks?.onToolCall?.({
itemId: "item-1" ,
callId: "tool-call-1" ,
name: "openclaw_agent_consult" ,
args: { question: "What should I say about launch timing?" },
});
expect(spawnMock).toHaveBeenNthCalledWith(1 , "play-meet" , [], {
stdio: ["pipe" , "ignore" , "pipe" ],
});
expect(spawnMock).toHaveBeenNthCalledWith(2 , "capture-meet" , [], {
stdio: ["ignore" , "pipe" , "pipe" ],
});
expect(sendAudio).toHaveBeenCalledWith(Buffer.from([1 , 2 , 3 ]));
expect(outputStdinWrites).toEqual([Buffer.from([4 , 5 ])]);
expect(bridge.acknowledgeMark).toHaveBeenCalled();
expect(bridge.triggerGreeting).not.toHaveBeenCalled();
handle.speak("Say exactly: hello from the meeting." );
expect(bridge.triggerGreeting).toHaveBeenLastCalledWith("Say exactly: hello from the meeting." );
expect(handle.getHealth()).toMatchObject({
providerConnected: true ,
realtimeReady: true ,
audioInputActive: true ,
audioOutputActive: true ,
lastInputBytes: 3 ,
lastOutputBytes: 2 ,
});
expect(callbacks).toMatchObject({
tools: [
expect.objectContaining({
name: "openclaw_agent_consult" ,
}),
],
});
await vi.waitFor(() => {
expect(bridge.submitToolResult).toHaveBeenCalledWith("tool-call-1" , {
text: "Use the Portugal launch data." ,
});
});
expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledWith(
expect.objectContaining({
messageProvider: "google-meet" ,
thinkLevel: "high" ,
toolsAllow: ["read" , "web_search" , "web_fetch" , "x_search" , "memory_search" , "memory_get" ],
}),
);
await handle.stop();
expect(bridge.close).toHaveBeenCalled();
expect(inputProcess.kill).toHaveBeenCalledWith("SIGTERM" );
expect(outputProcess.kill).toHaveBeenCalledWith("SIGTERM" );
});
it("pipes paired-node command-pair audio through the realtime provider" , async () => {
let callbacks:
| {
onAudio: (audio: Buffer) => void ;
onToolCall?: (event: {
itemId: string;
callId: string;
name: string;
args: unknown;
}) => void ;
onReady?: () => void ;
tools?: unknown[];
}
| undefined;
const sendAudio = vi.fn();
const bridge = {
connect: vi.fn(async () => {}),
sendAudio,
setMediaTimestamp: vi.fn(),
submitToolResult: vi.fn(),
acknowledgeMark: vi.fn(),
close: vi.fn(),
triggerGreeting: vi.fn(),
isConnected: vi.fn(() => true ),
};
const provider: RealtimeVoiceProviderPlugin = {
id: "openai" ,
label: "OpenAI" ,
autoSelectOrder: 1 ,
resolveConfig: ({ rawConfig }) => rawConfig,
isConfigured: () => true ,
createBridge: (req) => {
callbacks = req;
return bridge;
},
};
let pullCount = 0 ;
const runtime = {
nodes: {
invoke: vi.fn(async ({ params }: { params?: { action?: string; base64?: string } }) => {
if (params?.action === "pullAudio" ) {
pullCount += 1 ;
if (pullCount === 1 ) {
return { bridgeId: "bridge-1" , base64: Buffer.from([9 , 8 , 7 ]).toString("base64" ) };
}
await new Promise((resolve) => setTimeout(resolve, 1 _000 ));
return { bridgeId: "bridge-1" };
}
return { ok: true };
}),
},
agent: {
resolveAgentDir: vi.fn(() => "/tmp/agent" ),
resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace" ),
ensureAgentWorkspace: vi.fn(async () => {}),
session: {
resolveStorePath: vi.fn(() => "/tmp/sessions.json" ),
loadSessionStore: vi.fn(() => ({})),
saveSessionStore: vi.fn(async () => {}),
resolveSessionFilePath: vi.fn(() => "/tmp/session.json" ),
},
runEmbeddedPiAgent: vi.fn(async () => ({
payloads: [{ text: "Use the launch update." }],
meta: {},
})),
resolveAgentTimeoutMs: vi.fn(() => 1000 ),
},
};
const handle = await startNodeRealtimeAudioBridge({
config: resolveGoogleMeetConfig({
realtime: { provider: "openai" , model: "gpt-realtime" },
}),
fullConfig: {} as never,
runtime: runtime as never,
meetingSessionId: "meet-1" ,
nodeId: "node-1" ,
bridgeId: "bridge-1" ,
logger: noopLogger,
providers: [provider],
});
callbacks?.onAudio(Buffer.from([1 , 2 , 3 ]));
callbacks?.onReady?.();
callbacks?.onToolCall?.({
itemId: "item-1" ,
callId: "tool-call-1" ,
name: "openclaw_agent_consult" ,
args: { question: "What should I say?" },
});
await vi.waitFor(() => {
expect(sendAudio).toHaveBeenCalledWith(Buffer.from([9 , 8 , 7 ]));
});
await vi.waitFor(() => {
expect(runtime.nodes.invoke).toHaveBeenCalledWith(
expect.objectContaining({
nodeId: "node-1" ,
command: "googlemeet.chrome" ,
params: expect.objectContaining({
action: "pushAudio" ,
bridgeId: "bridge-1" ,
base64: Buffer.from([1 , 2 , 3 ]).toString("base64" ),
}),
}),
);
});
await vi.waitFor(() => {
expect(bridge.submitToolResult).toHaveBeenCalledWith("tool-call-1" , {
text: "Use the launch update." ,
});
});
expect(bridge.triggerGreeting).not.toHaveBeenCalled();
handle.speak("Say exactly: hello from the node." );
expect(bridge.triggerGreeting).toHaveBeenLastCalledWith("Say exactly: hello from the node." );
expect(callbacks).toMatchObject({
tools: [
expect.objectContaining({
name: "openclaw_agent_consult" ,
}),
],
});
expect(handle).toMatchObject({
type: "node-command-pair" ,
providerId: "openai" ,
nodeId: "node-1" ,
bridgeId: "bridge-1" ,
});
expect(handle.getHealth()).toMatchObject({
providerConnected: true ,
realtimeReady: true ,
audioInputActive: true ,
audioOutputActive: true ,
lastInputBytes: 3 ,
lastOutputBytes: 3 ,
});
await handle.stop();
expect(bridge.close).toHaveBeenCalled();
expect(runtime.nodes.invoke).toHaveBeenCalledWith(
expect.objectContaining({
nodeId: "node-1" ,
command: "googlemeet.chrome" ,
params: { action: "stop" , bridgeId: "bridge-1" },
timeoutMs: 5 _000 ,
}),
);
});
});
Messung V0.5 in Prozent C=98 H=100 G=98
¤ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet am 2026-06-06)
¤
*© Formatika GbR, Deutschland