diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf581f38b7..595d3225520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai - Dependencies/Pi: update bundled Pi packages to `0.70.0`, use Pi's upstream `gpt-5.5` catalog metadata for OpenAI and OpenAI Codex, and keep only local `gpt-5.5-pro` forward-compat handling. - Models/CLI: split `openclaw models list` row-source orchestration and registry loading into narrower helpers without changing list output behavior. (#70867) Thanks @shakkernerd. - Plugins/Google Meet: add a bundled participant plugin with personal Google auth, explicit meeting URL joins, Chrome and Twilio transports, and realtime voice support. (#70765) Thanks @steipete. +- Plugins/Google Meet: default Chrome realtime sessions to OpenAI plus SoX `rec`/`play` audio bridge commands, so the usual setup only needs the plugin enabled and `OPENAI_API_KEY`. - Providers/OpenAI: add image generation and reference-image editing through Codex OAuth, so `openai/gpt-image-2` works without an `OPENAI_API_KEY`. Fixes #70703. - Providers/OpenRouter: add image generation and reference-image editing through `image_generate`, so OpenRouter image models work with `OPENROUTER_API_KEY`. Fixes #55066 via #67668. Thanks @notamicrodose. - Image generation: let agents request provider-supported quality and output format hints, and pass OpenAI-specific background, moderation, compression, and user hints through the `image_generate` tool. (#70503) Thanks @ottodeng. diff --git a/docs/plugins/google-meet.md b/docs/plugins/google-meet.md index 87a0c4e7985..868fa068f16 100644 --- a/docs/plugins/google-meet.md +++ b/docs/plugins/google-meet.md @@ -103,7 +103,15 @@ Workspace Developer Preview Program for Meet media APIs. ## Config -Set config under `plugins.entries.google-meet.config`: +The common Chrome realtime path only needs the plugin enabled, BlackHole, SoX, +and an OpenAI key: + +```bash +brew install blackhole-2ch sox +export OPENAI_API_KEY=sk-... +``` + +Set the plugin config under `plugins.entries.google-meet.config`: ```json5 { @@ -111,86 +119,58 @@ Set config under `plugins.entries.google-meet.config`: entries: { "google-meet": { enabled: true, - config: { - defaultTransport: "chrome", - defaultMode: "realtime", - defaults: { - meeting: "https://meet.google.com/abc-defg-hij", - }, - preview: { - enrollmentAcknowledged: false, - }, - chrome: { - audioBackend: "blackhole-2ch", - launch: true, - browserProfile: "Default", - // Command-pair bridge: input writes 8 kHz G.711 mu-law audio to stdout. - audioInputCommand: [ - "rec", - "-q", - "-t", - "raw", - "-r", - "8000", - "-c", - "1", - "-e", - "mu-law", - "-b", - "8", - "-", - ], - // Output reads 8 kHz G.711 mu-law audio from stdin. - audioOutputCommand: [ - "play", - "-q", - "-t", - "raw", - "-r", - "8000", - "-c", - "1", - "-e", - "mu-law", - "-b", - "8", - "-", - ], - }, - twilio: { - defaultDialInNumber: "+15551234567", - defaultPin: "123456", - }, - voiceCall: { - enabled: true, - gatewayUrl: "ws://127.0.0.1:18789", - dtmfDelayMs: 2500, - }, - realtime: { - provider: "openai", - model: "gpt-realtime", - instructions: "You are joining a private Google Meet as my OpenClaw agent. Keep replies brief unless asked.", - toolPolicy: "safe-read-only", - providers: { - openai: { - apiKey: { env: "OPENAI_API_KEY" }, - }, - }, - }, - auth: { - provider: "google-oauth", - }, - oauth: { - clientId: "your-google-oauth-client-id.apps.googleusercontent.com", - refreshToken: "stored-refresh-token", - }, - }, + config: {}, }, }, }, } ``` +Defaults: + +- `defaultTransport: "chrome"` +- `defaultMode: "realtime"` +- `chrome.audioBackend: "blackhole-2ch"` +- `chrome.audioInputCommand`: SoX `rec` command writing 8 kHz G.711 mu-law + audio to stdout +- `chrome.audioOutputCommand`: SoX `play` command reading 8 kHz G.711 mu-law + audio from stdin +- `realtime.provider: "openai"` +- `realtime.toolPolicy: "safe-read-only"` +- `realtime.instructions`: brief spoken replies, with + `openclaw_agent_consult` for deeper answers + +Optional overrides: + +```json5 +{ + defaults: { + meeting: "https://meet.google.com/abc-defg-hij", + }, + chrome: { + browserProfile: "Default", + }, + realtime: { + toolPolicy: "owner", + }, +} +``` + +Twilio-only config: + +```json5 +{ + defaultTransport: "twilio", + twilio: { + defaultDialInNumber: "+15551234567", + defaultPin: "123456", + }, + voiceCall: { + gatewayUrl: "ws://127.0.0.1:18789", + }, +} +``` + ## Tool Agents can use the `google_meet` tool: diff --git a/extensions/google-meet/index.test.ts b/extensions/google-meet/index.test.ts index 66a4545cd62..d3497b4811c 100644 --- a/extensions/google-meet/index.test.ts +++ b/extensions/google-meet/index.test.ts @@ -113,12 +113,49 @@ describe("google-meet plugin", () => { preview: { enrollmentAcknowledged: false }, defaultTransport: "chrome", defaultMode: "realtime", - chrome: { audioBackend: "blackhole-2ch", launch: true }, + chrome: { + audioBackend: "blackhole-2ch", + launch: true, + 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: { toolPolicy: "safe-read-only" }, + realtime: { + provider: "openai", + 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", () => { diff --git a/extensions/google-meet/index.ts b/extensions/google-meet/index.ts index ab3940090cd..ccdebc0c328 100644 --- a/extensions/google-meet/index.ts +++ b/extensions/google-meet/index.ts @@ -78,7 +78,7 @@ const googleMeetConfigSchema = { "voiceCall.introMessage": { label: "Voice Call Intro Message", advanced: true }, "realtime.provider": { label: "Realtime Provider", - help: "Uses the first registered realtime voice provider when unset.", + help: "Defaults to OpenAI; uses OPENAI_API_KEY when no provider config is set.", }, "realtime.model": { label: "Realtime Model", advanced: true }, "realtime.instructions": { label: "Realtime Instructions", advanced: true }, diff --git a/extensions/google-meet/openclaw.plugin.json b/extensions/google-meet/openclaw.plugin.json index 2881c0a2e45..ccef029a5fb 100644 --- a/extensions/google-meet/openclaw.plugin.json +++ b/extensions/google-meet/openclaw.plugin.json @@ -93,7 +93,7 @@ }, "realtime.provider": { "label": "Realtime Provider", - "help": "Uses the first registered realtime voice provider when unset." + "help": "Defaults to OpenAI; uses OPENAI_API_KEY when no provider config is set." }, "realtime.model": { "label": "Realtime Model", @@ -157,11 +157,13 @@ }, "defaultTransport": { "type": "string", - "enum": ["chrome", "twilio"] + "enum": ["chrome", "twilio"], + "default": "chrome" }, "defaultMode": { "type": "string", - "enum": ["realtime", "transcribe"] + "enum": ["realtime", "transcribe"], + "default": "realtime" }, "chrome": { "type": "object", @@ -169,25 +171,58 @@ "properties": { "audioBackend": { "type": "string", - "enum": ["blackhole-2ch"] + "enum": ["blackhole-2ch"], + "default": "blackhole-2ch" }, "launch": { - "type": "boolean" + "type": "boolean", + "default": true }, "browserProfile": { "type": "string" }, "joinTimeoutMs": { - "type": "number" + "type": "number", + "default": 30000 }, "audioInputCommand": { "type": "array", + "default": [ + "rec", + "-q", + "-t", + "raw", + "-r", + "8000", + "-c", + "1", + "-e", + "mu-law", + "-b", + "8", + "-" + ], "items": { "type": "string" } }, "audioOutputCommand": { "type": "array", + "default": [ + "play", + "-q", + "-t", + "raw", + "-r", + "8000", + "-c", + "1", + "-e", + "mu-law", + "-b", + "8", + "-" + ], "items": { "type": "string" } @@ -226,7 +261,8 @@ "additionalProperties": false, "properties": { "enabled": { - "type": "boolean" + "type": "boolean", + "default": true }, "gatewayUrl": { "type": "string" @@ -235,10 +271,12 @@ "type": "string" }, "requestTimeoutMs": { - "type": "number" + "type": "number", + "default": 30000 }, "dtmfDelayMs": { - "type": "number" + "type": "number", + "default": 2500 }, "introMessage": { "type": "string" @@ -250,17 +288,20 @@ "additionalProperties": false, "properties": { "provider": { - "type": "string" + "type": "string", + "default": "openai" }, "model": { "type": "string" }, "instructions": { - "type": "string" + "type": "string", + "default": "You are joining a private Google Meet as an OpenClaw agent. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call openclaw_agent_consult before answering." }, "toolPolicy": { "type": "string", - "enum": ["safe-read-only", "owner", "none"] + "enum": ["safe-read-only", "owner", "none"], + "default": "safe-read-only" }, "providers": { "type": "object", diff --git a/extensions/google-meet/src/config.ts b/extensions/google-meet/src/config.ts index 93b6381eff0..aa12162fd90 100644 --- a/extensions/google-meet/src/config.ts +++ b/extensions/google-meet/src/config.ts @@ -62,6 +62,41 @@ export type GoogleMeetConfig = { }; }; +export const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND = [ + "rec", + "-q", + "-t", + "raw", + "-r", + "8000", + "-c", + "1", + "-e", + "mu-law", + "-b", + "8", + "-", +] as const; + +export const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = [ + "play", + "-q", + "-t", + "raw", + "-r", + "8000", + "-c", + "1", + "-e", + "mu-law", + "-b", + "8", + "-", +] as const; + +export const DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS = + "You are joining a private Google Meet as an OpenClaw agent. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call openclaw_agent_consult before answering."; + export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = { enabled: true, defaults: {}, @@ -74,6 +109,8 @@ export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = { audioBackend: "blackhole-2ch", launch: true, joinTimeoutMs: 30_000, + audioInputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND], + audioOutputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND], }, twilio: {}, voiceCall: { @@ -82,6 +119,8 @@ export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = { dtmfDelayMs: 2_500, }, realtime: { + provider: "openai", + instructions: DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS, toolPolicy: "safe-read-only", providers: {}, }, @@ -255,8 +294,12 @@ export function resolveGoogleMeetConfigWithEnv( chrome.joinTimeoutMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.joinTimeoutMs, ), - audioInputCommand: resolveStringArray(chrome.audioInputCommand), - audioOutputCommand: resolveStringArray(chrome.audioOutputCommand), + audioInputCommand: resolveStringArray(chrome.audioInputCommand) ?? [ + ...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND, + ], + audioOutputCommand: resolveStringArray(chrome.audioOutputCommand) ?? [ + ...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND, + ], audioBridgeCommand: resolveStringArray(chrome.audioBridgeCommand), audioBridgeHealthCommand: resolveStringArray(chrome.audioBridgeHealthCommand), }, @@ -280,9 +323,12 @@ export function resolveGoogleMeetConfigWithEnv( introMessage: normalizeOptionalString(voiceCall.introMessage), }, realtime: { - provider: normalizeOptionalString(realtime.provider), - model: normalizeOptionalString(realtime.model), - instructions: normalizeOptionalString(realtime.instructions), + provider: + normalizeOptionalString(realtime.provider) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.provider, + model: normalizeOptionalString(realtime.model) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.model, + instructions: + normalizeOptionalString(realtime.instructions) ?? + DEFAULT_GOOGLE_MEET_CONFIG.realtime.instructions, toolPolicy: resolveToolPolicy( realtime.toolPolicy, DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy,