@openclaw/google-meet 2026.5.28 → 2026.5.30-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- import { a as isSameMeetUrlForReuse, c as resolveChromeNode, f as endGoogleMeetActiveConference, g as fetchLatestGoogleMeetConferenceRecord, h as fetchGoogleMeetSpace, i as callBrowserProxyOnNode, l as resolveChromeNodeInfo, m as fetchGoogleMeetAttendance, n as isGoogleMeetBrowserManualActionError, o as normalizeMeetUrlForReuse, p as fetchGoogleMeetArtifacts, r as asBrowserTabs, s as readBrowserTab, t as createMeetWithBrowserProxyOnNode, u as buildGoogleMeetPreflightReport } from "./chrome-create-B3zyjf5f.js";
2
- import { n as findGoogleMeetCalendarEvent, r as listGoogleMeetCalendarEvents, t as buildGoogleMeetCalendarDayWindow } from "./calendar-IxWkFtfn.js";
1
+ import { a as isSameMeetUrlForReuse, c as resolveChromeNode, f as endGoogleMeetActiveConference, g as fetchLatestGoogleMeetConferenceRecord, h as fetchGoogleMeetSpace, i as callBrowserProxyOnNode, l as resolveChromeNodeInfo, m as fetchGoogleMeetAttendance, n as isGoogleMeetBrowserManualActionError, o as normalizeMeetUrlForReuse, p as fetchGoogleMeetArtifacts, r as asBrowserTabs, s as readBrowserTab, t as createMeetWithBrowserProxyOnNode, u as buildGoogleMeetPreflightReport } from "./chrome-create-By4DuikO.js";
2
+ import { a as buildGoogleMeetCalendarDayWindow, i as resolveGoogleMeetGatewayOperationTimeoutMs, n as DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND, o as findGoogleMeetCalendarEvent, r as resolveGoogleMeetConfig, s as listGoogleMeetCalendarEvents, t as DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND } from "./config-HmaADLv3.js";
3
3
  import { optionalPositiveIntegerSchema, readPositiveIntegerParam } from "openclaw/plugin-sdk/channel-actions";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
5
  import { ErrorCodes, GatewayClient, callGatewayFromCli, errorShape, startGatewayClientWhenEventLoopReady } from "openclaw/plugin-sdk/gateway-runtime";
6
6
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
7
- import { asRecord, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeOptionalTrimmedStringList, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { asRecord, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import { Type } from "typebox";
9
9
  import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
10
- import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentConsultWorkingResponse, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceBridgeSession, createRealtimeVoiceOutputActivityTracker, createTalkSessionController, extendRealtimeVoiceOutputEchoSuppression, getRealtimeVoiceBridgeEventHealth, getRealtimeVoiceTranscriptHealth, isLikelyRealtimeVoiceAssistantEchoTranscript, mulawToPcm, recordRealtimeVoiceBridgeEvent, recordRealtimeVoiceTranscript, recordTalkObservabilityEvent, resamplePcm, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultToolPolicy, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow } from "openclaw/plugin-sdk/realtime-voice";
10
+ import { addTimerTimeoutGraceMs } from "openclaw/plugin-sdk/number-runtime";
11
+ import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_G711_ULAW_8KHZ, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentConsultWorkingResponse, consultRealtimeVoiceAgent, convertPcmToMulaw8k, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceBridgeSession, createRealtimeVoiceOutputActivityTracker, createTalkSessionController, extendRealtimeVoiceOutputEchoSuppression, getRealtimeVoiceBridgeEventHealth, getRealtimeVoiceTranscriptHealth, isLikelyRealtimeVoiceAssistantEchoTranscript, mulawToPcm, recordRealtimeVoiceBridgeEvent, recordRealtimeVoiceTranscript, recordTalkObservabilityEvent, resamplePcm, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceAgentConsultToolsAllow } from "openclaw/plugin-sdk/realtime-voice";
11
12
  import { spawn, spawnSync } from "node:child_process";
12
13
  import { randomUUID } from "node:crypto";
13
14
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -17,328 +18,6 @@ import os from "node:os";
17
18
  import path from "node:path";
18
19
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
19
20
  import { getRealtimeTranscriptionProvider, listRealtimeTranscriptionProviders } from "openclaw/plugin-sdk/realtime-transcription";
20
- //#region extensions/google-meet/src/config.ts
21
- const SOX_DEFAULT_BUFFER_BYTES = 8192;
22
- const SOX_MIN_BUFFER_BYTES = 17;
23
- const DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES = SOX_DEFAULT_BUFFER_BYTES / 2;
24
- const PLAIN_DECIMAL_NUMBER_RE = /^\d+(?:\.\d+)?$/;
25
- function withSoxBuffer(command, bufferBytes) {
26
- return [
27
- command[0] ?? "sox",
28
- "-q",
29
- "--buffer",
30
- String(bufferBytes),
31
- ...command.slice(2)
32
- ];
33
- }
34
- const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE = [
35
- "sox",
36
- "-q",
37
- "-t",
38
- "coreaudio",
39
- "BlackHole 2ch",
40
- "-t",
41
- "raw",
42
- "-r",
43
- "24000",
44
- "-c",
45
- "1",
46
- "-e",
47
- "signed-integer",
48
- "-b",
49
- "16",
50
- "-L",
51
- "-"
52
- ];
53
- const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE = [
54
- "sox",
55
- "-q",
56
- "-t",
57
- "raw",
58
- "-r",
59
- "24000",
60
- "-c",
61
- "1",
62
- "-e",
63
- "signed-integer",
64
- "-b",
65
- "16",
66
- "-L",
67
- "-",
68
- "-t",
69
- "coreaudio",
70
- "BlackHole 2ch"
71
- ];
72
- const LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE = [
73
- "rec",
74
- "-q",
75
- "-t",
76
- "raw",
77
- "-r",
78
- "8000",
79
- "-c",
80
- "1",
81
- "-e",
82
- "mu-law",
83
- "-b",
84
- "8",
85
- "-"
86
- ];
87
- const LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE = [
88
- "play",
89
- "-q",
90
- "-t",
91
- "raw",
92
- "-r",
93
- "8000",
94
- "-c",
95
- "1",
96
- "-e",
97
- "mu-law",
98
- "-b",
99
- "8",
100
- "-"
101
- ];
102
- const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND = withSoxBuffer(DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE, DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES);
103
- const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = withSoxBuffer(DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE, DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES);
104
- const DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT = "pcm16-24khz";
105
- const DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD = 650;
106
- const DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD = 2500;
107
- const DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS = 900;
108
- const DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS = `You are joining a private Google Meet as an OpenClaw voice transport. Keep spoken replies brief and natural. In agent mode, wait for OpenClaw consult results and speak them exactly. In bidi mode, answer directly and call ${REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME} for deeper reasoning, current information, or tools.`;
109
- const DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE = "Say exactly: I'm here and listening.";
110
- const DEFAULT_GOOGLE_MEET_CONFIG = {
111
- enabled: true,
112
- defaults: {},
113
- preview: { enrollmentAcknowledged: false },
114
- defaultTransport: "chrome",
115
- defaultMode: "agent",
116
- chrome: {
117
- audioBackend: "blackhole-2ch",
118
- audioFormat: DEFAULT_GOOGLE_MEET_CHROME_AUDIO_FORMAT,
119
- audioBufferBytes: DEFAULT_GOOGLE_MEET_AUDIO_BUFFER_BYTES,
120
- launch: true,
121
- guestName: "OpenClaw Agent",
122
- reuseExistingTab: true,
123
- autoJoin: true,
124
- joinTimeoutMs: 3e4,
125
- waitForInCallMs: 2e4,
126
- audioInputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND],
127
- audioOutputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND],
128
- bargeInRmsThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_RMS_THRESHOLD,
129
- bargeInPeakThreshold: DEFAULT_GOOGLE_MEET_BARGE_IN_PEAK_THRESHOLD,
130
- bargeInCooldownMs: DEFAULT_GOOGLE_MEET_BARGE_IN_COOLDOWN_MS
131
- },
132
- chromeNode: {},
133
- twilio: {},
134
- voiceCall: {
135
- enabled: true,
136
- requestTimeoutMs: 3e4,
137
- dtmfDelayMs: 12e3,
138
- postDtmfSpeechDelayMs: 5e3
139
- },
140
- realtime: {
141
- strategy: "agent",
142
- provider: "openai",
143
- transcriptionProvider: "openai",
144
- instructions: DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS,
145
- introMessage: DEFAULT_GOOGLE_MEET_REALTIME_INTRO_MESSAGE,
146
- toolPolicy: "safe-read-only",
147
- providers: {}
148
- },
149
- oauth: {},
150
- auth: { provider: "google-oauth" }
151
- };
152
- const GOOGLE_MEET_CLIENT_ID_KEYS = ["OPENCLAW_GOOGLE_MEET_CLIENT_ID", "GOOGLE_MEET_CLIENT_ID"];
153
- const GOOGLE_MEET_CLIENT_SECRET_KEYS = ["OPENCLAW_GOOGLE_MEET_CLIENT_SECRET", "GOOGLE_MEET_CLIENT_SECRET"];
154
- const GOOGLE_MEET_REFRESH_TOKEN_KEYS = ["OPENCLAW_GOOGLE_MEET_REFRESH_TOKEN", "GOOGLE_MEET_REFRESH_TOKEN"];
155
- const GOOGLE_MEET_ACCESS_TOKEN_KEYS = ["OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN", "GOOGLE_MEET_ACCESS_TOKEN"];
156
- const GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS = ["OPENCLAW_GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT", "GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT"];
157
- const GOOGLE_MEET_DEFAULT_MEETING_KEYS = ["OPENCLAW_GOOGLE_MEET_DEFAULT_MEETING", "GOOGLE_MEET_DEFAULT_MEETING"];
158
- const GOOGLE_MEET_PREVIEW_ACK_KEYS = ["OPENCLAW_GOOGLE_MEET_PREVIEW_ACK", "GOOGLE_MEET_PREVIEW_ACK"];
159
- function resolveBoolean(value, fallback) {
160
- return typeof value === "boolean" ? value : fallback;
161
- }
162
- function resolveNumber(value, fallback) {
163
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
164
- }
165
- function resolveOptionalNumber(value) {
166
- if (typeof value === "number" && Number.isFinite(value)) return value;
167
- if (typeof value === "string" && value.trim()) {
168
- const trimmed = value.trim();
169
- const parsed = PLAIN_DECIMAL_NUMBER_RE.test(trimmed) ? Number(trimmed) : NaN;
170
- return Number.isFinite(parsed) ? parsed : void 0;
171
- }
172
- }
173
- function readEnvString(env, keys) {
174
- for (const key of keys) {
175
- const value = normalizeOptionalString(env[key]);
176
- if (value) return value;
177
- }
178
- }
179
- function normalizeStringAllowEmpty(value) {
180
- return typeof value === "string" ? value.trim() : void 0;
181
- }
182
- function readEnvBoolean(env, keys) {
183
- const normalized = normalizeOptionalLowercaseString(readEnvString(env, keys));
184
- if (!normalized) return;
185
- if ([
186
- "1",
187
- "true",
188
- "yes",
189
- "on"
190
- ].includes(normalized)) return true;
191
- if ([
192
- "0",
193
- "false",
194
- "no",
195
- "off"
196
- ].includes(normalized)) return false;
197
- }
198
- function readEnvNumber(env, keys) {
199
- return resolveOptionalNumber(readEnvString(env, keys));
200
- }
201
- function resolveStringArray(value) {
202
- return normalizeOptionalTrimmedStringList(value);
203
- }
204
- function resolveProvidersConfig(value) {
205
- const raw = asRecord(value);
206
- const providers = {};
207
- for (const [key, entry] of Object.entries(raw)) {
208
- const providerId = normalizeOptionalLowercaseString(key);
209
- if (!providerId) continue;
210
- providers[providerId] = asRecord(entry);
211
- }
212
- return providers;
213
- }
214
- function resolveTransport$1(value, fallback) {
215
- const normalized = normalizeOptionalLowercaseString(value);
216
- return normalized === "chrome" || normalized === "chrome-node" || normalized === "twilio" ? normalized : fallback;
217
- }
218
- function resolveMode$1(value, fallback) {
219
- const normalized = normalizeOptionalLowercaseString(value);
220
- if (normalized === "realtime") return "agent";
221
- return normalized === "agent" || normalized === "bidi" || normalized === "transcribe" ? normalized : fallback;
222
- }
223
- function resolveRealtimeStrategy(value, fallback) {
224
- const normalized = normalizeOptionalLowercaseString(value);
225
- return normalized === "agent" || normalized === "bidi" ? normalized : fallback;
226
- }
227
- function resolveChromeAudioFormat(value) {
228
- switch (normalizeOptionalString(value)?.toLowerCase().replaceAll("_", "-")) {
229
- case "pcm16-24khz":
230
- case "pcm16-24k":
231
- case "pcm24":
232
- case "pcm": return "pcm16-24khz";
233
- case "g711-ulaw-8khz":
234
- case "g711-ulaw-8k":
235
- case "g711-ulaw":
236
- case "mulaw":
237
- case "mu-law": return "g711-ulaw-8khz";
238
- default: return;
239
- }
240
- }
241
- function resolveAudioBufferBytes(value, fallback) {
242
- const number = resolveNumber(value, fallback);
243
- if (!Number.isFinite(number) || number <= 0) return fallback;
244
- return Math.max(SOX_MIN_BUFFER_BYTES, Math.trunc(number));
245
- }
246
- function defaultAudioInputCommand(format, bufferBytes) {
247
- return withSoxBuffer(format === "g711-ulaw-8khz" ? LEGACY_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE : DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND_BASE, bufferBytes);
248
- }
249
- function defaultAudioOutputCommand(format, bufferBytes) {
250
- return withSoxBuffer(format === "g711-ulaw-8khz" ? LEGACY_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE : DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND_BASE, bufferBytes);
251
- }
252
- function resolveGoogleMeetConfig(input) {
253
- return resolveGoogleMeetConfigWithEnv(input);
254
- }
255
- function resolveGoogleMeetConfigWithEnv(input, env = process.env) {
256
- const raw = asRecord(input);
257
- const defaults = asRecord(raw.defaults);
258
- const preview = asRecord(raw.preview);
259
- const chrome = asRecord(raw.chrome);
260
- const configuredAudioInputCommand = resolveStringArray(chrome.audioInputCommand);
261
- const configuredAudioOutputCommand = resolveStringArray(chrome.audioOutputCommand);
262
- const hasCustomAudioCommand = configuredAudioInputCommand !== void 0 || configuredAudioOutputCommand !== void 0;
263
- const audioFormat = resolveChromeAudioFormat(chrome.audioFormat) ?? (hasCustomAudioCommand ? "g711-ulaw-8khz" : DEFAULT_GOOGLE_MEET_CONFIG.chrome.audioFormat);
264
- const audioBufferBytes = resolveAudioBufferBytes(chrome.audioBufferBytes, DEFAULT_GOOGLE_MEET_CONFIG.chrome.audioBufferBytes);
265
- const chromeNode = asRecord(raw.chromeNode);
266
- const twilio = asRecord(raw.twilio);
267
- const voiceCall = asRecord(raw.voiceCall);
268
- const realtime = asRecord(raw.realtime);
269
- const realtimeProvider = normalizeOptionalString(realtime.provider);
270
- const resolvedRealtimeProvider = realtimeProvider ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.provider;
271
- const oauth = asRecord(raw.oauth);
272
- const auth = asRecord(raw.auth);
273
- return {
274
- enabled: resolveBoolean(raw.enabled, DEFAULT_GOOGLE_MEET_CONFIG.enabled),
275
- defaults: { meeting: normalizeOptionalString(defaults.meeting) ?? readEnvString(env, GOOGLE_MEET_DEFAULT_MEETING_KEYS) },
276
- preview: { enrollmentAcknowledged: resolveBoolean(preview.enrollmentAcknowledged, readEnvBoolean(env, GOOGLE_MEET_PREVIEW_ACK_KEYS) ?? DEFAULT_GOOGLE_MEET_CONFIG.preview.enrollmentAcknowledged) },
277
- defaultTransport: resolveTransport$1(raw.defaultTransport, DEFAULT_GOOGLE_MEET_CONFIG.defaultTransport),
278
- defaultMode: resolveMode$1(raw.defaultMode, DEFAULT_GOOGLE_MEET_CONFIG.defaultMode),
279
- chrome: {
280
- audioBackend: "blackhole-2ch",
281
- audioFormat,
282
- audioBufferBytes,
283
- launch: resolveBoolean(chrome.launch, DEFAULT_GOOGLE_MEET_CONFIG.chrome.launch),
284
- browserProfile: normalizeOptionalString(chrome.browserProfile),
285
- guestName: normalizeOptionalString(chrome.guestName) ?? DEFAULT_GOOGLE_MEET_CONFIG.chrome.guestName,
286
- reuseExistingTab: resolveBoolean(chrome.reuseExistingTab, DEFAULT_GOOGLE_MEET_CONFIG.chrome.reuseExistingTab),
287
- autoJoin: resolveBoolean(chrome.autoJoin, DEFAULT_GOOGLE_MEET_CONFIG.chrome.autoJoin),
288
- joinTimeoutMs: resolveNumber(chrome.joinTimeoutMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.joinTimeoutMs),
289
- waitForInCallMs: resolveNumber(chrome.waitForInCallMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.waitForInCallMs),
290
- audioInputCommand: configuredAudioInputCommand ?? defaultAudioInputCommand(audioFormat, audioBufferBytes),
291
- audioOutputCommand: configuredAudioOutputCommand ?? defaultAudioOutputCommand(audioFormat, audioBufferBytes),
292
- bargeInInputCommand: resolveStringArray(chrome.bargeInInputCommand),
293
- bargeInRmsThreshold: resolveNumber(chrome.bargeInRmsThreshold, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInRmsThreshold),
294
- bargeInPeakThreshold: resolveNumber(chrome.bargeInPeakThreshold, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInPeakThreshold),
295
- bargeInCooldownMs: resolveNumber(chrome.bargeInCooldownMs, DEFAULT_GOOGLE_MEET_CONFIG.chrome.bargeInCooldownMs),
296
- audioBridgeCommand: resolveStringArray(chrome.audioBridgeCommand),
297
- audioBridgeHealthCommand: resolveStringArray(chrome.audioBridgeHealthCommand)
298
- },
299
- chromeNode: { node: normalizeOptionalString(chromeNode.node) },
300
- twilio: {
301
- defaultDialInNumber: normalizeOptionalString(twilio.defaultDialInNumber),
302
- defaultPin: normalizeOptionalString(twilio.defaultPin),
303
- defaultDtmfSequence: normalizeOptionalString(twilio.defaultDtmfSequence)
304
- },
305
- voiceCall: {
306
- enabled: resolveBoolean(voiceCall.enabled, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.enabled),
307
- gatewayUrl: normalizeOptionalString(voiceCall.gatewayUrl),
308
- token: normalizeOptionalString(voiceCall.token),
309
- requestTimeoutMs: resolveNumber(voiceCall.requestTimeoutMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.requestTimeoutMs),
310
- dtmfDelayMs: resolveNumber(voiceCall.dtmfDelayMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.dtmfDelayMs),
311
- postDtmfSpeechDelayMs: resolveNumber(voiceCall.postDtmfSpeechDelayMs, DEFAULT_GOOGLE_MEET_CONFIG.voiceCall.postDtmfSpeechDelayMs),
312
- introMessage: normalizeOptionalString(voiceCall.introMessage)
313
- },
314
- realtime: {
315
- strategy: resolveRealtimeStrategy(realtime.strategy, DEFAULT_GOOGLE_MEET_CONFIG.realtime.strategy),
316
- provider: resolvedRealtimeProvider,
317
- transcriptionProvider: normalizeOptionalString(realtime.transcriptionProvider) ?? (realtimeProvider && realtimeProvider !== "google" ? resolvedRealtimeProvider : DEFAULT_GOOGLE_MEET_CONFIG.realtime.transcriptionProvider),
318
- voiceProvider: normalizeOptionalString(realtime.voiceProvider),
319
- model: normalizeOptionalString(realtime.model) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.model,
320
- instructions: normalizeOptionalString(realtime.instructions) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.instructions,
321
- introMessage: normalizeStringAllowEmpty(realtime.introMessage) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.introMessage,
322
- agentId: normalizeOptionalString(realtime.agentId),
323
- toolPolicy: resolveRealtimeVoiceAgentConsultToolPolicy(realtime.toolPolicy, DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy),
324
- providers: resolveProvidersConfig(realtime.providers)
325
- },
326
- oauth: {
327
- clientId: normalizeOptionalString(oauth.clientId) ?? normalizeOptionalString(auth.clientId) ?? readEnvString(env, GOOGLE_MEET_CLIENT_ID_KEYS),
328
- clientSecret: normalizeOptionalString(oauth.clientSecret) ?? normalizeOptionalString(auth.clientSecret) ?? readEnvString(env, GOOGLE_MEET_CLIENT_SECRET_KEYS),
329
- refreshToken: normalizeOptionalString(oauth.refreshToken) ?? readEnvString(env, GOOGLE_MEET_REFRESH_TOKEN_KEYS),
330
- accessToken: normalizeOptionalString(oauth.accessToken) ?? readEnvString(env, GOOGLE_MEET_ACCESS_TOKEN_KEYS),
331
- expiresAt: resolveOptionalNumber(oauth.expiresAt) ?? readEnvNumber(env, GOOGLE_MEET_ACCESS_TOKEN_EXPIRES_AT_KEYS)
332
- },
333
- auth: {
334
- provider: "google-oauth",
335
- clientId: normalizeOptionalString(auth.clientId),
336
- clientSecret: normalizeOptionalString(auth.clientSecret),
337
- tokenPath: normalizeOptionalString(auth.tokenPath)
338
- }
339
- };
340
- }
341
- //#endregion
342
21
  //#region extensions/google-meet/src/transports/chrome-audio-device.ts
