Add Google Meet realtime consult agentId (#72381)

Remote proof:
- CI run 24982271745 passed on 6122e13c9f.
- Blacksmith Testbox tbx_01kq6vwehcszjfpp52f0pb3v1q passed focused Google Meet formatting, docs/link checks, realtime consult runtime tests, Google Meet tests, extension test typecheck, the core-unit-fast-support shard, and the core support boundary shard.

Thanks @BsnizND.

Co-authored-by: BSnizND <199837910+BsnizND@users.noreply.github.com>
This commit is contained in:
BsnizND
2026-04-27 00:36:59 -07:00
committed by GitHub
parent f6db86f9a0
commit d5e6abcb3d
7 changed files with 44 additions and 2 deletions

View File

@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
- Voice Call: allow SecretRef-backed Twilio auth tokens and call-specific OpenAI/ElevenLabs TTS API keys through the plugin config surface. Fixes #68690. Thanks @joshavant.
- Google Meet: clean stale chrome-node realtime audio bridges by URL before rejoining, expose active node bridge inspection, and tolerate transient node input pull failures instead of dropping the Meet session. Fixes #72371. (#72372) Thanks @BsnizND.
- Google Meet: clear queued Gemini Live playback when realtime interruptions arrive, restart Chrome command-pair audio output after clears, and expose Google Live interruption/VAD config knobs for Meet and Voice Call realtime bridges. Fixes #72523. (#72524) Thanks @BsnizND.
- Google Meet: add `realtime.agentId` so live meeting consults can target a named OpenClaw agent instead of always using `main`. (#72381) Thanks @BsnizND.
- Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.
- Cron: treat isolated run-level agent failures as job errors even when no reply payload is produced, synthesizing a safe error payload so model/provider failures increment error counters and trigger failure notifications instead of clearing as successful. Fixes #43604; carries forward #43631. Thanks @SPFAdvisors.
- Cron: preserve exact `NO_REPLY` tool results from isolated jobs with empty final assistant turns as quiet successes instead of surfacing incomplete-turn errors. Fixes #68452; carries forward #68453. Thanks @anyech.

View File

@@ -897,6 +897,8 @@ Defaults:
`openclaw_agent_consult` for deeper answers
- `realtime.introMessage`: short spoken readiness check when the realtime bridge
connects; set it to `""` to join silently
- `realtime.agentId`: optional OpenClaw agent id for
`openclaw_agent_consult`; defaults to `main`
Optional overrides:
@@ -915,6 +917,7 @@ Optional overrides:
},
realtime: {
provider: "google",
agentId: "jay",
toolPolicy: "owner",
introMessage: "Say exactly: I'm here.",
providers: {
@@ -1001,6 +1004,10 @@ meeting transcript context and returns a concise spoken answer to the realtime
voice session. The voice model can then speak that answer back into the meeting.
It uses the same shared realtime consult tool as Voice Call.
By default, consults run against the `main` agent. Set `realtime.agentId` when a
Meet lane should consult a dedicated OpenClaw agent workspace, model defaults,
tool policy, memory, and session history.
`realtime.toolPolicy` controls the consult run:
- `safe-read-only`: expose the consult tool and limit the regular agent to

View File

@@ -287,6 +287,16 @@ describe("google-meet plugin", () => {
expect(resolveGoogleMeetConfig({}).realtime.instructions).toContain("openclaw_agent_consult");
});
it("resolves the realtime consult agent id", () => {
expect(
resolveGoogleMeetConfig({
realtime: {
agentId: " jay ",
},
}).realtime.agentId,
).toBe("jay");
});
it("uses env fallbacks for OAuth, preview, and default meeting values", () => {
expect(
resolveGoogleMeetConfigWithEnv(
@@ -1976,7 +1986,7 @@ describe("google-meet plugin", () => {
const handle = await startCommandRealtimeAudioBridge({
config: resolveGoogleMeetConfig({
realtime: { provider: "openai", model: "gpt-realtime" },
realtime: { provider: "openai", model: "gpt-realtime", agentId: "jay" },
}),
fullConfig: {} as never,
runtime: runtime as never,
@@ -2041,10 +2051,14 @@ describe("google-meet plugin", () => {
expect(runtime.agent.runEmbeddedPiAgent).toHaveBeenCalledWith(
expect.objectContaining({
messageProvider: "google-meet",
agentId: "jay",
sessionKey: "agent:jay:google-meet:meet-1",
sandboxSessionKey: "agent:jay:google-meet:meet-1",
thinkLevel: "high",
toolsAllow: ["read", "web_search", "web_fetch", "x_search", "memory_search", "memory_get"],
}),
);
expect(sessionStore).toHaveProperty("agent:jay:google-meet:meet-1");
await handle.stop();
expect(bridge.close).toHaveBeenCalled();

View File

@@ -120,6 +120,11 @@ const googleMeetConfigSchema = {
label: "Realtime Intro Message",
help: "Spoken once when the realtime bridge is ready. Set to an empty string to join silently.",
},
"realtime.agentId": {
label: "Realtime Consult Agent",
help: 'OpenClaw agent id used by openclaw_agent_consult. Defaults to "main".',
advanced: true,
},
"realtime.toolPolicy": {
label: "Realtime Tool Policy",
help: "Safe read-only tools are available by default; owner requests can unlock broader tools.",

View File

@@ -129,6 +129,11 @@
"label": "Realtime Intro Message",
"help": "Spoken once when the realtime bridge is ready. Set to an empty string to join silently."
},
"realtime.agentId": {
"label": "Realtime Consult Agent",
"help": "OpenClaw agent id used by openclaw_agent_consult. Defaults to \"main\".",
"advanced": true
},
"realtime.toolPolicy": {
"label": "Realtime Tool Policy",
"help": "Safe read-only tools are available by default; owner requests can unlock broader tools.",
@@ -353,6 +358,10 @@
"type": "string",
"default": "Say exactly: I'm here and listening."
},
"agentId": {
"type": "string",
"description": "OpenClaw agent id used by openclaw_agent_consult. Defaults to \"main\"."
},
"toolPolicy": {
"type": "string",
"enum": ["safe-read-only", "owner", "none"],

View File

@@ -8,6 +8,7 @@ import {
resolveRealtimeVoiceAgentConsultToolsAllow,
type RealtimeVoiceTool,
} from "openclaw/plugin-sdk/realtime-voice";
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
import type { GoogleMeetConfig, GoogleMeetToolPolicy } from "./config.js";
export const GOOGLE_MEET_AGENT_CONSULT_TOOL_NAME = REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME;
@@ -26,11 +27,14 @@ export async function consultOpenClawAgentForGoogleMeet(params: {
args: unknown;
transcript: Array<{ role: "user" | "assistant"; text: string }>;
}): Promise<{ text: string }> {
const agentId = normalizeAgentId(params.config.realtime.agentId);
const sessionKey = `agent:${agentId}:google-meet:${params.meetingSessionId}`;
return await consultRealtimeVoiceAgent({
cfg: params.fullConfig,
agentRuntime: params.runtime.agent,
logger: params.logger,
sessionKey: `google-meet:${params.meetingSessionId}`,
agentId,
sessionKey,
messageProvider: "google-meet",
lane: "google-meet",
runIdPrefix: `google-meet:${params.meetingSessionId}`,

View File

@@ -57,6 +57,7 @@ export type GoogleMeetConfig = {
model?: string;
instructions?: string;
introMessage?: string;
agentId?: string;
toolPolicy: GoogleMeetToolPolicy;
providers: Record<string, Record<string, unknown>>;
};
@@ -361,6 +362,7 @@ export function resolveGoogleMeetConfigWithEnv(
introMessage:
normalizeOptionalString(realtime.introMessage) ??
DEFAULT_GOOGLE_MEET_CONFIG.realtime.introMessage,
agentId: normalizeOptionalString(realtime.agentId),
toolPolicy: resolveRealtimeVoiceAgentConsultToolPolicy(
realtime.toolPolicy,
DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy,