
























@@ -371,6 +371,7 @@ describe("google-meet plugin", () => {
371371postDtmfSpeechDelayMs: 5000,
372372},
373373realtime: {
374+strategy: "agent",
374375provider: "openai",
375376introMessage: "Say exactly: I'm here and listening.",
376377toolPolicy: "safe-read-only",
@@ -2253,7 +2254,7 @@ describe("google-meet plugin", () => {
22532254);
22542255});
225522562256-it("retries caption enable until the captions button is available", () => {
2257+it("retries caption enable until the captions button is available", async () => {
22572258const makeButton = (label: string) => ({
22582259disabled: false,
22592260innerText: "",
@@ -2302,23 +2303,23 @@ describe("google-meet plugin", () => {
23022303 captureCaptions: true,
23032304 guestName: "OpenClaw Agent",
23042305 })})`,
2305-).runInContext(context) as () => string;
2306+).runInContext(context) as () => string | Promise<string>;
230623072307-const first = JSON.parse(inspect()) as { captionsEnabledAttempted?: boolean };
2308+const first = JSON.parse(await inspect()) as { captionsEnabledAttempted?: boolean };
23082309const stateAfterFirst = windowState.__openclawMeetCaptions as { enabledAttempted?: boolean };
23092310expect(first.captionsEnabledAttempted).toBe(false);
23102311expect(stateAfterFirst.enabledAttempted).toBe(false);
23112312expect(captionButton.click).not.toHaveBeenCalled();
2312231323132314page.buttons = [leaveButton, captionButton];
2314-const second = JSON.parse(inspect()) as { captionsEnabledAttempted?: boolean };
2315+const second = JSON.parse(await inspect()) as { captionsEnabledAttempted?: boolean };
23152316const stateAfterSecond = windowState.__openclawMeetCaptions as { enabledAttempted?: boolean };
23162317expect(second.captionsEnabledAttempted).toBe(true);
23172318expect(stateAfterSecond.enabledAttempted).toBe(true);
23182319expect(captionButton.click).toHaveBeenCalledTimes(1);
23192320});
232023212321-it("reports in-call Meet audio permission problems from button labels", () => {
2322+it("reports in-call Meet audio permission problems from button labels", async () => {
23222323const makeButton = (label: string) => ({
23232324disabled: false,
23242325innerText: "",
@@ -2361,9 +2362,9 @@ describe("google-meet plugin", () => {
23612362 captureCaptions: false,
23622363 guestName: "OpenClaw Agent",
23632364 })})`,
2364-).runInContext(context) as () => string;
2365+).runInContext(context) as () => string | Promise<string>;
236523662366-const result = JSON.parse(inspect()) as {
2367+const result = JSON.parse(await inspect()) as {
23672368inCall?: boolean;
23682369manualActionRequired?: boolean;
23692370manualActionReason?: string;
@@ -2376,7 +2377,7 @@ describe("google-meet plugin", () => {
23762377expect(result.manualActionMessage).toContain("Allow microphone/camera/speaker permissions");
23772378});
237823792379-it("uses the local Meet microphone control instead of remote participant mute buttons", () => {
2380+it("uses the local Meet microphone control instead of remote participant mute buttons", async () => {
23802381const makeButton = (label: string, disabled = false) => ({
23812382 disabled,
23822383innerText: "",
@@ -2416,9 +2417,9 @@ describe("google-meet plugin", () => {
24162417 captureCaptions: false,
24172418 guestName: "OpenClaw Agent",
24182419 })})`,
2419-).runInContext(context) as () => string;
2420+).runInContext(context) as () => string | Promise<string>;
242024212421-const result = JSON.parse(inspect()) as { micMuted?: boolean; notes?: string[] };
2422+const result = JSON.parse(await inspect()) as { micMuted?: boolean; notes?: string[] };
2422242324232424expect(result.micMuted).toBe(true);
24242425expect(localMic.click).toHaveBeenCalledTimes(1);
@@ -3526,7 +3527,7 @@ describe("google-meet plugin", () => {
3526352735273528const handle = await startCommandRealtimeAudioBridge({
35283529config: resolveGoogleMeetConfig({
3529-realtime: { provider: "openai", model: "gpt-realtime", agentId: "jay" },
3530+realtime: { strategy: "bidi", provider: "openai", model: "gpt-realtime", agentId: "jay" },
35303531}),
35313532fullConfig: {} as never,
35323533runtime: runtime as never,
@@ -3579,6 +3580,7 @@ describe("google-meet plugin", () => {
35793580expect(outputProcess.kill).toHaveBeenCalledWith("SIGKILL");
35803581expect(replacementOutputStdinWrites).toEqual([Buffer.from([6, 7])]);
35813582outputProcess.emit("error", new Error("stale output process failed after clear"));
3583+outputStdin.emit("error", new Error("stale output pipe closed after clear"));
35823584expect(bridge.close).not.toHaveBeenCalled();
35833585expect(bridge.acknowledgeMark).toHaveBeenCalled();
35843586expect(bridge.triggerGreeting).not.toHaveBeenCalled();
@@ -3616,6 +3618,7 @@ describe("google-meet plugin", () => {
36163618sampleRateHz: 24000,
36173619channels: 1,
36183620},
3621+autoRespondToAudio: true,
36193622tools: [
36203623expect.objectContaining({
36213624name: "openclaw_agent_consult",
@@ -3635,20 +3638,134 @@ describe("google-meet plugin", () => {
36353638expect.objectContaining({
36363639messageProvider: "google-meet",
36373640agentId: "jay",
3638-sessionKey: "agent:jay:google-meet:meet-1",
3639-sandboxSessionKey: "agent:jay:google-meet:meet-1",
3641+spawnedBy: "agent:jay:main",
3642+sessionKey: "agent:jay:subagent:google-meet:meet-1",
3643+sandboxSessionKey: "agent:jay:subagent:google-meet:meet-1",
36403644thinkLevel: "high",
36413645toolsAllow: ["read", "web_search", "web_fetch", "x_search", "memory_search", "memory_get"],
36423646}),
36433647);
3644-expect(sessionStore).toHaveProperty("agent:jay:google-meet:meet-1");
3648+expect(sessionStore).toHaveProperty("agent:jay:subagent:google-meet:meet-1");
3645364936463650await handle.stop();
36473651expect(bridge.close).toHaveBeenCalled();
36483652expect(inputProcess.kill).toHaveBeenCalledWith("SIGTERM");
36493653expect(replacementOutputProcess.kill).toHaveBeenCalledWith("SIGTERM");
36503654});
365136553656+it("defaults Chrome command-pair realtime to agent-driven talk-back", async () => {
3657+let callbacks: Parameters<RealtimeVoiceProviderPlugin["createBridge"]>[0] | undefined;
3658+const sendUserMessage = vi.fn();
3659+const bridge = {
3660+connect: vi.fn(async () => {}),
3661+sendAudio: vi.fn(),
3662+ sendUserMessage,
3663+setMediaTimestamp: vi.fn(),
3664+submitToolResult: vi.fn(),
3665+acknowledgeMark: vi.fn(),
3666+close: vi.fn(),
3667+triggerGreeting: vi.fn(),
3668+isConnected: vi.fn(() => true),
3669+};
3670+const provider: RealtimeVoiceProviderPlugin = {
3671+id: "openai",
3672+label: "OpenAI",
3673+autoSelectOrder: 1,
3674+resolveConfig: ({ rawConfig }) => rawConfig,
3675+isConfigured: () => true,
3676+createBridge: (req) => {
3677+callbacks = req;
3678+return bridge;
3679+},
3680+};
3681+const inputStdout = new PassThrough();
3682+const makeProcess = (stdio: {
3683+stdin?: { write(chunk: unknown): unknown } | null;
3684+stdout?: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null;
3685+}): TestBridgeProcess => {
3686+const proc = new EventEmitter() as unknown as TestBridgeProcess;
3687+proc.stdin = stdio.stdin;
3688+proc.stdout = stdio.stdout;
3689+proc.stderr = new PassThrough();
3690+proc.killed = false;
3691+proc.kill = vi.fn(() => {
3692+proc.killed = true;
3693+return true;
3694+});
3695+return proc;
3696+};
3697+const outputProcess = makeProcess({
3698+stdin: new Writable({
3699+write(_chunk, _encoding, done) {
3700+done();
3701+},
3702+}),
3703+stdout: null,
3704+});
3705+const inputProcess = makeProcess({ stdout: inputStdout, stdin: null });
3706+const spawnMock = vi.fn().mockReturnValueOnce(outputProcess).mockReturnValueOnce(inputProcess);
3707+const sessionStore: Record<string, unknown> = {};
3708+const runtime = {
3709+agent: {
3710+resolveAgentDir: vi.fn(() => "/tmp/agent"),
3711+resolveAgentWorkspaceDir: vi.fn(() => "/tmp/workspace"),
3712+ensureAgentWorkspace: vi.fn(async () => {}),
3713+session: {
3714+resolveStorePath: vi.fn(() => "/tmp/sessions.json"),
3715+loadSessionStore: vi.fn(() => sessionStore),
3716+saveSessionStore: vi.fn(async () => {}),
3717+updateSessionStore: vi.fn(async (_storePath, mutator) => mutator(sessionStore as never)),
3718+resolveSessionFilePath: vi.fn(() => "/tmp/session.json"),
3719+},
3720+runEmbeddedPiAgent: vi.fn(async () => ({
3721+payloads: [{ text: "The launch is still on track." }],
3722+meta: {},
3723+})),
3724+resolveAgentTimeoutMs: vi.fn(() => 1000),
3725+},
3726+};
3727+3728+const handle = await startCommandRealtimeAudioBridge({
3729+config: resolveGoogleMeetConfig({ realtime: { provider: "openai", agentId: "jay" } }),
3730+fullConfig: {} as never,
3731+runtime: runtime as never,
3732+meetingSessionId: "meet-1",
3733+inputCommand: ["capture-meet"],
3734+outputCommand: ["play-meet"],
3735+logger: noopLogger,
3736+providers: [provider],
3737+spawn: spawnMock,
3738+});
3739+3740+expect(callbacks).toMatchObject({
3741+autoRespondToAudio: false,
3742+tools: [],
3743+});
3744+callbacks?.onTranscript?.("user", "Are we still on track?", true);
3745+callbacks?.onTranscript?.("user", "Please include launch blockers.", true);
3746+3747+await vi.waitFor(() => {
3748+expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledTimes(1);
3749+expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledWith(
3750+expect.objectContaining({
3751+agentId: "jay",
3752+spawnedBy: "agent:jay:main",
3753+sessionKey: "agent:jay:subagent:google-meet:meet-1",
3754+sandboxSessionKey: "agent:jay:subagent:google-meet:meet-1",
3755+}),
3756+);
3757+});
3758+expect(JSON.stringify(runtime.agent.runEmbeddedPiAgent.mock.calls[0]?.[0])).toContain(
3759+"Are we still on track?\\nPlease include launch blockers.",
3760+);
3761+expect(sendUserMessage).toHaveBeenCalledWith(
3762+expect.stringContaining(JSON.stringify("The launch is still on track.")),
3763+);
3764+expect(sessionStore).toHaveProperty("agent:jay:subagent:google-meet:meet-1");
3765+3766+await handle.stop();
3767+});
3768+36523769it("uses a local barge-in input command to clear active Chrome playback", async () => {
36533770let callbacks:
36543771| {
@@ -3818,7 +3935,7 @@ describe("google-meet plugin", () => {
3818393538193936const handle = await startNodeRealtimeAudioBridge({
38203937config: resolveGoogleMeetConfig({
3821-realtime: { provider: "openai", model: "gpt-realtime" },
3938+realtime: { strategy: "bidi", provider: "openai", model: "gpt-realtime" },
38223939}),
38233940fullConfig: {} as never,
38243941runtime: runtime as never,
@@ -3901,6 +4018,7 @@ describe("google-meet plugin", () => {
39014018sampleRateHz: 24000,
39024019channels: 1,
39034020},
4021+autoRespondToAudio: true,
39044022tools: [
39054023expect.objectContaining({
39064024name: "openclaw_agent_consult",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。