343
22
  const GOOGLE_MEET_SYSTEM_PROFILER_COMMAND = "/usr/sbin/system_profiler";
344
23
  function outputMentionsBlackHole2ch(output) {
@@ -1930,20 +1609,73 @@ function normalizeGoogleMeetTtsPromptText(text) {
1930
1609
  if (sayExactly) return sayExactly.replace(/^["']|["']$/g, "").trim() || trimmed;
1931
1610
  return trimmed;
1932
1611
  }
1612
+ function startGoogleMeetNodeAudioInputLoop(params) {
1613
+ let lastInputAt;
1614
+ let lastInputBytes = 0;
1615
+ let suppressedInputBytes = 0;
1616
+ let lastSuppressedInputAt;
1617
+ let consecutiveInputErrors = 0;
1618
+ let lastInputError;
1619
+ (async () => {
1620
+ for (;;) {
1621
+ if (params.isStopped()) break;
1622
+ try {
1623
+ const raw = await params.runtime.nodes.invoke({
1624
+ nodeId: params.nodeId,
1625
+ command: "googlemeet.chrome",
1626
+ params: {
1627
+ action: "pullAudio",
1628
+ bridgeId: params.bridgeId,
1629
+ timeoutMs: 250
1630
+ },
1631
+ timeoutMs: 2e3
1632
+ });
1633
+ const result = asRecord$1(asRecord$1(raw).payload ?? raw);
1634
+ consecutiveInputErrors = 0;
1635
+ lastInputError = void 0;
1636
+ const base64 = readString(result.base64);
1637
+ if (base64) {
1638
+ const audio = Buffer.from(base64, "base64");
1639
+ if (params.isInputSuppressed()) {
1640
+ lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
1641
+ suppressedInputBytes += audio.byteLength;
1642
+ continue;
1643
+ }
1644
+ lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
1645
+ lastInputBytes += audio.byteLength;
1646
+ params.onAudio(audio);
1647
+ }
1648
+ if (result.closed === true) await params.stop();
1649
+ } catch (error) {
1650
+ if (!params.isStopped()) {
1651
+ const message = formatErrorMessage(error);
1652
+ consecutiveInputErrors += 1;
1653
+ lastInputError = message;
1654
+ params.logger.warn(`[google-meet] ${params.logPrefix} audio input failed (${consecutiveInputErrors}/5): ${message}`);
1655
+ if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await params.stop();
1656
+ else await new Promise((resolve) => setTimeout(resolve, 250));
1657
+ }
1658
+ }
1659
+ }
1660
+ })();
1661
+ return { getHealth: () => ({
1662
+ audioInputActive: lastInputBytes > 0,
1663
+ lastInputAt,
1664
+ lastSuppressedInputAt,
1665
+ lastInputBytes,
1666
+ suppressedInputBytes,
1667
+ consecutiveInputErrors,
1668
+ lastInputError
1669
+ }) };
1670
+ }
1933
1671
  async function startNodeAgentAudioBridge(params) {
1934
1672
  let stopped = false;
1935
1673
  let sttSession = null;
1936
1674
  let realtimeReady = false;
1937
- let lastInputAt;
1938
1675
  let lastOutputAt;
1939
- let lastInputBytes = 0;
1940
1676
  const outputActivity = createRealtimeVoiceOutputActivityTracker();
1941
- let suppressedInputBytes = 0;
1942
- let lastSuppressedInputAt;
1943
1677
  let suppressInputUntil = 0;
1944
1678
  let lastOutputPlayableUntilMs = 0;
1945
- let consecutiveInputErrors = 0;
1946
- let lastInputError;
1947
1679
  const resolved = resolveGoogleMeetRealtimeTranscriptionProvider({
1948
1680
  config: params.config,
1949
1681
  fullConfig: params.fullConfig,
@@ -2067,48 +1799,19 @@ async function startNodeAgentAudioBridge(params) {
2067
1799
  });
2068
1800
  await sttSession.connect();
2069
1801
  realtimeReady = true;
2070
- (async () => {
2071
- for (;;) {
2072
- if (stopped) break;
2073
- try {
2074
- const raw = await params.runtime.nodes.invoke({
2075
- nodeId: params.nodeId,
2076
- command: "googlemeet.chrome",
2077
- params: {
2078
- action: "pullAudio",
2079
- bridgeId: params.bridgeId,
2080
- timeoutMs: 250
2081
- },
2082
- timeoutMs: 2e3
2083
- });
2084
- const result = asRecord$1(asRecord$1(raw).payload ?? raw);
2085
- consecutiveInputErrors = 0;
2086
- lastInputError = void 0;
2087
- const base64 = readString(result.base64);
2088
- if (base64) {
2089
- const audio = Buffer.from(base64, "base64");
2090
- if (Date.now() < suppressInputUntil) {
2091
- lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
2092
- suppressedInputBytes += audio.byteLength;
2093
- continue;
2094
- }
2095
- lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
2096
- lastInputBytes += audio.byteLength;
2097
- sttSession?.sendAudio(convertGoogleMeetBridgeAudioForStt(audio, params.config));
2098
- }
2099
- if (result.closed === true) await stop();
2100
- } catch (error) {
2101
- if (!stopped) {
2102
- const message = formatErrorMessage(error);
2103
- consecutiveInputErrors += 1;
2104
- lastInputError = message;
2105
- params.logger.warn(`[google-meet] node agent audio input failed (${consecutiveInputErrors}/5): ${message}`);
2106
- if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await stop();
2107
- else await new Promise((resolve) => setTimeout(resolve, 250));
2108
- }
2109
- }
1802
+ const audioInputLoop = startGoogleMeetNodeAudioInputLoop({
1803
+ runtime: params.runtime,
1804
+ nodeId: params.nodeId,
1805
+ bridgeId: params.bridgeId,
1806
+ logger: params.logger,
1807
+ logPrefix: "node agent",
1808
+ isStopped: () => stopped,
1809
+ stop,
1810
+ isInputSuppressed: () => Date.now() < suppressInputUntil,
1811
+ onAudio: (audio) => {
1812
+ sttSession?.sendAudio(convertGoogleMeetBridgeAudioForStt(audio, params.config));
2110
1813
  }
2111
- })();
1814
+ });
2112
1815
  return {
2113
1816
  type: "node-command-pair",
2114
1817
  providerId: resolved.provider.id,
@@ -2118,17 +1821,11 @@ async function startNodeAgentAudioBridge(params) {
2118
1821
  getHealth: () => ({
2119
1822
  providerConnected: sttSession?.isConnected() ?? false,
2120
1823
  realtimeReady,
2121
- audioInputActive: lastInputBytes > 0,
1824
+ ...audioInputLoop.getHealth(),
2122
1825
  audioOutputActive: outputActivity.isActive(),
2123
- lastInputAt,
2124
1826
  lastOutputAt,
2125
- lastSuppressedInputAt,
2126
- lastInputBytes,
2127
1827
  lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
2128
- suppressedInputBytes,
2129
1828
  ...getGoogleMeetRealtimeTranscriptHealth(transcript),
2130
- consecutiveInputErrors,
2131
- lastInputError,
2132
1829
  bridgeClosed: stopped
2133
1830
  }),
2134
1831
  stop
@@ -2138,17 +1835,11 @@ async function startNodeRealtimeAudioBridge(params) {
2138
1835
  let stopped = false;
2139
1836
  let bridge = null;
2140
1837
  let realtimeReady = false;
2141
- let lastInputAt;
2142
1838
  let lastOutputAt;
2143
1839
  let lastClearAt;
2144
- let lastInputBytes = 0;
2145
1840
  const outputActivity = createRealtimeVoiceOutputActivityTracker();
2146
- let suppressedInputBytes = 0;
2147
- let lastSuppressedInputAt;
2148
1841
  let suppressInputUntil = 0;
2149
1842
  let lastOutputPlayableUntilMs = 0;
2150
- let consecutiveInputErrors = 0;
2151
- let lastInputError;
2152
1843
  let clearCount = 0;
2153
1844
  const resolved = resolveGoogleMeetRealtimeProvider({
2154
1845
  config: params.config,
@@ -2442,53 +2133,24 @@ async function startNodeRealtimeAudioBridge(params) {
2442
2133
  }
2443
2134
  });
2444
2135
  await bridge.connect();
2445
- (async () => {
2446
- for (;;) {
2447
- if (stopped) break;
2448
- try {
2449
- const raw = await params.runtime.nodes.invoke({
2450
- nodeId: params.nodeId,
2451
- command: "googlemeet.chrome",
2452
- params: {
2453
- action: "pullAudio",
2454
- bridgeId: params.bridgeId,
2455
- timeoutMs: 250
2456
- },
2457
- timeoutMs: 2e3
2458
- });
2459
- const result = asRecord$1(asRecord$1(raw).payload ?? raw);
2460
- consecutiveInputErrors = 0;
2461
- lastInputError = void 0;
2462
- const base64 = readString(result.base64);
2463
- if (base64) {
2464
- const audio = Buffer.from(base64, "base64");
2465
- if (Date.now() < suppressInputUntil) {
2466
- lastSuppressedInputAt = (/* @__PURE__ */ new Date()).toISOString();
2467
- suppressedInputBytes += audio.byteLength;
2468
- continue;
2469
- }
2470
- lastInputAt = (/* @__PURE__ */ new Date()).toISOString();
2471
- lastInputBytes += audio.byteLength;
2472
- emitTalkEvent({
2473
- type: "input.audio.delta",
2474
- turnId: ensureTalkTurn(),
2475
- payload: { byteLength: audio.byteLength }
2476
- });
2477
- bridge?.sendAudio(audio);
2478
- }
2479
- if (result.closed === true) await stop();
2480
- } catch (error) {
2481
- if (!stopped) {
2482
- const message = formatErrorMessage(error);
2483
- consecutiveInputErrors += 1;
2484
- lastInputError = message;
2485
- params.logger.warn(`[google-meet] node audio input failed (${consecutiveInputErrors}/5): ${message}`);
2486
- if (consecutiveInputErrors >= 5 || /unknown bridgeId|bridge is not open/i.test(message)) await stop();
2487
- else await new Promise((resolve) => setTimeout(resolve, 250));
2488
- }
2489
- }
2136
+ const audioInputLoop = startGoogleMeetNodeAudioInputLoop({
2137
+ runtime: params.runtime,
2138
+ nodeId: params.nodeId,
2139
+ bridgeId: params.bridgeId,
2140
+ logger: params.logger,
2141
+ logPrefix: "node",
2142
+ isStopped: () => stopped,
2143
+ stop,
2144
+ isInputSuppressed: () => Date.now() < suppressInputUntil,
2145
+ onAudio: (audio) => {
2146
+ emitTalkEvent({
2147
+ type: "input.audio.delta",
2148
+ turnId: ensureTalkTurn(),
2149
+ payload: { byteLength: audio.byteLength }
2150
+ });
2151
+ bridge?.sendAudio(audio);
2490
2152
  }
2491
- })();
2153
+ });
2492
2154
  return {
2493
2155
  type: "node-command-pair",
2494
2156
  providerId: resolved.provider.id,
@@ -2500,20 +2162,14 @@ async function startNodeRealtimeAudioBridge(params) {
2500
2162
  getHealth: () => ({
2501
2163
  providerConnected: bridge?.bridge.isConnected() ?? false,
2502
2164
  realtimeReady,
2503
- audioInputActive: lastInputBytes > 0,
2165
+ ...audioInputLoop.getHealth(),
2504
2166
  audioOutputActive: outputActivity.isActive(),
2505
- lastInputAt,
2506
2167
  lastOutputAt,
2507
- lastSuppressedInputAt,
2508
2168
  lastClearAt,
2509
- lastInputBytes,
2510
2169
  lastOutputBytes: outputActivity.snapshot().sinkAudioBytes,
2511
- suppressedInputBytes,
2512
2170
  ...getGoogleMeetRealtimeTranscriptHealth(transcript),
2513
2171
  ...getGoogleMeetRealtimeEventHealth(realtimeEvents),
2514
2172
  recentTalkEvents: summarizeGoogleMeetTalkEvents(recentTalkEvents),
2515
- consecutiveInputErrors,
2516
- lastInputError,
2517
2173
  clearCount,
2518
2174
  bridgeClosed: stopped
2519
2175
  }),
@@ -2650,7 +2306,7 @@ function parseMeetBrowserStatus(result) {
2650
2306
  async function callLocalBrowserRequest(params) {
2651
2307
  return await chromeTransportDeps.callGatewayFromCli("browser.request", {
2652
2308
  json: true,
2653
- timeout: String(params.timeoutMs + 5e3)
2309
+ timeout: String(resolveBrowserGatewayTimeoutMs(params.timeoutMs))
2654
2310
  }, {
2655
2311
  method: params.method,
2656
2312
  path: params.path,
@@ -2658,6 +2314,9 @@ async function callLocalBrowserRequest(params) {
2658
2314
  timeoutMs: params.timeoutMs
2659
2315
  }, { progress: false });
2660
2316
  }
2317
+ function resolveBrowserGatewayTimeoutMs(timeoutMs) {
2318
+ return addTimerTimeoutGraceMs(timeoutMs) ?? 1;
2319
+ }
2661
2320
  function mergeBrowserNotes(browser, notes) {
2662
2321
  if (!browser || notes.length === 0) return browser;
2663
2322
  return {
@@ -3214,7 +2873,7 @@ async function launchChromeMeetOnNode(params) {
3214
2873
  audioBridgeCommand: params.config.chrome.audioBridgeCommand,
3215
2874
  audioBridgeHealthCommand: params.config.chrome.audioBridgeHealthCommand
3216
2875
  },
3217
- timeoutMs: params.config.chrome.joinTimeoutMs + 5e3
2876
+ timeoutMs: addTimerTimeoutGraceMs(params.config.chrome.joinTimeoutMs) ?? 1
3218
2877
  }));
3219
2878
  if (result.audioBridge?.type === "node-command-pair") {
3220
2879
  if (!result.bridgeId) throw new Error("Google Meet node did not return an audio bridge id.");
@@ -4082,6 +3741,16 @@ var GoogleMeetRuntime = class {
4082
3741
  };
4083
3742
  //#endregion
4084
3743
  //#region extensions/google-meet/index.ts
3744
+ let googleMeetCreateModulePromise = null;
3745
+ let googleMeetCliModulePromise = null;
3746
+ const loadGoogleMeetCreateModule = async () => {
3747
+ googleMeetCreateModulePromise ??= import("./create-BzkoPzD2.js");
3748
+ return await googleMeetCreateModulePromise;
3749
+ };
3750
+ const loadGoogleMeetCliModule = async () => {
3751
+ googleMeetCliModulePromise ??= import("./cli-CORe6B8A.js");
3752
+ return await googleMeetCliModulePromise;
3753
+ };
4085
3754
  const googleMeetConfigSchema = {
4086
3755
  parse(value) {
4087
3756
  return resolveGoogleMeetConfig(value);
@@ -4404,7 +4073,8 @@ const testing = {
4404
4073
  setPlatformForTests(next) {
4405
4074
  googleMeetToolDeps.platform = next ?? (() => process.platform);
4406
4075
  },
4407
- isGoogleMeetAgentToolActionUnsupportedOnHost
4076
+ isGoogleMeetAgentToolActionUnsupportedOnHost,
4077
+ resolveGoogleMeetGatewayOperationTimeoutMs
4408
4078
  };
4409
4079
  function googleMeetGatewayMethodForToolAction(action) {
4410
4080
  switch (action) {
@@ -4428,9 +4098,6 @@ function assertGoogleMeetAgentToolActionSupported(params) {
4428
4098
  if (!isGoogleMeetAgentToolActionUnsupportedOnHost(params)) return;
4429
4099
  throw new Error("Google Meet local Chrome talk-back audio is macOS-only. On this host, use mode: transcribe, transport: twilio, or transport: chrome-node backed by a macOS node.");
4430
4100
  }
4431
- function resolveGoogleMeetToolGatewayTimeoutMs(config) {
4432
- return Math.max(6e4, config.chrome.joinTimeoutMs + 3e4, config.voiceCall.requestTimeoutMs + 1e4);
4433
- }
4434
4101
  function readGatewayErrorDetails(err) {
4435
4102
  if (!err || typeof err !== "object" || !("details" in err)) return;
4436
4103
  return err.details;
@@ -4439,7 +4106,7 @@ async function callGoogleMeetGatewayFromTool(params) {
4439
4106
  try {
4440
4107
  return await googleMeetToolDeps.callGatewayFromCli(googleMeetGatewayMethodForToolAction(params.action), {
4441
4108
  json: true,
4442
- timeout: String(resolveGoogleMeetToolGatewayTimeoutMs(params.config))
4109
+ timeout: String(resolveGoogleMeetGatewayOperationTimeoutMs(params.config))
4443
4110
  }, params.raw, { progress: false });
4444
4111
  } catch (err) {
4445
4112
  const details = readGatewayErrorDetails(err);
@@ -4448,13 +4115,13 @@ async function callGoogleMeetGatewayFromTool(params) {
4448
4115
  }
4449
4116
  }
4450
4117
  async function createMeetFromParams(params) {
4451
- return (await import("./create-OMWgK9on.js")).createMeetFromParams(params);
4118
+ return (await loadGoogleMeetCreateModule()).createMeetFromParams(params);
4452
4119
  }
4453
4120
  async function createAndJoinMeetFromParams(params) {
4454
- return (await import("./create-OMWgK9on.js")).createAndJoinMeetFromParams(params);
4121
+ return (await loadGoogleMeetCreateModule()).createAndJoinMeetFromParams(params);
4455
4122
  }
4456
4123
  async function resolveGoogleMeetTokenFromParams(config, raw) {
4457
- const { resolveGoogleMeetAccessToken } = await import("./oauth-B3tz8JcD.js");
4124
+ const { resolveGoogleMeetAccessToken } = await import("./oauth--J53aI66.js");
4458
4125
  return resolveGoogleMeetAccessToken({
4459
4126
  clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
4460
4127
  clientSecret: normalizeOptionalString(raw.clientSecret) ?? config.oauth.clientSecret,
@@ -4543,7 +4210,7 @@ async function exportGoogleMeetBundleFromParams(config, raw) {
4543
4210
  lateAfterMinutes: resolved.lateAfterMinutes,
4544
4211
  earlyBeforeMinutes: resolved.earlyBeforeMinutes
4545
4212
  })]);
4546
- const { buildGoogleMeetExportManifest, googleMeetExportFileNames, writeMeetExportBundle } = await import("./cli-Cc7dWaK8.js");
4213
+ const { buildGoogleMeetExportManifest, googleMeetExportFileNames, writeMeetExportBundle } = await loadGoogleMeetCliModule();
4547
4214
  const calendarId = normalizeOptionalString(raw.calendarId);
4548
4215
  const request = {
4549
4216
  ...resolved.meeting ? { meeting: resolved.meeting } : {},
@@ -4959,7 +4626,7 @@ var google_meet_default = definePluginEntry({
4959
4626
  handle: handleGoogleMeetNodeHostCommand
4960
4627
  });
4961
4628
  api.registerCli(async ({ program }) => {
4962
- const { registerGoogleMeetCli } = await import("./cli-Cc7dWaK8.js");
4629
+ const { registerGoogleMeetCli } = await loadGoogleMeetCliModule();
4963
4630
  registerGoogleMeetCli({
4964
4631
  program,
4965
4632
  config,