@openclaw/google-meet 2026.7.1 → 2026.7.2-beta.2
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/{chrome-create-BhgM0jQO.js → chrome-create-DM1XJCOH.js} +87 -61
- package/dist/{cli-D364Yrym.js → cli-SodPSOFX.js} +47 -90
- package/dist/{config-DDYRr7-P.js → config-BeoHH_z5.js} +49 -13
- package/dist/{create-CkkaRlbI.js → create-CWcFI0ub.js} +2 -2
- package/dist/index.js +1236 -361
- package/dist/{oauth-75iPDzIn.js → oauth-DOfMKlGp.js} +3 -1
- package/node_modules/core-util-is/LICENSE +19 -0
- package/node_modules/core-util-is/README.md +3 -0
- package/node_modules/core-util-is/lib/util.js +107 -0
- package/node_modules/core-util-is/package.json +38 -0
- package/node_modules/immediate/LICENSE.txt +20 -0
- package/node_modules/immediate/README.md +93 -0
- package/node_modules/immediate/dist/immediate.js +75 -0
- package/node_modules/immediate/dist/immediate.min.js +1 -0
- package/node_modules/immediate/lib/browser.js +69 -0
- package/node_modules/immediate/lib/index.js +73 -0
- package/node_modules/immediate/package.json +42 -0
- package/node_modules/inherits/LICENSE +16 -0
- package/node_modules/inherits/README.md +42 -0
- package/node_modules/inherits/inherits.js +9 -0
- package/node_modules/inherits/inherits_browser.js +27 -0
- package/node_modules/inherits/package.json +29 -0
- package/node_modules/isarray/.npmignore +1 -0
- package/node_modules/isarray/.travis.yml +4 -0
- package/node_modules/isarray/Makefile +6 -0
- package/node_modules/isarray/README.md +60 -0
- package/node_modules/isarray/component.json +19 -0
- package/node_modules/isarray/index.js +5 -0
- package/node_modules/isarray/package.json +45 -0
- package/node_modules/isarray/test.js +20 -0
- package/node_modules/jszip/.codeclimate.yml +16 -0
- package/node_modules/jszip/.editorconfig +8 -0
- package/node_modules/jszip/.eslintrc.js +43 -0
- package/node_modules/jszip/.github/workflows/pr.yaml +58 -0
- package/node_modules/jszip/.jekyll-metadata +0 -0
- package/node_modules/jszip/.travis.yml +17 -0
- package/node_modules/jszip/CHANGES.md +204 -0
- package/node_modules/jszip/LICENSE.markdown +651 -0
- package/node_modules/jszip/README.markdown +33 -0
- package/node_modules/jszip/deps.js +37 -0
- package/node_modules/jszip/dist/jszip.js +11577 -0
- package/node_modules/jszip/dist/jszip.min.js +13 -0
- package/node_modules/jszip/graph.svg +601 -0
- package/node_modules/jszip/index.d.ts +330 -0
- package/node_modules/jszip/lib/base64.js +106 -0
- package/node_modules/jszip/lib/compressedObject.js +74 -0
- package/node_modules/jszip/lib/compressions.js +14 -0
- package/node_modules/jszip/lib/crc32.js +77 -0
- package/node_modules/jszip/lib/defaults.js +11 -0
- package/node_modules/jszip/lib/external.js +18 -0
- package/node_modules/jszip/lib/flate.js +85 -0
- package/node_modules/jszip/lib/generate/ZipFileWorker.js +539 -0
- package/node_modules/jszip/lib/generate/index.js +57 -0
- package/node_modules/jszip/lib/index.js +55 -0
- package/node_modules/jszip/lib/license_header.js +11 -0
- package/node_modules/jszip/lib/load.js +88 -0
- package/node_modules/jszip/lib/nodejs/NodejsStreamInputAdapter.js +74 -0
- package/node_modules/jszip/lib/nodejs/NodejsStreamOutputAdapter.js +42 -0
- package/node_modules/jszip/lib/nodejsUtils.js +57 -0
- package/node_modules/jszip/lib/object.js +384 -0
- package/node_modules/jszip/lib/readable-stream-browser.js +10 -0
- package/node_modules/jszip/lib/reader/ArrayReader.js +57 -0
- package/node_modules/jszip/lib/reader/DataReader.js +116 -0
- package/node_modules/jszip/lib/reader/NodeBufferReader.js +19 -0
- package/node_modules/jszip/lib/reader/StringReader.js +38 -0
- package/node_modules/jszip/lib/reader/Uint8ArrayReader.js +22 -0
- package/node_modules/jszip/lib/reader/readerFor.js +28 -0
- package/node_modules/jszip/lib/signature.js +7 -0
- package/node_modules/jszip/lib/stream/ConvertWorker.js +26 -0
- package/node_modules/jszip/lib/stream/Crc32Probe.js +24 -0
- package/node_modules/jszip/lib/stream/DataLengthProbe.js +29 -0
- package/node_modules/jszip/lib/stream/DataWorker.js +116 -0
- package/node_modules/jszip/lib/stream/GenericWorker.js +263 -0
- package/node_modules/jszip/lib/stream/StreamHelper.js +214 -0
- package/node_modules/jszip/lib/support.js +38 -0
- package/node_modules/jszip/lib/utf8.js +275 -0
- package/node_modules/jszip/lib/utils.js +501 -0
- package/node_modules/jszip/lib/zipEntries.js +261 -0
- package/node_modules/jszip/lib/zipEntry.js +293 -0
- package/node_modules/jszip/lib/zipObject.js +133 -0
- package/node_modules/jszip/package.json +67 -0
- package/node_modules/jszip/sponsors.md +21 -0
- package/node_modules/jszip/tsconfig.json +101 -0
- package/node_modules/jszip/vendor/FileSaver.js +247 -0
- package/node_modules/lie/README.md +62 -0
- package/node_modules/lie/dist/lie.js +350 -0
- package/node_modules/lie/dist/lie.min.js +1 -0
- package/node_modules/lie/dist/lie.polyfill.js +358 -0
- package/node_modules/lie/dist/lie.polyfill.min.js +1 -0
- package/node_modules/lie/lib/browser.js +273 -0
- package/node_modules/lie/lib/index.js +298 -0
- package/node_modules/lie/license.md +7 -0
- package/node_modules/lie/lie.d.ts +244 -0
- package/node_modules/lie/package.json +69 -0
- package/node_modules/lie/polyfill.js +4 -0
- package/node_modules/pako/CHANGELOG.md +164 -0
- package/node_modules/pako/LICENSE +21 -0
- package/node_modules/pako/README.md +191 -0
- package/node_modules/pako/dist/pako.js +6818 -0
- package/node_modules/pako/dist/pako.min.js +1 -0
- package/node_modules/pako/dist/pako_deflate.js +3997 -0
- package/node_modules/pako/dist/pako_deflate.min.js +1 -0
- package/node_modules/pako/dist/pako_inflate.js +3300 -0
- package/node_modules/pako/dist/pako_inflate.min.js +1 -0
- package/node_modules/pako/index.js +14 -0
- package/node_modules/pako/lib/deflate.js +400 -0
- package/node_modules/pako/lib/inflate.js +423 -0
- package/node_modules/pako/lib/utils/common.js +105 -0
- package/node_modules/pako/lib/utils/strings.js +187 -0
- package/node_modules/pako/lib/zlib/README +59 -0
- package/node_modules/pako/lib/zlib/adler32.js +51 -0
- package/node_modules/pako/lib/zlib/constants.js +68 -0
- package/node_modules/pako/lib/zlib/crc32.js +59 -0
- package/node_modules/pako/lib/zlib/deflate.js +1874 -0
- package/node_modules/pako/lib/zlib/gzheader.js +58 -0
- package/node_modules/pako/lib/zlib/inffast.js +345 -0
- package/node_modules/pako/lib/zlib/inflate.js +1556 -0
- package/node_modules/pako/lib/zlib/inftrees.js +343 -0
- package/node_modules/pako/lib/zlib/messages.js +32 -0
- package/node_modules/pako/lib/zlib/trees.js +1222 -0
- package/node_modules/pako/lib/zlib/zstream.js +47 -0
- package/node_modules/pako/package.json +44 -0
- package/node_modules/parse-ms/index.d.ts +30 -0
- package/node_modules/parse-ms/index.js +45 -0
- package/node_modules/parse-ms/license +9 -0
- package/node_modules/parse-ms/package.json +47 -0
- package/node_modules/parse-ms/readme.md +46 -0
- package/node_modules/pretty-ms/index.d.ts +157 -0
- package/node_modules/pretty-ms/index.js +149 -0
- package/node_modules/pretty-ms/license +9 -0
- package/node_modules/pretty-ms/package.json +55 -0
- package/node_modules/pretty-ms/readme.md +179 -0
- package/node_modules/process-nextick-args/index.js +45 -0
- package/node_modules/process-nextick-args/license.md +19 -0
- package/node_modules/process-nextick-args/package.json +25 -0
- package/node_modules/process-nextick-args/readme.md +18 -0
- package/node_modules/readable-stream/.travis.yml +34 -0
- package/node_modules/readable-stream/CONTRIBUTING.md +38 -0
- package/node_modules/readable-stream/GOVERNANCE.md +136 -0
- package/node_modules/readable-stream/LICENSE +47 -0
- package/node_modules/readable-stream/README.md +58 -0
- package/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md +60 -0
- package/node_modules/readable-stream/duplex-browser.js +1 -0
- package/node_modules/readable-stream/duplex.js +1 -0
- package/node_modules/readable-stream/lib/_stream_duplex.js +131 -0
- package/node_modules/readable-stream/lib/_stream_passthrough.js +47 -0
- package/node_modules/readable-stream/lib/_stream_readable.js +1019 -0
- package/node_modules/readable-stream/lib/_stream_transform.js +214 -0
- package/node_modules/readable-stream/lib/_stream_writable.js +685 -0
- package/node_modules/readable-stream/lib/internal/streams/BufferList.js +78 -0
- package/node_modules/readable-stream/lib/internal/streams/destroy.js +84 -0
- package/node_modules/readable-stream/lib/internal/streams/stream-browser.js +1 -0
- package/node_modules/readable-stream/lib/internal/streams/stream.js +1 -0
- package/node_modules/readable-stream/package.json +52 -0
- package/node_modules/readable-stream/passthrough.js +1 -0
- package/node_modules/readable-stream/readable-browser.js +7 -0
- package/node_modules/readable-stream/readable.js +19 -0
- package/node_modules/readable-stream/transform.js +1 -0
- package/node_modules/readable-stream/writable-browser.js +1 -0
- package/node_modules/readable-stream/writable.js +8 -0
- package/node_modules/safe-buffer/LICENSE +21 -0
- package/node_modules/safe-buffer/README.md +584 -0
- package/node_modules/safe-buffer/index.d.ts +187 -0
- package/node_modules/safe-buffer/index.js +62 -0
- package/node_modules/safe-buffer/package.json +37 -0
- package/node_modules/{commander/LICENSE → setimmediate/LICENSE.txt} +8 -10
- package/node_modules/setimmediate/package.json +30 -0
- package/node_modules/setimmediate/setImmediate.js +186 -0
- package/node_modules/string_decoder/.travis.yml +50 -0
- package/node_modules/string_decoder/LICENSE +48 -0
- package/node_modules/string_decoder/README.md +47 -0
- package/node_modules/string_decoder/lib/string_decoder.js +296 -0
- package/node_modules/string_decoder/package.json +31 -0
- package/node_modules/util-deprecate/History.md +16 -0
- package/node_modules/util-deprecate/LICENSE +24 -0
- package/node_modules/util-deprecate/README.md +53 -0
- package/node_modules/util-deprecate/browser.js +67 -0
- package/node_modules/util-deprecate/node.js +6 -0
- package/node_modules/util-deprecate/package.json +27 -0
- package/npm-shrinkwrap.json +127 -9
- package/openclaw.plugin.json +1 -1
- package/package.json +8 -6
- package/node_modules/commander/Readme.md +0 -1172
- package/node_modules/commander/index.js +0 -21
- package/node_modules/commander/lib/argument.js +0 -147
- package/node_modules/commander/lib/command.js +0 -2790
- package/node_modules/commander/lib/error.js +0 -36
- package/node_modules/commander/lib/help.js +0 -731
- package/node_modules/commander/lib/option.js +0 -377
- package/node_modules/commander/lib/suggestSimilar.js +0 -99
- package/node_modules/commander/package-support.json +0 -19
- package/node_modules/commander/package.json +0 -64
- package/node_modules/commander/typings/index.d.ts +0 -1113
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
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-
|
|
2
|
-
import { _ as
|
|
1
|
+
import { a as buildGoogleMeetCalendarDayWindow, c as normalizeMeetUrl, 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-BeoHH_z5.js";
|
|
2
|
+
import { _ as fetchGoogleMeetSpace, a as forceMeetEnglishUi, c as normalizeMeetUrlForReuse, d as resolveChromeNodeInfo, f as buildGoogleMeetPreflightReport, g as fetchGoogleMeetAttendance, h as fetchGoogleMeetArtifacts, i as callBrowserProxyOnNode, l as readBrowserTab, m as endGoogleMeetActiveConference, n as isGoogleMeetBrowserManualActionError, o as isEnglishMeetTab, r as asBrowserTabs, s as isSameMeetUrlForReuse, t as createMeetWithBrowserProxyOnNode, u as resolveChromeNode, v as fetchLatestGoogleMeetConferenceRecord } from "./chrome-create-DM1XJCOH.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 { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
7
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
8
|
-
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
8
|
+
import { normalizeAgentId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
|
9
9
|
import { asRecord, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
10
10
|
import { jsonResult } from "openclaw/plugin-sdk/tool-results";
|
|
11
11
|
import { Type } from "typebox";
|
|
@@ -20,6 +20,7 @@ import fs from "node:fs";
|
|
|
20
20
|
import os from "node:os";
|
|
21
21
|
import path from "node:path";
|
|
22
22
|
import { getRealtimeTranscriptionProvider, listRealtimeTranscriptionProviders } from "openclaw/plugin-sdk/realtime-transcription";
|
|
23
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
23
24
|
//#region extensions/google-meet/src/transports/chrome-audio-device.ts
|
|
24
25
|
const GOOGLE_MEET_SYSTEM_PROFILER_COMMAND = "/usr/sbin/system_profiler";
|
|
25
26
|
function outputMentionsBlackHole2ch(output) {
|
|
@@ -77,23 +78,21 @@ function wake(session) {
|
|
|
77
78
|
for (const waiter of waiters) waiter();
|
|
78
79
|
}
|
|
79
80
|
function stopSession(session) {
|
|
80
|
-
|
|
81
|
+
if (session.closed) return;
|
|
81
82
|
session.closed = true;
|
|
82
|
-
session.closedAt
|
|
83
|
+
session.closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
83
84
|
terminateChild(session.input);
|
|
84
85
|
terminateChild(session.output);
|
|
85
|
-
|
|
86
|
+
wake(session);
|
|
86
87
|
}
|
|
87
88
|
function attachOutputProcessHandlers(session, outputProcess) {
|
|
88
|
-
|
|
89
|
+
const stopIfCurrent = () => {
|
|
89
90
|
if (session.output === outputProcess) stopSession(session);
|
|
90
|
-
}
|
|
91
|
-
outputProcess.on("
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
outputProcess.
|
|
95
|
-
if (session.output === outputProcess) stopSession(session);
|
|
96
|
-
});
|
|
91
|
+
};
|
|
92
|
+
outputProcess.on("exit", stopIfCurrent);
|
|
93
|
+
outputProcess.on("error", stopIfCurrent);
|
|
94
|
+
outputProcess.stdin?.on("error", stopIfCurrent);
|
|
95
|
+
outputProcess.stderr?.on("error", stopIfCurrent);
|
|
97
96
|
}
|
|
98
97
|
function startOutputProcess(command) {
|
|
99
98
|
return spawn(command.command, command.args, { stdio: [
|
|
@@ -134,9 +133,12 @@ function startCommandPair(params) {
|
|
|
134
133
|
if (session.chunks.length > 200) session.chunks.splice(0, session.chunks.length - 200);
|
|
135
134
|
wake(session);
|
|
136
135
|
});
|
|
137
|
-
|
|
136
|
+
const stop = () => stopSession(session);
|
|
137
|
+
inputProcess.on("exit", stop);
|
|
138
|
+
inputProcess.on("error", stop);
|
|
139
|
+
inputProcess.stdout?.on("error", stop);
|
|
140
|
+
inputProcess.stderr?.on("error", stop);
|
|
138
141
|
attachOutputProcessHandlers(session, outputProcess);
|
|
139
|
-
inputProcess.on("error", () => stopSession(session));
|
|
140
142
|
sessions.set(session.id, session);
|
|
141
143
|
return session;
|
|
142
144
|
}
|
|
@@ -211,8 +213,7 @@ function clearAudio(params) {
|
|
|
211
213
|
};
|
|
212
214
|
}
|
|
213
215
|
function startChrome(params) {
|
|
214
|
-
const url =
|
|
215
|
-
if (!url) throw new Error("url required");
|
|
216
|
+
const url = normalizeMeetUrl(params.url);
|
|
216
217
|
const timeoutMs = readNumber(params.joinTimeoutMs, 3e4);
|
|
217
218
|
const mode = readString$2(params.mode);
|
|
218
219
|
let bridgeId;
|
|
@@ -399,6 +400,179 @@ async function handleGoogleMeetNodeHostCommand(paramsJSON) {
|
|
|
399
400
|
return JSON.stringify(result);
|
|
400
401
|
}
|
|
401
402
|
//#endregion
|
|
403
|
+
//#region extensions/google-meet/src/node-invoke-policy.ts
|
|
404
|
+
const GOOGLE_MEET_CHROME_NODE_COMMAND = "googlemeet.chrome";
|
|
405
|
+
const START_MODES = /* @__PURE__ */ new Set([
|
|
406
|
+
"agent",
|
|
407
|
+
"bidi",
|
|
408
|
+
"realtime",
|
|
409
|
+
"transcribe"
|
|
410
|
+
]);
|
|
411
|
+
function asRecord$2(value) {
|
|
412
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
413
|
+
}
|
|
414
|
+
function readString$1(value) {
|
|
415
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
416
|
+
}
|
|
417
|
+
function readPositiveNumber(value) {
|
|
418
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
419
|
+
}
|
|
420
|
+
function copyCommand(command) {
|
|
421
|
+
return command && command.length > 0 ? [...command] : void 0;
|
|
422
|
+
}
|
|
423
|
+
function denied(message, code = "GOOGLE_MEET_NODE_POLICY_DENIED") {
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
code,
|
|
427
|
+
message
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function approved(params) {
|
|
431
|
+
return {
|
|
432
|
+
approved: true,
|
|
433
|
+
params
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function buildStartParams(params, config) {
|
|
437
|
+
let url;
|
|
438
|
+
try {
|
|
439
|
+
url = normalizeMeetUrl(params.url);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
approved: false,
|
|
443
|
+
result: denied(error instanceof Error ? error.message : "googlemeet.chrome start requires url")
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const mode = readString$1(params.mode);
|
|
447
|
+
if (mode && !START_MODES.has(mode)) return {
|
|
448
|
+
approved: false,
|
|
449
|
+
result: denied(`googlemeet.chrome start mode is unsupported: ${mode}`)
|
|
450
|
+
};
|
|
451
|
+
const startParams = {
|
|
452
|
+
action: "start",
|
|
453
|
+
url,
|
|
454
|
+
launch: params.launch === false ? false : config.chrome.launch,
|
|
455
|
+
browserProfile: config.chrome.browserProfile,
|
|
456
|
+
joinTimeoutMs: config.chrome.joinTimeoutMs
|
|
457
|
+
};
|
|
458
|
+
if (mode) startParams.mode = mode;
|
|
459
|
+
const audioInputCommand = copyCommand(config.chrome.audioInputCommand);
|
|
460
|
+
if (audioInputCommand) startParams.audioInputCommand = audioInputCommand;
|
|
461
|
+
const audioOutputCommand = copyCommand(config.chrome.audioOutputCommand);
|
|
462
|
+
if (audioOutputCommand) startParams.audioOutputCommand = audioOutputCommand;
|
|
463
|
+
const audioBridgeCommand = copyCommand(config.chrome.audioBridgeCommand);
|
|
464
|
+
if (audioBridgeCommand) startParams.audioBridgeCommand = audioBridgeCommand;
|
|
465
|
+
const audioBridgeHealthCommand = copyCommand(config.chrome.audioBridgeHealthCommand);
|
|
466
|
+
if (audioBridgeHealthCommand) startParams.audioBridgeHealthCommand = audioBridgeHealthCommand;
|
|
467
|
+
return approved(startParams);
|
|
468
|
+
}
|
|
469
|
+
function denyMissing(action, field) {
|
|
470
|
+
return {
|
|
471
|
+
approved: false,
|
|
472
|
+
result: denied(`googlemeet.chrome ${action} requires ${field}`)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function buildForwardParams(params) {
|
|
476
|
+
const action = readString$1(params.action);
|
|
477
|
+
switch (action) {
|
|
478
|
+
case "setup": return approved({ action });
|
|
479
|
+
case "status": {
|
|
480
|
+
const bridgeId = readString$1(params.bridgeId);
|
|
481
|
+
return approved(bridgeId ? {
|
|
482
|
+
action,
|
|
483
|
+
bridgeId
|
|
484
|
+
} : { action });
|
|
485
|
+
}
|
|
486
|
+
case "list": {
|
|
487
|
+
const forwarded = { action };
|
|
488
|
+
const url = readString$1(params.url);
|
|
489
|
+
const mode = readString$1(params.mode);
|
|
490
|
+
if (url) try {
|
|
491
|
+
forwarded.url = normalizeMeetUrl(url);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
return {
|
|
494
|
+
approved: false,
|
|
495
|
+
result: denied(error instanceof Error ? error.message : "googlemeet.chrome list url")
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (mode) forwarded.mode = mode;
|
|
499
|
+
return approved(forwarded);
|
|
500
|
+
}
|
|
501
|
+
case "stopByUrl": {
|
|
502
|
+
const forwarded = { action };
|
|
503
|
+
const url = readString$1(params.url);
|
|
504
|
+
const mode = readString$1(params.mode);
|
|
505
|
+
const exceptBridgeId = readString$1(params.exceptBridgeId);
|
|
506
|
+
if (!url) return denyMissing(action, "url");
|
|
507
|
+
try {
|
|
508
|
+
forwarded.url = normalizeMeetUrl(url);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
return {
|
|
511
|
+
approved: false,
|
|
512
|
+
result: denied(error instanceof Error ? error.message : "googlemeet.chrome stopByUrl url")
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (mode) forwarded.mode = mode;
|
|
516
|
+
if (exceptBridgeId) forwarded.exceptBridgeId = exceptBridgeId;
|
|
517
|
+
return approved(forwarded);
|
|
518
|
+
}
|
|
519
|
+
case "pullAudio": {
|
|
520
|
+
const forwarded = { action };
|
|
521
|
+
const bridgeId = readString$1(params.bridgeId);
|
|
522
|
+
const timeoutMs = readPositiveNumber(params.timeoutMs);
|
|
523
|
+
if (!bridgeId) return denyMissing(action, "bridgeId");
|
|
524
|
+
forwarded.bridgeId = bridgeId;
|
|
525
|
+
if (timeoutMs) forwarded.timeoutMs = timeoutMs;
|
|
526
|
+
return approved(forwarded);
|
|
527
|
+
}
|
|
528
|
+
case "pushAudio": {
|
|
529
|
+
const forwarded = { action };
|
|
530
|
+
const bridgeId = readString$1(params.bridgeId);
|
|
531
|
+
const base64 = readString$1(params.base64);
|
|
532
|
+
if (!bridgeId) return denyMissing(action, "bridgeId");
|
|
533
|
+
if (!base64) return denyMissing(action, "base64");
|
|
534
|
+
forwarded.bridgeId = bridgeId;
|
|
535
|
+
forwarded.base64 = base64;
|
|
536
|
+
return approved(forwarded);
|
|
537
|
+
}
|
|
538
|
+
case "clearAudio": {
|
|
539
|
+
const bridgeId = readString$1(params.bridgeId);
|
|
540
|
+
if (!bridgeId) return denyMissing(action, "bridgeId");
|
|
541
|
+
return approved({
|
|
542
|
+
action,
|
|
543
|
+
bridgeId
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
case "stop": {
|
|
547
|
+
const bridgeId = readString$1(params.bridgeId);
|
|
548
|
+
return approved(bridgeId ? {
|
|
549
|
+
action,
|
|
550
|
+
bridgeId
|
|
551
|
+
} : { action });
|
|
552
|
+
}
|
|
553
|
+
default: return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function createGoogleMeetChromeNodeInvokePolicy(config) {
|
|
557
|
+
return {
|
|
558
|
+
commands: [GOOGLE_MEET_CHROME_NODE_COMMAND],
|
|
559
|
+
dangerous: true,
|
|
560
|
+
async handle(ctx) {
|
|
561
|
+
if (ctx.command !== "googlemeet.chrome") return denied(`unsupported Google Meet node command: ${ctx.command}`);
|
|
562
|
+
const params = asRecord$2(ctx.params);
|
|
563
|
+
const action = readString$1(params.action);
|
|
564
|
+
let decision;
|
|
565
|
+
if (action === "start") decision = buildStartParams(params, config);
|
|
566
|
+
else decision = buildForwardParams(params) ?? {
|
|
567
|
+
approved: false,
|
|
568
|
+
result: denied("unsupported googlemeet.chrome action")
|
|
569
|
+
};
|
|
570
|
+
if (!decision.approved) return decision.result;
|
|
571
|
+
return await ctx.invokeNode({ params: decision.params });
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
//#endregion
|
|
402
576
|
//#region extensions/google-meet/src/setup.ts
|
|
403
577
|
function resolveUserPath(input) {
|
|
404
578
|
if (input === "~") return os.homedir();
|
|
@@ -555,9 +729,9 @@ const GOOGLE_MEET_CONSULT_SYSTEM_PROMPT = [
|
|
|
555
729
|
function resolveGoogleMeetRealtimeTools(policy) {
|
|
556
730
|
return resolveRealtimeVoiceAgentConsultTools(policy);
|
|
557
731
|
}
|
|
558
|
-
function submitGoogleMeetConsultWorkingResponse(session, callId) {
|
|
732
|
+
async function submitGoogleMeetConsultWorkingResponse(session, callId) {
|
|
559
733
|
if (!session.bridge.supportsToolResultContinuation) return;
|
|
560
|
-
session.submitToolResult(callId, buildRealtimeVoiceAgentConsultWorkingResponse("participant"), { willContinue: true });
|
|
734
|
+
await session.submitToolResult(callId, buildRealtimeVoiceAgentConsultWorkingResponse("participant"), { willContinue: true });
|
|
561
735
|
}
|
|
562
736
|
async function consultOpenClawAgentForGoogleMeet(params) {
|
|
563
737
|
const agentId = normalizeAgentId(params.config.realtime.agentId);
|
|
@@ -584,34 +758,37 @@ async function consultOpenClawAgentForGoogleMeet(params) {
|
|
|
584
758
|
extraSystemPrompt: GOOGLE_MEET_CONSULT_SYSTEM_PROMPT
|
|
585
759
|
});
|
|
586
760
|
}
|
|
587
|
-
function handleGoogleMeetRealtimeConsultToolCall(params) {
|
|
761
|
+
async function handleGoogleMeetRealtimeConsultToolCall(params) {
|
|
588
762
|
const callId = params.event.callId || params.event.itemId;
|
|
589
763
|
if (params.strategy !== "bidi") {
|
|
764
|
+
const error = `Tool "${params.event.name}" is only available in bidi realtime strategy`;
|
|
765
|
+
await params.session.submitToolResult(callId, { error });
|
|
590
766
|
params.onTalkEvent?.({
|
|
591
767
|
type: "tool.error",
|
|
592
768
|
callId,
|
|
593
769
|
payload: {
|
|
594
770
|
name: params.event.name,
|
|
595
|
-
error
|
|
771
|
+
error
|
|
596
772
|
},
|
|
597
773
|
final: true
|
|
598
774
|
});
|
|
599
|
-
params.session.submitToolResult(callId, { error: `Tool "${params.event.name}" is only available in bidi realtime strategy` });
|
|
600
775
|
return;
|
|
601
776
|
}
|
|
602
777
|
if (params.event.name !== REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME) {
|
|
778
|
+
const error = `Tool "${params.event.name}" not available`;
|
|
779
|
+
await params.session.submitToolResult(callId, { error });
|
|
603
780
|
params.onTalkEvent?.({
|
|
604
781
|
type: "tool.error",
|
|
605
782
|
callId,
|
|
606
783
|
payload: {
|
|
607
784
|
name: params.event.name,
|
|
608
|
-
error
|
|
785
|
+
error
|
|
609
786
|
},
|
|
610
787
|
final: true
|
|
611
788
|
});
|
|
612
|
-
params.session.submitToolResult(callId, { error: `Tool "${params.event.name}" not available` });
|
|
613
789
|
return;
|
|
614
790
|
}
|
|
791
|
+
await submitGoogleMeetConsultWorkingResponse(params.session, callId);
|
|
615
792
|
params.onTalkEvent?.({
|
|
616
793
|
type: "tool.progress",
|
|
617
794
|
callId,
|
|
@@ -620,38 +797,41 @@ function handleGoogleMeetRealtimeConsultToolCall(params) {
|
|
|
620
797
|
status: "working"
|
|
621
798
|
}
|
|
622
799
|
});
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
params.onTalkEvent?.({
|
|
635
|
-
type: "tool.result",
|
|
636
|
-
callId,
|
|
637
|
-
payload: {
|
|
638
|
-
name: params.event.name,
|
|
639
|
-
result
|
|
640
|
-
},
|
|
641
|
-
final: true
|
|
800
|
+
let result;
|
|
801
|
+
try {
|
|
802
|
+
result = await consultOpenClawAgentForGoogleMeet({
|
|
803
|
+
config: params.config,
|
|
804
|
+
fullConfig: params.fullConfig,
|
|
805
|
+
runtime: params.runtime,
|
|
806
|
+
logger: params.logger,
|
|
807
|
+
meetingSessionId: params.meetingSessionId,
|
|
808
|
+
requesterSessionKey: params.requesterSessionKey,
|
|
809
|
+
args: params.event.args,
|
|
810
|
+
transcript: params.transcript
|
|
642
811
|
});
|
|
643
|
-
|
|
644
|
-
|
|
812
|
+
} catch (error) {
|
|
813
|
+
const message = formatErrorMessage(error);
|
|
814
|
+
await params.session.submitToolResult(callId, { error: message });
|
|
645
815
|
params.onTalkEvent?.({
|
|
646
816
|
type: "tool.error",
|
|
647
817
|
callId,
|
|
648
818
|
payload: {
|
|
649
819
|
name: params.event.name,
|
|
650
|
-
error:
|
|
820
|
+
error: message
|
|
651
821
|
},
|
|
652
822
|
final: true
|
|
653
823
|
});
|
|
654
|
-
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
await params.session.submitToolResult(callId, result);
|
|
827
|
+
params.onTalkEvent?.({
|
|
828
|
+
type: "tool.result",
|
|
829
|
+
callId,
|
|
830
|
+
payload: {
|
|
831
|
+
name: params.event.name,
|
|
832
|
+
result
|
|
833
|
+
},
|
|
834
|
+
final: true
|
|
655
835
|
});
|
|
656
836
|
}
|
|
657
837
|
//#endregion
|
|
@@ -814,7 +994,7 @@ function readLogString(value) {
|
|
|
814
994
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
815
995
|
}
|
|
816
996
|
function formatLogValue(value) {
|
|
817
|
-
return value
|
|
997
|
+
return (value ? truncateUtf16Safe(value.replace(/\s+/g, "_"), 180) : void 0) || "unknown";
|
|
818
998
|
}
|
|
819
999
|
function resolveProviderModelForLog(params) {
|
|
820
1000
|
return readLogString(params.providerConfig.model) ?? readLogString(params.providerConfig.modelId) ?? readLogString(params.fallbackModel) ?? readLogString(params.provider.defaultModel) ?? "provider-default";
|
|
@@ -1499,7 +1679,7 @@ async function startCommandRealtimeAudioBridge(params) {
|
|
|
1499
1679
|
}
|
|
1500
1680
|
});
|
|
1501
1681
|
const turnId = ensureTalkTurn();
|
|
1502
|
-
handleGoogleMeetRealtimeConsultToolCall({
|
|
1682
|
+
return handleGoogleMeetRealtimeConsultToolCall({
|
|
1503
1683
|
strategy,
|
|
1504
1684
|
session,
|
|
1505
1685
|
event,
|
|
@@ -1592,10 +1772,10 @@ async function startCommandRealtimeAudioBridge(params) {
|
|
|
1592
1772
|
}
|
|
1593
1773
|
//#endregion
|
|
1594
1774
|
//#region extensions/google-meet/src/realtime-node.ts
|
|
1595
|
-
function asRecord$
|
|
1775
|
+
function asRecord$1(value) {
|
|
1596
1776
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1597
1777
|
}
|
|
1598
|
-
function readString
|
|
1778
|
+
function readString(value) {
|
|
1599
1779
|
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1600
1780
|
}
|
|
1601
1781
|
function normalizeGoogleMeetTtsPromptText(text) {
|
|
@@ -1626,10 +1806,10 @@ function startGoogleMeetNodeAudioInputLoop(params) {
|
|
|
1626
1806
|
},
|
|
1627
1807
|
timeoutMs: 2e3
|
|
1628
1808
|
});
|
|
1629
|
-
const result = asRecord$
|
|
1809
|
+
const result = asRecord$1(asRecord$1(raw).payload ?? raw);
|
|
1630
1810
|
consecutiveInputErrors = 0;
|
|
1631
1811
|
lastInputError = void 0;
|
|
1632
|
-
const base64 = readString
|
|
1812
|
+
const base64 = readString(result.base64);
|
|
1633
1813
|
if (base64) {
|
|
1634
1814
|
const audio = Buffer.from(base64, "base64");
|
|
1635
1815
|
if (params.isInputSuppressed()) {
|
|
@@ -2084,7 +2264,7 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2084
2264
|
}
|
|
2085
2265
|
});
|
|
2086
2266
|
const turnId = ensureTalkTurn();
|
|
2087
|
-
handleGoogleMeetRealtimeConsultToolCall({
|
|
2267
|
+
return handleGoogleMeetRealtimeConsultToolCall({
|
|
2088
2268
|
strategy,
|
|
2089
2269
|
session,
|
|
2090
2270
|
event,
|
|
@@ -2173,11 +2353,22 @@ async function startNodeRealtimeAudioBridge(params) {
|
|
|
2173
2353
|
};
|
|
2174
2354
|
}
|
|
2175
2355
|
//#endregion
|
|
2356
|
+
//#region extensions/google-meet/src/transports/types.ts
|
|
2357
|
+
const GOOGLE_MEET_TRANSCRIPT_MAX_LINES = 2e3;
|
|
2358
|
+
//#endregion
|
|
2176
2359
|
//#region extensions/google-meet/src/transports/chrome.ts
|
|
2177
|
-
const
|
|
2360
|
+
const GOOGLE_MEET_CAPTION_SETTLE_MS = 1e3;
|
|
2178
2361
|
function isGoogleMeetTalkBackMode$2(mode) {
|
|
2179
2362
|
return mode === "agent" || mode === "bidi";
|
|
2180
2363
|
}
|
|
2364
|
+
function readMeetAuthUser(url) {
|
|
2365
|
+
if (!url) return;
|
|
2366
|
+
try {
|
|
2367
|
+
return new URL(url).searchParams.get("authuser") ?? void 0;
|
|
2368
|
+
} catch {
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2181
2372
|
async function assertBlackHole2chAvailable(params) {
|
|
2182
2373
|
if (process.platform !== "darwin") throw new Error("Chrome Meet transport with blackhole-2ch audio is currently macOS-only");
|
|
2183
2374
|
const result = await params.runtime.system.runCommandWithTimeout([GOOGLE_MEET_SYSTEM_PROFILER_COMMAND, "SPAudioDataType"], { timeoutMs: params.timeoutMs });
|
|
@@ -2250,9 +2441,10 @@ async function launchChromeMeet(params) {
|
|
|
2250
2441
|
audioBridge: await startRealtimeAudioBridge()
|
|
2251
2442
|
};
|
|
2252
2443
|
const result = await openMeetWithBrowserRequest({
|
|
2253
|
-
callBrowser:
|
|
2444
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
2254
2445
|
config: params.config,
|
|
2255
2446
|
mode: params.mode,
|
|
2447
|
+
meetingSessionId: params.meetingSessionId,
|
|
2256
2448
|
url: params.url
|
|
2257
2449
|
});
|
|
2258
2450
|
const audioBridge = isGoogleMeetTalkBackMode$2(params.mode) && result.browser?.inCall === true && result.browser.micMuted !== true && result.browser.manualActionRequired !== true ? await startRealtimeAudioBridge() : void 0;
|
|
@@ -2300,7 +2492,7 @@ function parseMeetBrowserStatus(result) {
|
|
|
2300
2492
|
};
|
|
2301
2493
|
}
|
|
2302
2494
|
async function callLocalBrowserRequest(params) {
|
|
2303
|
-
return await
|
|
2495
|
+
return await callGatewayFromCli("browser.request", {
|
|
2304
2496
|
json: true,
|
|
2305
2497
|
timeout: String(resolveBrowserGatewayTimeoutMs(params.timeoutMs))
|
|
2306
2498
|
}, {
|
|
@@ -2310,6 +2502,18 @@ async function callLocalBrowserRequest(params) {
|
|
|
2310
2502
|
timeoutMs: params.timeoutMs
|
|
2311
2503
|
}, { progress: false });
|
|
2312
2504
|
}
|
|
2505
|
+
async function resolveLocalBrowserRequest(runtime) {
|
|
2506
|
+
if (!await runtime.gateway.isAvailable()) return callLocalBrowserRequest;
|
|
2507
|
+
return async (params) => await runtime.gateway.request("browser.request", {
|
|
2508
|
+
method: params.method,
|
|
2509
|
+
path: params.path,
|
|
2510
|
+
body: params.body,
|
|
2511
|
+
timeoutMs: params.timeoutMs
|
|
2512
|
+
}, {
|
|
2513
|
+
timeoutMs: resolveBrowserGatewayTimeoutMs(params.timeoutMs),
|
|
2514
|
+
scopes: ["operator.admin"]
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2313
2517
|
function resolveBrowserGatewayTimeoutMs(timeoutMs) {
|
|
2314
2518
|
return addTimerTimeoutGraceMs(timeoutMs) ?? 1;
|
|
2315
2519
|
}
|
|
@@ -2350,6 +2554,7 @@ function meetStatusScript(params) {
|
|
|
2350
2554
|
return `async () => {
|
|
2351
2555
|
const text = (node) => (node?.innerText || node?.textContent || "").trim();
|
|
2352
2556
|
const allowMicrophone = ${JSON.stringify(params.allowMicrophone)};
|
|
2557
|
+
const captionSessionId = ${JSON.stringify(params.captionSessionId)};
|
|
2353
2558
|
const captureCaptions = ${JSON.stringify(params.captureCaptions)};
|
|
2354
2559
|
const readOnly = ${JSON.stringify(Boolean(params.readOnly))};
|
|
2355
2560
|
const buttons = [...document.querySelectorAll('button')];
|
|
@@ -2405,8 +2610,9 @@ function meetStatusScript(params) {
|
|
|
2405
2610
|
mic.click();
|
|
2406
2611
|
notes.push("Muted Meet microphone for observe-only mode.");
|
|
2407
2612
|
}
|
|
2613
|
+
const joinElsewhere = findButton(/join here too/i);
|
|
2408
2614
|
const join = !readOnly && ${JSON.stringify(params.autoJoin)}
|
|
2409
|
-
? findButton(/join now|ask to join
|
|
2615
|
+
? findButton(/join now|ask to join/i)
|
|
2410
2616
|
: null;
|
|
2411
2617
|
if (join) join.click();
|
|
2412
2618
|
const microphoneChoice = findButton(/\\buse microphone\\b/i);
|
|
@@ -2480,42 +2686,127 @@ function meetStatusScript(params) {
|
|
|
2480
2686
|
if (!captureCaptions) return undefined;
|
|
2481
2687
|
const w = window;
|
|
2482
2688
|
if (!inCall && !w.__openclawMeetCaptions) return undefined;
|
|
2483
|
-
|
|
2689
|
+
// A reused tab starts a fresh logical transcript for each OpenClaw session.
|
|
2690
|
+
// Status refreshes omit the id, so they preserve the active page-owned buffer.
|
|
2691
|
+
if (!w.__openclawMeetCaptions || (captionSessionId && w.__openclawMeetCaptions.sessionId !== captionSessionId)) {
|
|
2692
|
+
if (w.__openclawMeetCaptions?.settleTimer !== undefined) {
|
|
2693
|
+
clearTimeout(w.__openclawMeetCaptions.settleTimer);
|
|
2694
|
+
}
|
|
2695
|
+
w.__openclawMeetCaptions?.observer?.disconnect?.();
|
|
2484
2696
|
w.__openclawMeetCaptions = {
|
|
2697
|
+
sessionId: captionSessionId,
|
|
2698
|
+
// Epochs cross document lifetimes in the runtime transcript cursor.
|
|
2699
|
+
// Strong UUIDs keep a reloaded page distinct from its prior buffer.
|
|
2700
|
+
epoch: crypto.randomUUID(),
|
|
2485
2701
|
enabledAttempted: false,
|
|
2486
2702
|
observerInstalled: false,
|
|
2703
|
+
observer: undefined,
|
|
2704
|
+
droppedLines: 0,
|
|
2487
2705
|
lines: [],
|
|
2488
|
-
|
|
2706
|
+
settleTimer: undefined,
|
|
2707
|
+
visible: []
|
|
2489
2708
|
};
|
|
2490
2709
|
}
|
|
2491
2710
|
return w.__openclawMeetCaptions;
|
|
2492
2711
|
})();
|
|
2493
|
-
const
|
|
2712
|
+
const normalizeCaption = (speaker, captionText) => {
|
|
2494
2713
|
if (!captionState) return;
|
|
2495
2714
|
const clean = String(captionText || "").replace(/\\s+/g, " ").trim();
|
|
2496
2715
|
const cleanSpeaker = String(speaker || "").replace(/\\s+/g, " ").trim();
|
|
2497
|
-
if (!clean || clean.length < 2) return;
|
|
2498
|
-
if (/^(turn on captions|turn off captions|captions)$/i.test(clean)) return;
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2716
|
+
if (!clean || clean.length < 2) return undefined;
|
|
2717
|
+
if (/^(turn on captions|turn off captions|captions)$/i.test(clean)) return undefined;
|
|
2718
|
+
return { speaker: cleanSpeaker || undefined, text: clean };
|
|
2719
|
+
};
|
|
2720
|
+
const commitLines = (state, entries) => {
|
|
2721
|
+
state.lines.push(...entries.map((entry) => ({
|
|
2722
|
+
at: entry.at,
|
|
2723
|
+
speaker: entry.speaker,
|
|
2724
|
+
text: entry.text
|
|
2725
|
+
})));
|
|
2726
|
+
const excess = state.lines.length - ${GOOGLE_MEET_TRANSCRIPT_MAX_LINES};
|
|
2727
|
+
if (excess > 0) {
|
|
2728
|
+
state.lines.splice(0, excess);
|
|
2729
|
+
state.droppedLines = (state.droppedLines || 0) + excess;
|
|
2730
|
+
}
|
|
2505
2731
|
};
|
|
2506
2732
|
const scrapeCaptions = () => {
|
|
2507
2733
|
if (!captionState) return;
|
|
2508
2734
|
const regions = [...document.querySelectorAll(captionSelector)];
|
|
2735
|
+
const rows = [];
|
|
2509
2736
|
for (const region of regions) {
|
|
2510
2737
|
const raw = text(region);
|
|
2511
2738
|
if (!raw) continue;
|
|
2512
2739
|
const pieces = raw.split(/\\n+/).map((part) => part.trim()).filter(Boolean);
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2740
|
+
const row = pieces.length >= 2
|
|
2741
|
+
? normalizeCaption(pieces[0], pieces.slice(1).join(" "))
|
|
2742
|
+
: normalizeCaption("", pieces[0] || raw);
|
|
2743
|
+
if (row) rows.push({ ...row, node: region });
|
|
2744
|
+
}
|
|
2745
|
+
if (rows.length === 0) {
|
|
2746
|
+
// Meet briefly removes caption rows while rerendering. Keep them mutable
|
|
2747
|
+
// for one settle window so a DOM gap cannot fabricate a repeated line.
|
|
2748
|
+
if (captionState.visible.length > 0 && captionState.settleTimer === undefined) {
|
|
2749
|
+
const pendingState = captionState;
|
|
2750
|
+
pendingState.settleTimer = setTimeout(() => {
|
|
2751
|
+
if (window.__openclawMeetCaptions !== pendingState) return;
|
|
2752
|
+
commitLines(pendingState, pendingState.visible);
|
|
2753
|
+
pendingState.visible = [];
|
|
2754
|
+
pendingState.settleTimer = undefined;
|
|
2755
|
+
}, ${GOOGLE_MEET_CAPTION_SETTLE_MS});
|
|
2517
2756
|
}
|
|
2757
|
+
return;
|
|
2518
2758
|
}
|
|
2759
|
+
if (captionState.settleTimer !== undefined) {
|
|
2760
|
+
clearTimeout(captionState.settleTimer);
|
|
2761
|
+
captionState.settleTimer = undefined;
|
|
2762
|
+
}
|
|
2763
|
+
const previous = Array.isArray(captionState.visible) ? captionState.visible : [];
|
|
2764
|
+
const unmatchedPrevious = [...previous];
|
|
2765
|
+
const nextVisible = [];
|
|
2766
|
+
const now = Date.now();
|
|
2767
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
2768
|
+
const row = rows[index];
|
|
2769
|
+
const priorIndex = unmatchedPrevious.findIndex((candidate) => {
|
|
2770
|
+
const sameTextLifecycle =
|
|
2771
|
+
candidate.text === row.text ||
|
|
2772
|
+
row.text.startsWith(candidate.text) ||
|
|
2773
|
+
candidate.text.startsWith(row.text);
|
|
2774
|
+
const sameDomLifecycle =
|
|
2775
|
+
candidate.node === row.node || now - candidate.seenAt <= ${GOOGLE_MEET_CAPTION_SETTLE_MS};
|
|
2776
|
+
return candidate.speaker === row.speaker && sameTextLifecycle && sameDomLifecycle;
|
|
2777
|
+
});
|
|
2778
|
+
const prior = priorIndex >= 0 ? unmatchedPrevious.splice(priorIndex, 1)[0] : undefined;
|
|
2779
|
+
const sameSpeaker = Boolean(prior) && prior.speaker === row.speaker;
|
|
2780
|
+
if (sameSpeaker && prior.text === row.text) {
|
|
2781
|
+
prior.node = row.node;
|
|
2782
|
+
prior.seenAt = now;
|
|
2783
|
+
nextVisible.push(prior);
|
|
2784
|
+
continue;
|
|
2785
|
+
}
|
|
2786
|
+
if (sameSpeaker && row.text.startsWith(prior.text)) {
|
|
2787
|
+
prior.text = row.text;
|
|
2788
|
+
prior.node = row.node;
|
|
2789
|
+
prior.seenAt = now;
|
|
2790
|
+
nextVisible.push(prior);
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2793
|
+
if (sameSpeaker && prior.text.startsWith(row.text)) {
|
|
2794
|
+
prior.node = row.node;
|
|
2795
|
+
prior.seenAt = now;
|
|
2796
|
+
nextVisible.push(prior);
|
|
2797
|
+
continue;
|
|
2798
|
+
}
|
|
2799
|
+
const entry = {
|
|
2800
|
+
at: new Date().toISOString(),
|
|
2801
|
+
node: row.node,
|
|
2802
|
+
seenAt: now,
|
|
2803
|
+
speaker: row.speaker,
|
|
2804
|
+
text: row.text
|
|
2805
|
+
};
|
|
2806
|
+
nextVisible.push(entry);
|
|
2807
|
+
}
|
|
2808
|
+
commitLines(captionState, unmatchedPrevious);
|
|
2809
|
+
captionState.visible = nextVisible;
|
|
2519
2810
|
};
|
|
2520
2811
|
if (captionState) {
|
|
2521
2812
|
if (!readOnly && inCall && !captionState.enabledAttempted) {
|
|
@@ -2534,7 +2825,8 @@ function meetStatusScript(params) {
|
|
|
2534
2825
|
}
|
|
2535
2826
|
if (inCall && !captionState.observerInstalled) {
|
|
2536
2827
|
captionState.observerInstalled = true;
|
|
2537
|
-
new MutationObserver(scrapeCaptions)
|
|
2828
|
+
captionState.observer = new MutationObserver(scrapeCaptions);
|
|
2829
|
+
captionState.observer.observe(document.body, {
|
|
2538
2830
|
childList: true,
|
|
2539
2831
|
subtree: true,
|
|
2540
2832
|
characterData: true
|
|
@@ -2544,17 +2836,19 @@ function meetStatusScript(params) {
|
|
|
2544
2836
|
if (inCall) {
|
|
2545
2837
|
scrapeCaptions();
|
|
2546
2838
|
}
|
|
2547
|
-
const
|
|
2839
|
+
const committedLines = Array.isArray(captionState.lines) ? captionState.lines : [];
|
|
2840
|
+
const visibleLines = Array.isArray(captionState.visible) ? captionState.visible : [];
|
|
2841
|
+
const lines = [...committedLines, ...visibleLines];
|
|
2548
2842
|
const last = lines[lines.length - 1];
|
|
2549
2843
|
captioning = document.querySelector(captionSelector) !== null || lines.length > 0;
|
|
2550
|
-
transcriptLines = lines.length;
|
|
2844
|
+
transcriptLines = (captionState.droppedLines || 0) + lines.length;
|
|
2551
2845
|
lastCaptionAt = last?.at;
|
|
2552
2846
|
lastCaptionSpeaker = last?.speaker;
|
|
2553
2847
|
lastCaptionText = last?.text;
|
|
2554
2848
|
recentTranscript = lines.slice(-5);
|
|
2555
2849
|
}
|
|
2556
2850
|
const lobbyWaiting = !inCall && /asking to be let in|you.?ll join when someone lets you in|waiting to be let in|ask to join/i.test(pageText);
|
|
2557
|
-
const leaveReason = /you left the meeting|you.?ve left the meeting|removed from the meeting|you were removed|call ended|meeting ended/i.test(pageText)
|
|
2851
|
+
const leaveReason = !inCall && /you left the meeting|you.?ve left the meeting|removed from the meeting|you were removed|call ended|meeting ended/i.test(pageText)
|
|
2558
2852
|
? pageText.match(/you left the meeting|you.?ve left the meeting|removed from the meeting|you were removed|call ended|meeting ended/i)?.[0]
|
|
2559
2853
|
: undefined;
|
|
2560
2854
|
let manualActionReason;
|
|
@@ -2562,6 +2856,9 @@ function meetStatusScript(params) {
|
|
|
2562
2856
|
if (!inCall && (host === "accounts.google.com" || /use your google account|to continue to google meet|choose an account|sign in to (join|continue)/i.test(pageText))) {
|
|
2563
2857
|
manualActionReason = "google-login-required";
|
|
2564
2858
|
manualActionMessage = "Sign in to Google in the OpenClaw browser profile, then retry the Meet join.";
|
|
2859
|
+
} else if (!inCall && joinElsewhere) {
|
|
2860
|
+
manualActionReason = "meet-session-conflict";
|
|
2861
|
+
manualActionMessage = "Meet is already active in another tab or device. Leave that session or reuse an English-pinned tab before retrying.";
|
|
2565
2862
|
} else if (!inCall && /asking to be let in|you.?ll join when someone lets you in|waiting to be let in|ask to join/i.test(pageText)) {
|
|
2566
2863
|
manualActionReason = "meet-admission-required";
|
|
2567
2864
|
manualActionMessage = "Admit the OpenClaw browser participant in Google Meet, then retry speech.";
|
|
@@ -2602,63 +2899,404 @@ function meetStatusScript(params) {
|
|
|
2602
2899
|
});
|
|
2603
2900
|
}`;
|
|
2604
2901
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2902
|
+
function meetTranscriptScript(meetingUrl, meetingSessionId, finalize) {
|
|
2903
|
+
const expectedMeetingUrl = normalizeMeetUrlForReuse(meetingUrl);
|
|
2904
|
+
return `() => {
|
|
2905
|
+
const expectedMeetingUrl = ${JSON.stringify(expectedMeetingUrl)};
|
|
2906
|
+
const expectedSessionId = ${JSON.stringify(meetingSessionId)};
|
|
2907
|
+
let currentMeetingUrl;
|
|
2908
|
+
try {
|
|
2909
|
+
const currentUrl = new URL(location.href);
|
|
2910
|
+
currentMeetingUrl = currentUrl.origin + currentUrl.pathname.toLowerCase().replace(/\\/$/, "");
|
|
2911
|
+
} catch {
|
|
2912
|
+
return JSON.stringify({ urlMatched: false });
|
|
2913
|
+
}
|
|
2914
|
+
if (!expectedMeetingUrl || currentMeetingUrl !== expectedMeetingUrl) {
|
|
2915
|
+
return JSON.stringify({ urlMatched: false });
|
|
2916
|
+
}
|
|
2917
|
+
const state = window.__openclawMeetCaptions;
|
|
2918
|
+
if (state?.sessionId && state.sessionId !== expectedSessionId) {
|
|
2919
|
+
return JSON.stringify({ urlMatched: true, sessionMatched: false });
|
|
2920
|
+
}
|
|
2921
|
+
if (${JSON.stringify(finalize)} && Array.isArray(state?.visible) && state.visible.length > 0) {
|
|
2922
|
+
if (state.settleTimer !== undefined) clearTimeout(state.settleTimer);
|
|
2923
|
+
state.settleTimer = undefined;
|
|
2924
|
+
state.lines = Array.isArray(state.lines) ? state.lines : [];
|
|
2925
|
+
state.lines.push(...state.visible.map((entry) => ({
|
|
2926
|
+
at: entry.at,
|
|
2927
|
+
speaker: entry.speaker,
|
|
2928
|
+
text: entry.text
|
|
2929
|
+
})));
|
|
2930
|
+
state.visible = [];
|
|
2931
|
+
const excess = state.lines.length - ${GOOGLE_MEET_TRANSCRIPT_MAX_LINES};
|
|
2932
|
+
if (excess > 0) {
|
|
2933
|
+
state.lines.splice(0, excess);
|
|
2934
|
+
state.droppedLines = (state.droppedLines || 0) + excess;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
const lines = Array.isArray(state?.lines) ? state.lines : [];
|
|
2938
|
+
return JSON.stringify({
|
|
2939
|
+
urlMatched: true,
|
|
2940
|
+
sessionMatched: true,
|
|
2941
|
+
epoch: typeof state?.epoch === "string" ? state.epoch : undefined,
|
|
2942
|
+
droppedLines: Number.isFinite(state?.droppedLines) ? Math.max(0, Math.trunc(state.droppedLines)) : 0,
|
|
2943
|
+
lines: lines.map((line) => ({
|
|
2944
|
+
at: typeof line?.at === "string" ? line.at : undefined,
|
|
2945
|
+
speaker: typeof line?.speaker === "string" ? line.speaker : undefined,
|
|
2946
|
+
text: typeof line?.text === "string" ? line.text : ""
|
|
2947
|
+
})).filter((line) => line.text)
|
|
2948
|
+
});
|
|
2949
|
+
}`;
|
|
2619
2950
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
targetId = tab?.targetId;
|
|
2632
|
-
if (targetId) await params.callBrowser({
|
|
2633
|
-
method: "POST",
|
|
2634
|
-
path: "/tabs/focus",
|
|
2635
|
-
body: { targetId },
|
|
2636
|
-
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
2637
|
-
});
|
|
2638
|
-
}
|
|
2639
|
-
if (!targetId) {
|
|
2640
|
-
tab = readBrowserTab(await params.callBrowser({
|
|
2641
|
-
method: "POST",
|
|
2642
|
-
path: "/tabs/open",
|
|
2643
|
-
body: { url: forceMeetEnglishUi(params.url) },
|
|
2644
|
-
timeoutMs
|
|
2645
|
-
}));
|
|
2646
|
-
targetId = tab?.targetId;
|
|
2951
|
+
function parseMeetTranscriptSnapshot(result) {
|
|
2952
|
+
const raw = (result && typeof result === "object" ? result : {}).result;
|
|
2953
|
+
if (typeof raw !== "string" || !raw.trim()) return {
|
|
2954
|
+
droppedLines: 0,
|
|
2955
|
+
lines: []
|
|
2956
|
+
};
|
|
2957
|
+
let parsed;
|
|
2958
|
+
try {
|
|
2959
|
+
parsed = JSON.parse(raw);
|
|
2960
|
+
} catch {
|
|
2961
|
+
throw new Error("Google Meet transcript JSON is malformed.");
|
|
2647
2962
|
}
|
|
2648
|
-
if (!
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2963
|
+
if (!parsed || typeof parsed !== "object") throw new Error("Google Meet transcript payload is invalid.");
|
|
2964
|
+
const payload = parsed;
|
|
2965
|
+
const droppedLines = typeof payload.droppedLines === "number" && Number.isSafeInteger(payload.droppedLines) ? Math.max(0, payload.droppedLines) : 0;
|
|
2966
|
+
const lines = Array.isArray(payload.lines) ? payload.lines.flatMap((value) => {
|
|
2967
|
+
if (!value || typeof value !== "object") return [];
|
|
2968
|
+
const line = value;
|
|
2969
|
+
if (typeof line.text !== "string" || !line.text.trim()) return [];
|
|
2970
|
+
return [{
|
|
2971
|
+
...typeof line.at === "string" ? { at: line.at } : {},
|
|
2972
|
+
...typeof line.speaker === "string" ? { speaker: line.speaker } : {},
|
|
2973
|
+
text: line.text
|
|
2974
|
+
}];
|
|
2975
|
+
}) : [];
|
|
2976
|
+
return {
|
|
2977
|
+
droppedLines,
|
|
2978
|
+
...typeof payload.epoch === "string" ? { epoch: payload.epoch } : {},
|
|
2979
|
+
lines,
|
|
2980
|
+
...typeof payload.urlMatched === "boolean" ? { urlMatched: payload.urlMatched } : {},
|
|
2981
|
+
...typeof payload.sessionMatched === "boolean" ? { sessionMatched: payload.sessionMatched } : {}
|
|
2656
2982
|
};
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2983
|
+
}
|
|
2984
|
+
async function readMeetTranscriptWithBrowserRequest(params) {
|
|
2985
|
+
const snapshot = parseMeetTranscriptSnapshot(await params.callBrowser({
|
|
2986
|
+
method: "POST",
|
|
2987
|
+
path: "/act",
|
|
2988
|
+
body: {
|
|
2989
|
+
kind: "evaluate",
|
|
2990
|
+
targetId: params.tab.targetId,
|
|
2991
|
+
fn: meetTranscriptScript(params.meetingUrl, params.meetingSessionId, params.finalize)
|
|
2992
|
+
},
|
|
2993
|
+
timeoutMs: params.timeoutMs
|
|
2994
|
+
}));
|
|
2995
|
+
if (snapshot.urlMatched === false) throw new Error("The tracked Meet tab no longer shows this session's meeting URL.");
|
|
2996
|
+
if (snapshot.sessionMatched === false) throw new Error("The tracked Meet tab now belongs to another OpenClaw meeting session.");
|
|
2997
|
+
return {
|
|
2998
|
+
droppedLines: snapshot.droppedLines,
|
|
2999
|
+
...snapshot.epoch ? { epoch: snapshot.epoch } : {},
|
|
3000
|
+
lines: snapshot.lines
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function meetLeaveScript(meetingUrl) {
|
|
3004
|
+
const expectedMeetingUrl = normalizeMeetUrlForReuse(meetingUrl);
|
|
3005
|
+
return `() => {
|
|
3006
|
+
const expectedMeetingUrl = ${JSON.stringify(expectedMeetingUrl)};
|
|
3007
|
+
let currentMeetingUrl;
|
|
3008
|
+
try {
|
|
3009
|
+
const currentUrl = new URL(location.href);
|
|
3010
|
+
currentMeetingUrl = currentUrl.origin + currentUrl.pathname.toLowerCase().replace(/\\/$/, "");
|
|
3011
|
+
} catch {
|
|
3012
|
+
return JSON.stringify({ departed: false });
|
|
3013
|
+
}
|
|
3014
|
+
if (!expectedMeetingUrl || currentMeetingUrl !== expectedMeetingUrl) {
|
|
3015
|
+
return JSON.stringify({ departed: true, urlMatched: false });
|
|
3016
|
+
}
|
|
3017
|
+
const text = (node) => (node?.innerText || node?.textContent || "").trim();
|
|
3018
|
+
// Locale-independent fallback: Meet renders the leave control as a Material
|
|
3019
|
+
// Symbols icon whose ligature text is "call_end" in every UI language, so a
|
|
3020
|
+
// localized aria-label (e.g. "Anruf verlassen") still resolves to the button.
|
|
3021
|
+
const hasLeaveIcon = (button) => {
|
|
3022
|
+
const icon = button.querySelector ? button.querySelector("i") : null;
|
|
3023
|
+
return icon ? (icon.textContent || "").trim() === "call_end" : false;
|
|
3024
|
+
};
|
|
3025
|
+
const buttons = [...document.querySelectorAll('button')];
|
|
3026
|
+
const label = (button) => [
|
|
3027
|
+
button.getAttribute("aria-label"),
|
|
3028
|
+
button.getAttribute("data-tooltip"),
|
|
3029
|
+
text(button),
|
|
3030
|
+
]
|
|
3031
|
+
.filter(Boolean)
|
|
3032
|
+
.join(" ");
|
|
3033
|
+
const postCall = buttons.some((button) => /\\b(rejoin|return to home screen)\\b/i.test(label(button)));
|
|
3034
|
+
if (postCall) {
|
|
3035
|
+
return JSON.stringify({ departed: true, urlMatched: true });
|
|
3036
|
+
}
|
|
3037
|
+
// Managed join tabs are reused only after the English-tab gate or opened
|
|
3038
|
+
// through the English-UI helper, so follow-up labels are pinned to English.
|
|
3039
|
+
const confirmation = buttons.find((button) => {
|
|
3040
|
+
return !button.disabled && /\\bleave meeting\\b/i.test(label(button));
|
|
3041
|
+
});
|
|
3042
|
+
if (confirmation) {
|
|
3043
|
+
confirmation.click();
|
|
3044
|
+
return JSON.stringify({ departed: false, leaveAction: "confirm", urlMatched: true });
|
|
3045
|
+
}
|
|
3046
|
+
const leave = buttons.find((button) => {
|
|
3047
|
+
if (button.disabled) return false;
|
|
3048
|
+
return /leave call/i.test(label(button)) || hasLeaveIcon(button);
|
|
3049
|
+
});
|
|
3050
|
+
if (leave) {
|
|
3051
|
+
leave.click();
|
|
3052
|
+
return JSON.stringify({ departed: false, leaveAction: "leave", urlMatched: true });
|
|
3053
|
+
}
|
|
3054
|
+
return JSON.stringify({ departed: false, urlMatched: true });
|
|
3055
|
+
}`;
|
|
3056
|
+
}
|
|
3057
|
+
function parseMeetLeaveResult(result) {
|
|
3058
|
+
const raw = (result && typeof result === "object" ? result : {}).result;
|
|
3059
|
+
if (typeof raw !== "string" || !raw.trim()) return { departed: false };
|
|
3060
|
+
try {
|
|
3061
|
+
const parsed = JSON.parse(raw);
|
|
3062
|
+
const leaveAction = parsed.leaveAction === "leave" || parsed.leaveAction === "confirm" ? parsed.leaveAction : void 0;
|
|
3063
|
+
return {
|
|
3064
|
+
departed: parsed.departed === true,
|
|
3065
|
+
...leaveAction ? { leaveAction } : {},
|
|
3066
|
+
...typeof parsed.urlMatched === "boolean" ? { urlMatched: parsed.urlMatched } : {}
|
|
3067
|
+
};
|
|
3068
|
+
} catch {
|
|
3069
|
+
return { departed: false };
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
async function leaveMeetInPage(params) {
|
|
3073
|
+
const deadline = Date.now() + params.timeoutMs;
|
|
3074
|
+
let clickedLeave = false;
|
|
3075
|
+
let clickedConfirmation = false;
|
|
3076
|
+
do {
|
|
3077
|
+
const step = parseMeetLeaveResult(await params.callBrowser({
|
|
3078
|
+
method: "POST",
|
|
3079
|
+
path: "/act",
|
|
3080
|
+
body: {
|
|
3081
|
+
kind: "evaluate",
|
|
3082
|
+
targetId: params.targetId,
|
|
3083
|
+
fn: meetLeaveScript(params.meetingUrl)
|
|
3084
|
+
},
|
|
3085
|
+
timeoutMs: params.timeoutMs
|
|
3086
|
+
}));
|
|
3087
|
+
clickedLeave ||= step.leaveAction === "leave";
|
|
3088
|
+
clickedConfirmation ||= step.leaveAction === "confirm";
|
|
3089
|
+
if (step.departed || step.urlMatched !== true) return {
|
|
3090
|
+
departed: step.departed,
|
|
3091
|
+
clickedLeave,
|
|
3092
|
+
clickedConfirmation,
|
|
3093
|
+
urlMatched: step.urlMatched
|
|
3094
|
+
};
|
|
3095
|
+
if (!step.leaveAction && !clickedLeave) return {
|
|
3096
|
+
departed: false,
|
|
3097
|
+
clickedLeave,
|
|
3098
|
+
clickedConfirmation,
|
|
3099
|
+
urlMatched: true
|
|
3100
|
+
};
|
|
3101
|
+
if (!step.leaveAction) await new Promise((resolve) => {
|
|
3102
|
+
setTimeout(resolve, 100);
|
|
3103
|
+
});
|
|
3104
|
+
} while (Date.now() < deadline);
|
|
3105
|
+
return {
|
|
3106
|
+
departed: false,
|
|
3107
|
+
clickedLeave,
|
|
3108
|
+
clickedConfirmation,
|
|
3109
|
+
urlMatched: true
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
async function leaveMeetWithBrowserRequest(params) {
|
|
3113
|
+
if (!params.config.chrome.launch) return {
|
|
3114
|
+
left: false,
|
|
3115
|
+
note: "Browser leave skipped because chrome.launch is disabled."
|
|
3116
|
+
};
|
|
3117
|
+
const timeoutMs = Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 5e3);
|
|
3118
|
+
const { targetId, openedByPlugin } = params.tab;
|
|
3119
|
+
try {
|
|
3120
|
+
if (!asBrowserTabs(await params.callBrowser({
|
|
3121
|
+
method: "GET",
|
|
3122
|
+
path: "/tabs",
|
|
3123
|
+
timeoutMs
|
|
3124
|
+
})).find((entry) => entry.targetId === targetId)) return {
|
|
3125
|
+
left: true,
|
|
3126
|
+
note: "Meet tab is already closed."
|
|
3127
|
+
};
|
|
3128
|
+
let leaveResult;
|
|
3129
|
+
try {
|
|
3130
|
+
leaveResult = await leaveMeetInPage({
|
|
3131
|
+
callBrowser: params.callBrowser,
|
|
3132
|
+
meetingUrl: params.meetingUrl,
|
|
3133
|
+
targetId,
|
|
3134
|
+
timeoutMs
|
|
3135
|
+
});
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
return {
|
|
3138
|
+
left: false,
|
|
3139
|
+
note: `Browser control could not verify the Meet tab before leaving: ${error instanceof Error ? error.message : String(error)}`
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
if (leaveResult.urlMatched === false) return {
|
|
3143
|
+
left: true,
|
|
3144
|
+
note: "Meet tab moved away from this session; left its current page untouched."
|
|
3145
|
+
};
|
|
3146
|
+
if (leaveResult.urlMatched !== true) return {
|
|
3147
|
+
left: false,
|
|
3148
|
+
note: "Browser control could not verify that the tracked tab still showed this meeting."
|
|
3149
|
+
};
|
|
3150
|
+
const { clickedLeave, departed } = leaveResult;
|
|
3151
|
+
if (!openedByPlugin) return {
|
|
3152
|
+
left: departed,
|
|
3153
|
+
note: departed ? "Clicked Meet's Leave call button; kept the reused browser tab open." : clickedLeave ? "Clicked Meet's Leave call button, but could not verify departure; leave it manually." : "Could not find Meet's Leave call button in the reused browser tab; leave it manually."
|
|
3154
|
+
};
|
|
3155
|
+
await params.callBrowser({
|
|
3156
|
+
method: "DELETE",
|
|
3157
|
+
path: `/tabs/${targetId}`,
|
|
3158
|
+
timeoutMs
|
|
3159
|
+
});
|
|
3160
|
+
return {
|
|
3161
|
+
left: true,
|
|
3162
|
+
note: clickedLeave ? "Clicked Meet's Leave call button and closed the Meet tab." : "Closed the Meet tab to leave the meeting (Leave call button was not found)."
|
|
3163
|
+
};
|
|
3164
|
+
} catch (error) {
|
|
3165
|
+
return {
|
|
3166
|
+
left: false,
|
|
3167
|
+
note: `Browser control could not leave the Meet tab: ${error instanceof Error ? error.message : String(error)}`
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
async function leaveChromeMeet(params) {
|
|
3172
|
+
return await leaveMeetWithBrowserRequest({
|
|
3173
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
3174
|
+
config: params.config,
|
|
3175
|
+
meetingUrl: params.meetingUrl,
|
|
3176
|
+
tab: params.tab
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
async function readChromeMeetTranscript(params) {
|
|
3180
|
+
return await readMeetTranscriptWithBrowserRequest({
|
|
3181
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
3182
|
+
finalize: params.finalize === true,
|
|
3183
|
+
meetingUrl: params.meetingUrl,
|
|
3184
|
+
meetingSessionId: params.meetingSessionId,
|
|
3185
|
+
tab: params.tab,
|
|
3186
|
+
timeoutMs: Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 1e4)
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
async function readChromeMeetTranscriptOnNode(params) {
|
|
3190
|
+
const nodeId = params.nodeId ?? await resolveChromeNode({
|
|
3191
|
+
runtime: params.runtime,
|
|
3192
|
+
requestedNode: params.config.chromeNode.node
|
|
3193
|
+
});
|
|
3194
|
+
const timeoutMs = Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 1e4);
|
|
3195
|
+
return await readMeetTranscriptWithBrowserRequest({
|
|
3196
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3197
|
+
runtime: params.runtime,
|
|
3198
|
+
nodeId,
|
|
3199
|
+
method: request.method,
|
|
3200
|
+
path: request.path,
|
|
3201
|
+
body: request.body,
|
|
3202
|
+
timeoutMs: request.timeoutMs
|
|
3203
|
+
}),
|
|
3204
|
+
finalize: params.finalize === true,
|
|
3205
|
+
meetingUrl: params.meetingUrl,
|
|
3206
|
+
meetingSessionId: params.meetingSessionId,
|
|
3207
|
+
tab: params.tab,
|
|
3208
|
+
timeoutMs
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
async function leaveChromeMeetOnNode(params) {
|
|
3212
|
+
const nodeId = params.nodeId ?? await resolveChromeNode({
|
|
3213
|
+
runtime: params.runtime,
|
|
3214
|
+
requestedNode: params.config.chromeNode.node
|
|
3215
|
+
});
|
|
3216
|
+
return await leaveMeetWithBrowserRequest({
|
|
3217
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3218
|
+
runtime: params.runtime,
|
|
3219
|
+
nodeId,
|
|
3220
|
+
method: request.method,
|
|
3221
|
+
path: request.path,
|
|
3222
|
+
body: request.body,
|
|
3223
|
+
timeoutMs: request.timeoutMs
|
|
3224
|
+
}),
|
|
3225
|
+
config: params.config,
|
|
3226
|
+
meetingUrl: params.meetingUrl,
|
|
3227
|
+
tab: params.tab
|
|
3228
|
+
});
|
|
3229
|
+
}
|
|
3230
|
+
async function openMeetWithBrowserProxy(params) {
|
|
3231
|
+
return await openMeetWithBrowserRequest({
|
|
3232
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3233
|
+
runtime: params.runtime,
|
|
3234
|
+
nodeId: params.nodeId,
|
|
3235
|
+
method: request.method,
|
|
3236
|
+
path: request.path,
|
|
3237
|
+
body: request.body,
|
|
3238
|
+
timeoutMs: request.timeoutMs
|
|
3239
|
+
}),
|
|
3240
|
+
config: params.config,
|
|
3241
|
+
mode: params.mode,
|
|
3242
|
+
meetingSessionId: params.meetingSessionId,
|
|
3243
|
+
url: params.url
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
async function openMeetWithBrowserRequest(params) {
|
|
3247
|
+
if (!params.config.chrome.launch) return { launched: false };
|
|
3248
|
+
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
3249
|
+
let targetId;
|
|
3250
|
+
let tab;
|
|
3251
|
+
let openUrl = params.url;
|
|
3252
|
+
let openedByPlugin = false;
|
|
3253
|
+
if (params.config.chrome.reuseExistingTab) {
|
|
3254
|
+
const matchingTabs = asBrowserTabs(await params.callBrowser({
|
|
3255
|
+
method: "GET",
|
|
3256
|
+
path: "/tabs",
|
|
3257
|
+
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
3258
|
+
})).filter((entry) => isSameMeetUrlForReuse(entry.url, params.url));
|
|
3259
|
+
const requestedAuthUser = readMeetAuthUser(params.url);
|
|
3260
|
+
tab = matchingTabs.find((entry) => isEnglishMeetTab(entry.url) && (!requestedAuthUser || readMeetAuthUser(entry.url) === requestedAuthUser));
|
|
3261
|
+
if (!tab) {
|
|
3262
|
+
if (!new URL(params.url).searchParams.has("authuser")) openUrl = matchingTabs.find((entry) => entry.url)?.url ?? params.url;
|
|
3263
|
+
}
|
|
3264
|
+
targetId = tab?.targetId;
|
|
3265
|
+
if (tab && targetId) await params.callBrowser({
|
|
3266
|
+
method: "POST",
|
|
3267
|
+
path: "/tabs/focus",
|
|
3268
|
+
body: { targetId },
|
|
3269
|
+
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
if (!targetId) {
|
|
3273
|
+
tab = readBrowserTab(await params.callBrowser({
|
|
3274
|
+
method: "POST",
|
|
3275
|
+
path: "/tabs/open",
|
|
3276
|
+
body: { url: forceMeetEnglishUi(openUrl) },
|
|
3277
|
+
timeoutMs
|
|
3278
|
+
}));
|
|
3279
|
+
targetId = tab?.targetId;
|
|
3280
|
+
openedByPlugin = Boolean(targetId);
|
|
3281
|
+
}
|
|
3282
|
+
if (!targetId) return {
|
|
3283
|
+
launched: true,
|
|
3284
|
+
browser: {
|
|
3285
|
+
status: "browser-control",
|
|
3286
|
+
notes: ["Browser proxy opened Meet but did not return a targetId."],
|
|
3287
|
+
browserUrl: tab?.url,
|
|
3288
|
+
browserTitle: tab?.title
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
const tabIdentity = {
|
|
3292
|
+
targetId,
|
|
3293
|
+
openedByPlugin
|
|
3294
|
+
};
|
|
3295
|
+
const permissionNotes = await grantMeetMediaPermissions({
|
|
3296
|
+
allowMicrophone: isGoogleMeetTalkBackMode$2(params.mode),
|
|
3297
|
+
callBrowser: params.callBrowser,
|
|
3298
|
+
targetId,
|
|
3299
|
+
timeoutMs
|
|
2662
3300
|
});
|
|
2663
3301
|
const deadline = Date.now() + Math.max(0, params.config.chrome.waitForInCallMs);
|
|
2664
3302
|
let browser = {
|
|
@@ -2677,6 +3315,7 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2677
3315
|
targetId,
|
|
2678
3316
|
fn: meetStatusScript({
|
|
2679
3317
|
allowMicrophone: isGoogleMeetTalkBackMode$2(params.mode),
|
|
3318
|
+
captionSessionId: params.meetingSessionId,
|
|
2680
3319
|
captureCaptions: params.mode === "transcribe",
|
|
2681
3320
|
guestName: params.config.chrome.guestName,
|
|
2682
3321
|
autoJoin: params.config.chrome.autoJoin
|
|
@@ -2686,11 +3325,13 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2686
3325
|
})) ?? browser, permissionNotes);
|
|
2687
3326
|
if (browser?.inCall === true && (!isGoogleMeetTalkBackMode$2(params.mode) || browser.micMuted !== true)) return {
|
|
2688
3327
|
launched: true,
|
|
2689
|
-
browser
|
|
3328
|
+
browser,
|
|
3329
|
+
tab: tabIdentity
|
|
2690
3330
|
};
|
|
2691
3331
|
if (browser?.manualActionRequired === true) return {
|
|
2692
3332
|
launched: true,
|
|
2693
|
-
browser
|
|
3333
|
+
browser,
|
|
3334
|
+
tab: tabIdentity
|
|
2694
3335
|
};
|
|
2695
3336
|
} catch (error) {
|
|
2696
3337
|
browser = {
|
|
@@ -2710,7 +3351,8 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2710
3351
|
} while (Date.now() < deadline);
|
|
2711
3352
|
return {
|
|
2712
3353
|
launched: true,
|
|
2713
|
-
browser
|
|
3354
|
+
browser,
|
|
3355
|
+
tab: tabIdentity
|
|
2714
3356
|
};
|
|
2715
3357
|
}
|
|
2716
3358
|
function isRecoverableMeetTab(tab, url) {
|
|
@@ -2718,6 +3360,13 @@ function isRecoverableMeetTab(tab, url) {
|
|
|
2718
3360
|
if (normalizeMeetUrlForReuse(tab.url)) return true;
|
|
2719
3361
|
return (tab.url ?? "").startsWith("https://accounts.google.com/") && /sign in|google accounts|meet/i.test(tab.title ?? "");
|
|
2720
3362
|
}
|
|
3363
|
+
function findRecoverableMeetTab(tabs, url) {
|
|
3364
|
+
const candidates = tabs.filter((tab) => isRecoverableMeetTab(tab, url));
|
|
3365
|
+
if (!url) return candidates[0];
|
|
3366
|
+
const requestedAuthUser = readMeetAuthUser(url);
|
|
3367
|
+
const accountCandidates = requestedAuthUser ? candidates.filter((tab) => readMeetAuthUser(tab.url) === requestedAuthUser) : candidates;
|
|
3368
|
+
return accountCandidates.find((tab) => isEnglishMeetTab(tab.url)) ?? accountCandidates[0];
|
|
3369
|
+
}
|
|
2721
3370
|
async function inspectRecoverableMeetTab(params) {
|
|
2722
3371
|
const allowMicrophone = params.mode !== "transcribe";
|
|
2723
3372
|
await params.callBrowser({
|
|
@@ -2726,6 +3375,23 @@ async function inspectRecoverableMeetTab(params) {
|
|
|
2726
3375
|
body: { targetId: params.targetId },
|
|
2727
3376
|
timeoutMs: Math.min(params.timeoutMs, 5e3)
|
|
2728
3377
|
});
|
|
3378
|
+
if (normalizeMeetUrlForReuse(params.tab.url) && !isEnglishMeetTab(params.tab.url)) {
|
|
3379
|
+
const manualActionMessage = "The existing Meet tab is not pinned to English. Open the meeting with ?hl=en, then retry recovery.";
|
|
3380
|
+
return {
|
|
3381
|
+
found: true,
|
|
3382
|
+
targetId: params.targetId,
|
|
3383
|
+
tab: params.tab,
|
|
3384
|
+
browser: {
|
|
3385
|
+
status: "browser-control",
|
|
3386
|
+
browserUrl: params.tab.url,
|
|
3387
|
+
browserTitle: params.tab.title,
|
|
3388
|
+
manualActionRequired: true,
|
|
3389
|
+
manualActionReason: "meet-locale-required",
|
|
3390
|
+
manualActionMessage
|
|
3391
|
+
},
|
|
3392
|
+
message: manualActionMessage
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
2729
3395
|
const permissionNotes = params.readOnly ? [] : await grantMeetMediaPermissions({
|
|
2730
3396
|
allowMicrophone,
|
|
2731
3397
|
callBrowser: params.callBrowser,
|
|
@@ -2763,11 +3429,12 @@ async function inspectRecoverableMeetTab(params) {
|
|
|
2763
3429
|
}
|
|
2764
3430
|
async function recoverCurrentMeetTab(params) {
|
|
2765
3431
|
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
2766
|
-
const
|
|
3432
|
+
const callBrowser = await resolveLocalBrowserRequest(params.runtime);
|
|
3433
|
+
const tab = findRecoverableMeetTab(asBrowserTabs(await callBrowser({
|
|
2767
3434
|
method: "GET",
|
|
2768
3435
|
path: "/tabs",
|
|
2769
3436
|
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
2770
|
-
}))
|
|
3437
|
+
})), params.url);
|
|
2771
3438
|
const targetId = tab?.targetId;
|
|
2772
3439
|
if (!tab || !targetId) return {
|
|
2773
3440
|
transport: "chrome",
|
|
@@ -2778,7 +3445,7 @@ async function recoverCurrentMeetTab(params) {
|
|
|
2778
3445
|
return {
|
|
2779
3446
|
transport: "chrome",
|
|
2780
3447
|
...await inspectRecoverableMeetTab({
|
|
2781
|
-
callBrowser
|
|
3448
|
+
callBrowser,
|
|
2782
3449
|
config: params.config,
|
|
2783
3450
|
mode: params.mode,
|
|
2784
3451
|
readOnly: params.readOnly,
|
|
@@ -2794,13 +3461,13 @@ async function recoverCurrentMeetTabOnNode(params) {
|
|
|
2794
3461
|
requestedNode: params.config.chromeNode.node
|
|
2795
3462
|
});
|
|
2796
3463
|
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
2797
|
-
const tab = asBrowserTabs(await callBrowserProxyOnNode({
|
|
3464
|
+
const tab = findRecoverableMeetTab(asBrowserTabs(await callBrowserProxyOnNode({
|
|
2798
3465
|
runtime: params.runtime,
|
|
2799
3466
|
nodeId,
|
|
2800
3467
|
method: "GET",
|
|
2801
3468
|
path: "/tabs",
|
|
2802
3469
|
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
2803
|
-
}))
|
|
3470
|
+
})), params.url);
|
|
2804
3471
|
const targetId = tab?.targetId;
|
|
2805
3472
|
if (!tab || !targetId) return {
|
|
2806
3473
|
transport: "chrome-node",
|
|
@@ -2854,6 +3521,7 @@ async function launchChromeMeetOnNode(params) {
|
|
|
2854
3521
|
nodeId,
|
|
2855
3522
|
config: params.config,
|
|
2856
3523
|
mode: params.mode,
|
|
3524
|
+
meetingSessionId: params.meetingSessionId,
|
|
2857
3525
|
url: params.url
|
|
2858
3526
|
});
|
|
2859
3527
|
const result = parseNodeStartResult(await params.runtime.nodes.invoke({
|
|
@@ -2895,19 +3563,22 @@ async function launchChromeMeetOnNode(params) {
|
|
|
2895
3563
|
nodeId,
|
|
2896
3564
|
launched: browserControl.launched || result.launched === true,
|
|
2897
3565
|
audioBridge: bridge,
|
|
2898
|
-
browser: browserControl.browser ?? result.browser
|
|
3566
|
+
browser: browserControl.browser ?? result.browser,
|
|
3567
|
+
tab: browserControl.tab
|
|
2899
3568
|
};
|
|
2900
3569
|
}
|
|
2901
3570
|
if (result.audioBridge?.type === "external-command") return {
|
|
2902
3571
|
nodeId,
|
|
2903
3572
|
launched: browserControl.launched || result.launched === true,
|
|
2904
3573
|
audioBridge: { type: "external-command" },
|
|
2905
|
-
browser: browserControl.browser ?? result.browser
|
|
3574
|
+
browser: browserControl.browser ?? result.browser,
|
|
3575
|
+
tab: browserControl.tab
|
|
2906
3576
|
};
|
|
2907
3577
|
return {
|
|
2908
3578
|
nodeId,
|
|
2909
3579
|
launched: browserControl.launched || result.launched === true,
|
|
2910
|
-
browser: browserControl.browser ?? result.browser
|
|
3580
|
+
browser: browserControl.browser ?? result.browser,
|
|
3581
|
+
tab: browserControl.tab
|
|
2911
3582
|
};
|
|
2912
3583
|
}
|
|
2913
3584
|
//#endregion
|
|
@@ -3075,28 +3746,33 @@ function nowIso() {
|
|
|
3075
3746
|
function buildTwilioVoiceCallSessionKey(meetingSessionId) {
|
|
3076
3747
|
return `voice:google-meet:${meetingSessionId}`;
|
|
3077
3748
|
}
|
|
3078
|
-
function normalizeMeetUrl(input) {
|
|
3079
|
-
const raw = normalizeOptionalString(input);
|
|
3080
|
-
if (!raw) throw new Error("url required");
|
|
3081
|
-
let url;
|
|
3082
|
-
try {
|
|
3083
|
-
url = new URL(raw);
|
|
3084
|
-
} catch {
|
|
3085
|
-
throw new Error("url must be a valid Google Meet URL");
|
|
3086
|
-
}
|
|
3087
|
-
if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "meet.google.com") throw new Error("url must be an explicit https://meet.google.com/... URL");
|
|
3088
|
-
if (!/^\/[a-z]{3}-[a-z]{4}-[a-z]{3}(?:$|[/?#])/i.test(url.pathname)) throw new Error("url must include a Google Meet meeting code");
|
|
3089
|
-
return url.toString();
|
|
3090
|
-
}
|
|
3091
3749
|
function resolveTransport(input, config) {
|
|
3092
3750
|
return input ?? config.defaultTransport;
|
|
3093
3751
|
}
|
|
3094
3752
|
function resolveMode(input, config) {
|
|
3095
3753
|
return input === "realtime" ? "agent" : input ?? config.defaultMode;
|
|
3096
3754
|
}
|
|
3755
|
+
function resolveSessionAgentId(request, config) {
|
|
3756
|
+
return normalizeAgentId(request.agentId ?? config.realtime.agentId);
|
|
3757
|
+
}
|
|
3758
|
+
function withSessionAgentConfig(config, agentId) {
|
|
3759
|
+
return config.realtime.agentId === agentId ? config : {
|
|
3760
|
+
...config,
|
|
3761
|
+
realtime: {
|
|
3762
|
+
...config.realtime,
|
|
3763
|
+
agentId
|
|
3764
|
+
}
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3097
3767
|
function isGoogleMeetTalkBackMode$1(mode) {
|
|
3098
3768
|
return mode === "agent" || mode === "bidi";
|
|
3099
3769
|
}
|
|
3770
|
+
function isBrowserTransport(transport) {
|
|
3771
|
+
return transport === "chrome" || transport === "chrome-node";
|
|
3772
|
+
}
|
|
3773
|
+
function isReusableMeetSession(session, params) {
|
|
3774
|
+
return session.state === "active" && isSameMeetUrlForReuse(session.url, params.url) && session.transport === params.transport && session.mode === params.mode && session.agentId === params.agentId;
|
|
3775
|
+
}
|
|
3100
3776
|
function hasRealtimeAudioOutputAdvanced(health, startOutputBytes) {
|
|
3101
3777
|
return (health?.lastOutputBytes ?? 0) > startOutputBytes;
|
|
3102
3778
|
}
|
|
@@ -3181,9 +3857,16 @@ async function commandExists(runtime, command) {
|
|
|
3181
3857
|
}
|
|
3182
3858
|
var GoogleMeetRuntime = class {
|
|
3183
3859
|
#sessions = /* @__PURE__ */ new Map();
|
|
3860
|
+
#sessionLeaves = /* @__PURE__ */ new Map();
|
|
3861
|
+
#browserMeetingLocks = /* @__PURE__ */ new Map();
|
|
3862
|
+
#createdBrowserTabs = /* @__PURE__ */ new Map();
|
|
3184
3863
|
#sessionStops = /* @__PURE__ */ new Map();
|
|
3185
3864
|
#sessionSpeakers = /* @__PURE__ */ new Map();
|
|
3186
3865
|
#sessionHealth = /* @__PURE__ */ new Map();
|
|
3866
|
+
#transcripts = /* @__PURE__ */ new Map();
|
|
3867
|
+
#transcriptCaptures = /* @__PURE__ */ new Map();
|
|
3868
|
+
#transcriptFinalizing = /* @__PURE__ */ new Set();
|
|
3869
|
+
#retiredTranscripts = /* @__PURE__ */ new Set();
|
|
3187
3870
|
#voiceCallGateway;
|
|
3188
3871
|
constructor(params) {
|
|
3189
3872
|
this.params = params;
|
|
@@ -3210,6 +3893,28 @@ var GoogleMeetRuntime = class {
|
|
|
3210
3893
|
session
|
|
3211
3894
|
} : { found: false };
|
|
3212
3895
|
}
|
|
3896
|
+
async transcript(sessionId, options = {}) {
|
|
3897
|
+
const session = this.#sessions.get(sessionId);
|
|
3898
|
+
if (!session) return { found: false };
|
|
3899
|
+
if (session.mode !== "transcribe") throw new Error("transcript is only available for transcribe-mode sessions");
|
|
3900
|
+
const sinceIndex = options.sinceIndex ?? 0;
|
|
3901
|
+
if (!Number.isSafeInteger(sinceIndex) || sinceIndex < 0) throw new Error("sinceIndex must be a non-negative safe integer");
|
|
3902
|
+
if (session.state === "active" && !this.#transcriptFinalizing.has(session.id)) await this.#captureTranscriptSnapshot(session);
|
|
3903
|
+
const snapshot = this.#transcripts.get(sessionId) ?? {
|
|
3904
|
+
droppedLines: 0,
|
|
3905
|
+
lines: []
|
|
3906
|
+
};
|
|
3907
|
+
const startIndex = Math.max(sinceIndex, snapshot.droppedLines);
|
|
3908
|
+
return {
|
|
3909
|
+
found: true,
|
|
3910
|
+
sessionId,
|
|
3911
|
+
startIndex,
|
|
3912
|
+
nextIndex: snapshot.droppedLines + snapshot.lines.length,
|
|
3913
|
+
droppedLines: snapshot.droppedLines,
|
|
3914
|
+
...session.transcriptEvicted ? { evicted: true } : {},
|
|
3915
|
+
lines: snapshot.lines.slice(startIndex - snapshot.droppedLines)
|
|
3916
|
+
};
|
|
3917
|
+
}
|
|
3213
3918
|
async setupStatus(options = {}) {
|
|
3214
3919
|
const transport = resolveTransport(options.transport, this.params.config);
|
|
3215
3920
|
const mode = resolveMode(options.mode, this.params.config);
|
|
@@ -3273,10 +3978,12 @@ var GoogleMeetRuntime = class {
|
|
|
3273
3978
|
return status;
|
|
3274
3979
|
}
|
|
3275
3980
|
async createViaBrowser() {
|
|
3276
|
-
|
|
3981
|
+
const result = await createMeetWithBrowserProxyOnNode({
|
|
3277
3982
|
runtime: this.params.runtime,
|
|
3278
3983
|
config: this.params.config
|
|
3279
3984
|
});
|
|
3985
|
+
if (result.openedByPlugin && result.targetId) this.#createdBrowserTabs.set(`${result.nodeId}:${result.targetId}`, result.meetingUri);
|
|
3986
|
+
return result;
|
|
3280
3987
|
}
|
|
3281
3988
|
async recoverCurrentTab(request = {}) {
|
|
3282
3989
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
@@ -3288,6 +3995,7 @@ var GoogleMeetRuntime = class {
|
|
|
3288
3995
|
url
|
|
3289
3996
|
});
|
|
3290
3997
|
return recoverCurrentMeetTab({
|
|
3998
|
+
runtime: this.params.runtime,
|
|
3291
3999
|
config: this.params.config,
|
|
3292
4000
|
url
|
|
3293
4001
|
});
|
|
@@ -3296,7 +4004,51 @@ var GoogleMeetRuntime = class {
|
|
|
3296
4004
|
const url = normalizeMeetUrl(request.url);
|
|
3297
4005
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
3298
4006
|
const mode = resolveMode(request.mode, this.params.config);
|
|
3299
|
-
|
|
4007
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
4008
|
+
if (!isBrowserTransport(transport)) return await this.#joinUnlocked(request, {
|
|
4009
|
+
url,
|
|
4010
|
+
transport,
|
|
4011
|
+
mode,
|
|
4012
|
+
agentId
|
|
4013
|
+
});
|
|
4014
|
+
return await this.#withBrowserMeetingLock(transport, url, async () => await this.#joinUnlocked(request, {
|
|
4015
|
+
url,
|
|
4016
|
+
transport,
|
|
4017
|
+
mode,
|
|
4018
|
+
agentId
|
|
4019
|
+
}));
|
|
4020
|
+
}
|
|
4021
|
+
async #joinUnlocked(request, resolved) {
|
|
4022
|
+
const { url, transport, mode, agentId } = resolved;
|
|
4023
|
+
const activeSessions = this.list().filter((session) => session.state === "active" && isSameMeetUrlForReuse(session.url, url) && session.transport === transport);
|
|
4024
|
+
const retainedBrowserTabs = [];
|
|
4025
|
+
if (isBrowserTransport(transport) && isGoogleMeetTalkBackMode$1(mode)) for (const session of activeSessions) {
|
|
4026
|
+
if (!isGoogleMeetTalkBackMode$1(session.mode) || isReusableMeetSession(session, {
|
|
4027
|
+
url,
|
|
4028
|
+
transport,
|
|
4029
|
+
mode,
|
|
4030
|
+
agentId
|
|
4031
|
+
})) continue;
|
|
4032
|
+
const tab = this.params.config.chrome.reuseExistingTab ? session.chrome?.browserTab : void 0;
|
|
4033
|
+
const keepBrowserParticipant = Boolean(tab) || session.chrome?.launched === false;
|
|
4034
|
+
if (tab) retainedBrowserTabs.push({
|
|
4035
|
+
session,
|
|
4036
|
+
tab
|
|
4037
|
+
});
|
|
4038
|
+
try {
|
|
4039
|
+
if ((await this.#leaveUnlocked(session.id, keepBrowserParticipant ? { keepBrowserTab: true } : void 0)).browserLeft === false) throw new Error("Could not leave the previous Meet browser tab before reassignment.");
|
|
4040
|
+
} catch (error) {
|
|
4041
|
+
await this.#settleRetainedBrowserTabs(retainedBrowserTabs);
|
|
4042
|
+
throw error;
|
|
4043
|
+
}
|
|
4044
|
+
noteSession(session, "Ended before the same Meet tab was reassigned to another agent.");
|
|
4045
|
+
}
|
|
4046
|
+
let reusable = activeSessions.find((session) => isReusableMeetSession(session, {
|
|
4047
|
+
url,
|
|
4048
|
+
transport,
|
|
4049
|
+
mode,
|
|
4050
|
+
agentId
|
|
4051
|
+
}));
|
|
3300
4052
|
if (reusable?.transport === "twilio") {
|
|
3301
4053
|
await this.#refreshTwilioVoiceCallStatus(reusable);
|
|
3302
4054
|
if (reusable.state !== "active") reusable = void 0;
|
|
@@ -3319,6 +4071,7 @@ var GoogleMeetRuntime = class {
|
|
|
3319
4071
|
url,
|
|
3320
4072
|
transport,
|
|
3321
4073
|
mode,
|
|
4074
|
+
agentId,
|
|
3322
4075
|
state: "active",
|
|
3323
4076
|
createdAt,
|
|
3324
4077
|
updatedAt: createdAt,
|
|
@@ -3335,40 +4088,60 @@ var GoogleMeetRuntime = class {
|
|
|
3335
4088
|
};
|
|
3336
4089
|
try {
|
|
3337
4090
|
if (transport === "chrome" || transport === "chrome-node") {
|
|
3338
|
-
const chromeConfig =
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
4091
|
+
const chromeConfig = withSessionAgentConfig(this.params.config, agentId);
|
|
4092
|
+
let result;
|
|
4093
|
+
try {
|
|
4094
|
+
result = transport === "chrome-node" ? await launchChromeMeetOnNode({
|
|
4095
|
+
runtime: this.params.runtime,
|
|
4096
|
+
config: chromeConfig,
|
|
4097
|
+
fullConfig: this.params.fullConfig,
|
|
4098
|
+
meetingSessionId: session.id,
|
|
4099
|
+
requesterSessionKey: request.requesterSessionKey,
|
|
4100
|
+
mode,
|
|
4101
|
+
url,
|
|
4102
|
+
logger: this.params.logger
|
|
4103
|
+
}) : await launchChromeMeet({
|
|
4104
|
+
runtime: this.params.runtime,
|
|
4105
|
+
config: chromeConfig,
|
|
4106
|
+
fullConfig: this.params.fullConfig,
|
|
4107
|
+
meetingSessionId: session.id,
|
|
4108
|
+
requesterSessionKey: request.requesterSessionKey,
|
|
4109
|
+
mode,
|
|
4110
|
+
url,
|
|
4111
|
+
logger: this.params.logger
|
|
4112
|
+
});
|
|
4113
|
+
} catch (error) {
|
|
4114
|
+
await this.#settleRetainedBrowserTabs(retainedBrowserTabs);
|
|
4115
|
+
throw error;
|
|
4116
|
+
}
|
|
4117
|
+
const resultNodeId = "nodeId" in result ? result.nodeId : void 0;
|
|
4118
|
+
const browserTab = this.#inheritBrowserTabOwnership({
|
|
4119
|
+
transport,
|
|
4120
|
+
nodeId: resultNodeId,
|
|
4121
|
+
meetingUrl: url,
|
|
4122
|
+
tab: result.tab
|
|
3363
4123
|
});
|
|
3364
4124
|
session.chrome = {
|
|
3365
4125
|
audioBackend: this.params.config.chrome.audioBackend,
|
|
3366
4126
|
launched: result.launched,
|
|
3367
|
-
nodeId:
|
|
4127
|
+
nodeId: resultNodeId,
|
|
3368
4128
|
browserProfile: this.params.config.chrome.browserProfile,
|
|
4129
|
+
browserTab,
|
|
3369
4130
|
health: "browser" in result ? result.browser : void 0
|
|
3370
4131
|
};
|
|
3371
4132
|
this.#attachChromeAudioBridge(session, result.audioBridge);
|
|
4133
|
+
if (!await this.#settleRetainedBrowserTabs(retainedBrowserTabs, browserTab ? {
|
|
4134
|
+
transport,
|
|
4135
|
+
nodeId: resultNodeId,
|
|
4136
|
+
tab: browserTab
|
|
4137
|
+
} : void 0)) {
|
|
4138
|
+
try {
|
|
4139
|
+
await this.#leaveSession(session);
|
|
4140
|
+
} catch (error) {
|
|
4141
|
+
this.params.logger.warn(`[google-meet] replacement cleanup failed: ${formatErrorMessage(error)}`);
|
|
4142
|
+
}
|
|
4143
|
+
throw new Error("Could not leave the previous Meet browser tab before reassignment.");
|
|
4144
|
+
}
|
|
3372
4145
|
session.notes.push(result.audioBridge ? transport === "chrome-node" ? "Chrome node transport joins as the signed-in Google profile on the selected node and routes realtime audio through the node bridge." : "Chrome transport joins as the signed-in Google profile and routes realtime audio through the configured bridge." : isGoogleMeetTalkBackMode$1(mode) ? "Chrome transport joins as the signed-in Google profile and expects BlackHole 2ch audio routing." : "Chrome transport joins as the signed-in Google profile without starting the realtime audio bridge.");
|
|
3373
4146
|
this.#refreshSpeechReadiness(session);
|
|
3374
4147
|
} else {
|
|
@@ -3379,6 +4152,7 @@ var GoogleMeetRuntime = class {
|
|
|
3379
4152
|
dtmfSequence: request.dtmfSequence ?? this.params.config.twilio.defaultDtmfSequence
|
|
3380
4153
|
});
|
|
3381
4154
|
const dtmfSequence = request.dtmfSequence || this.params.config.twilio.defaultDtmfSequence ? rawDtmfSequence : prefixDtmfWait(rawDtmfSequence, this.params.config.voiceCall.dtmfDelayMs);
|
|
4155
|
+
const delegatedAgentId = Boolean(normalizeOptionalString(request.agentId) || normalizeOptionalString(this.params.config.realtime.agentId)) ? agentId : void 0;
|
|
3382
4156
|
const voiceCallResult = this.params.config.voiceCall.enabled ? await joinMeetViaVoiceCallGateway({
|
|
3383
4157
|
config: this.params.config,
|
|
3384
4158
|
gateway: this.#voiceCallGateway,
|
|
@@ -3386,8 +4160,8 @@ var GoogleMeetRuntime = class {
|
|
|
3386
4160
|
dtmfSequence,
|
|
3387
4161
|
logger: this.params.logger,
|
|
3388
4162
|
...request.requesterSessionKey ? { requesterSessionKey: request.requesterSessionKey } : {},
|
|
3389
|
-
agentId:
|
|
3390
|
-
sessionKey:
|
|
4163
|
+
agentId: delegatedAgentId,
|
|
4164
|
+
sessionKey: delegatedAgentId ? `agent:${delegatedAgentId}:google-meet:${session.id}` : buildTwilioVoiceCallSessionKey(session.id),
|
|
3391
4165
|
message: isGoogleMeetTalkBackMode$1(mode) ? request.message ?? this.params.config.voiceCall.introMessage ?? this.params.config.realtime.introMessage : void 0
|
|
3392
4166
|
}) : void 0;
|
|
3393
4167
|
delegatedTwilioSpoken = Boolean(voiceCallResult?.introSent);
|
|
@@ -3417,28 +4191,218 @@ var GoogleMeetRuntime = class {
|
|
|
3417
4191
|
spoken: transport === "twilio" ? delegatedTwilioSpoken : isGoogleMeetTalkBackMode$1(mode) && speechInstructions ? await this.#speakWhenReady(session, speechInstructions) : false
|
|
3418
4192
|
};
|
|
3419
4193
|
}
|
|
3420
|
-
async leave(sessionId) {
|
|
4194
|
+
async leave(sessionId, opts) {
|
|
4195
|
+
const session = this.#sessions.get(sessionId);
|
|
4196
|
+
if (!session) return { found: false };
|
|
4197
|
+
if (!isBrowserTransport(session.transport)) return await this.#leaveUnlocked(sessionId, opts);
|
|
4198
|
+
return await this.#withBrowserMeetingLock(session.transport, session.url, async () => await this.#leaveUnlocked(sessionId, opts));
|
|
4199
|
+
}
|
|
4200
|
+
async #leaveUnlocked(sessionId, opts) {
|
|
4201
|
+
const inFlight = this.#sessionLeaves.get(sessionId);
|
|
4202
|
+
if (inFlight) return await inFlight;
|
|
3421
4203
|
const session = this.#sessions.get(sessionId);
|
|
3422
4204
|
if (!session) return { found: false };
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
4205
|
+
if (session.state === "ended") return {
|
|
4206
|
+
found: true,
|
|
4207
|
+
session,
|
|
4208
|
+
...session.browserLeft === void 0 ? {} : { browserLeft: session.browserLeft }
|
|
4209
|
+
};
|
|
4210
|
+
const leave = this.#leaveSession(session, opts);
|
|
4211
|
+
this.#sessionLeaves.set(sessionId, leave);
|
|
4212
|
+
try {
|
|
4213
|
+
return await leave;
|
|
4214
|
+
} finally {
|
|
4215
|
+
if (this.#sessionLeaves.get(sessionId) === leave) this.#sessionLeaves.delete(sessionId);
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
async #withBrowserMeetingLock(transport, url, operation) {
|
|
4219
|
+
const key = `${transport}:${normalizeMeetUrlForReuse(url) ?? url}`;
|
|
4220
|
+
const previous = this.#browserMeetingLocks.get(key) ?? Promise.resolve();
|
|
4221
|
+
let release;
|
|
4222
|
+
const gate = new Promise((resolve) => {
|
|
4223
|
+
release = resolve;
|
|
4224
|
+
});
|
|
4225
|
+
const tail = previous.then(() => gate);
|
|
4226
|
+
this.#browserMeetingLocks.set(key, tail);
|
|
4227
|
+
await previous;
|
|
4228
|
+
try {
|
|
4229
|
+
return await operation();
|
|
4230
|
+
} finally {
|
|
4231
|
+
release?.();
|
|
4232
|
+
if (this.#browserMeetingLocks.get(key) === tail) this.#browserMeetingLocks.delete(key);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
async #leaveSession(session, opts) {
|
|
4236
|
+
if (session.mode === "transcribe") {
|
|
4237
|
+
this.#transcriptFinalizing.add(session.id);
|
|
4238
|
+
await this.#captureTranscriptSnapshot(session, { finalize: true }).catch((error) => {
|
|
4239
|
+
this.params.logger.debug?.(`[google-meet] final transcript snapshot ignored: ${formatErrorMessage(error)}`);
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
session.state = "ended";
|
|
4243
|
+
session.updatedAt = nowIso();
|
|
4244
|
+
const stop = this.#sessionStops.get(session.id);
|
|
4245
|
+
this.#sessionStops.delete(session.id);
|
|
4246
|
+
this.#sessionSpeakers.delete(session.id);
|
|
4247
|
+
this.#sessionHealth.delete(session.id);
|
|
4248
|
+
let browserLeft;
|
|
4249
|
+
try {
|
|
4250
|
+
await stop?.();
|
|
4251
|
+
} finally {
|
|
3428
4252
|
try {
|
|
3429
|
-
await
|
|
4253
|
+
if (!opts?.keepBrowserTab) browserLeft = await this.#releaseBrowserTab(session);
|
|
3430
4254
|
} finally {
|
|
3431
|
-
session.
|
|
3432
|
-
session.
|
|
4255
|
+
this.#retireTranscript(session.id);
|
|
4256
|
+
this.#transcriptFinalizing.delete(session.id);
|
|
3433
4257
|
}
|
|
3434
4258
|
}
|
|
3435
|
-
session.state = "ended";
|
|
3436
|
-
session.updatedAt = nowIso();
|
|
3437
4259
|
return {
|
|
3438
4260
|
found: true,
|
|
3439
|
-
session
|
|
4261
|
+
session,
|
|
4262
|
+
...browserLeft === void 0 ? {} : { browserLeft }
|
|
3440
4263
|
};
|
|
3441
4264
|
}
|
|
4265
|
+
async #captureTranscriptSnapshot(session, options = {}) {
|
|
4266
|
+
const capture = (this.#transcriptCaptures.get(session.id) ?? Promise.resolve()).catch(() => {}).then(async () => {
|
|
4267
|
+
if (!isBrowserTransport(session.transport) || session.mode !== "transcribe") return;
|
|
4268
|
+
const tab = session.chrome?.browserTab;
|
|
4269
|
+
if (!tab) return;
|
|
4270
|
+
const snapshot = session.transport === "chrome-node" ? await readChromeMeetTranscriptOnNode({
|
|
4271
|
+
runtime: this.params.runtime,
|
|
4272
|
+
nodeId: session.chrome?.nodeId,
|
|
4273
|
+
config: this.params.config,
|
|
4274
|
+
...options.finalize === void 0 ? {} : { finalize: options.finalize },
|
|
4275
|
+
meetingUrl: session.url,
|
|
4276
|
+
meetingSessionId: session.id,
|
|
4277
|
+
tab
|
|
4278
|
+
}) : await readChromeMeetTranscript({
|
|
4279
|
+
runtime: this.params.runtime,
|
|
4280
|
+
config: this.params.config,
|
|
4281
|
+
...options.finalize === void 0 ? {} : { finalize: options.finalize },
|
|
4282
|
+
meetingUrl: session.url,
|
|
4283
|
+
meetingSessionId: session.id,
|
|
4284
|
+
tab
|
|
4285
|
+
});
|
|
4286
|
+
this.#mergeTranscriptSnapshot(session.id, snapshot);
|
|
4287
|
+
});
|
|
4288
|
+
this.#transcriptCaptures.set(session.id, capture);
|
|
4289
|
+
try {
|
|
4290
|
+
await capture;
|
|
4291
|
+
} finally {
|
|
4292
|
+
if (this.#transcriptCaptures.get(session.id) === capture) this.#transcriptCaptures.delete(session.id);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
#mergeTranscriptSnapshot(sessionId, snapshot) {
|
|
4296
|
+
const pageNextIndex = snapshot.droppedLines + snapshot.lines.length;
|
|
4297
|
+
const retained = this.#transcripts.get(sessionId);
|
|
4298
|
+
if (!retained) {
|
|
4299
|
+
this.#transcripts.set(sessionId, {
|
|
4300
|
+
droppedLines: snapshot.droppedLines,
|
|
4301
|
+
lines: snapshot.lines,
|
|
4302
|
+
pageEpoch: snapshot.epoch,
|
|
4303
|
+
pageNextIndex
|
|
4304
|
+
});
|
|
4305
|
+
return;
|
|
4306
|
+
}
|
|
4307
|
+
if (retained.pageEpoch !== snapshot.epoch) {
|
|
4308
|
+
retained.droppedLines += snapshot.droppedLines;
|
|
4309
|
+
retained.lines.push(...snapshot.lines);
|
|
4310
|
+
retained.pageEpoch = snapshot.epoch;
|
|
4311
|
+
retained.pageNextIndex = pageNextIndex;
|
|
4312
|
+
} else if (pageNextIndex > retained.pageNextIndex) {
|
|
4313
|
+
const appendFrom = Math.max(retained.pageNextIndex, snapshot.droppedLines);
|
|
4314
|
+
retained.droppedLines += Math.max(0, snapshot.droppedLines - retained.pageNextIndex);
|
|
4315
|
+
retained.lines.push(...snapshot.lines.slice(appendFrom - snapshot.droppedLines));
|
|
4316
|
+
retained.pageNextIndex = pageNextIndex;
|
|
4317
|
+
}
|
|
4318
|
+
const excess = retained.lines.length - GOOGLE_MEET_TRANSCRIPT_MAX_LINES;
|
|
4319
|
+
if (excess > 0) {
|
|
4320
|
+
retained.lines.splice(0, excess);
|
|
4321
|
+
retained.droppedLines += excess;
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
#retireTranscript(sessionId) {
|
|
4325
|
+
const snapshot = this.#transcripts.get(sessionId);
|
|
4326
|
+
if (snapshot) {
|
|
4327
|
+
this.#transcripts.delete(sessionId);
|
|
4328
|
+
this.#transcripts.set(sessionId, snapshot);
|
|
4329
|
+
this.#retiredTranscripts.delete(sessionId);
|
|
4330
|
+
this.#retiredTranscripts.add(sessionId);
|
|
4331
|
+
}
|
|
4332
|
+
const retainedIds = [...this.#retiredTranscripts].filter((id) => this.#transcripts.has(id)).toSorted((a, b) => (this.#sessions.get(a)?.updatedAt ?? "").localeCompare(this.#sessions.get(b)?.updatedAt ?? ""));
|
|
4333
|
+
for (const id of retainedIds.slice(0, -4)) {
|
|
4334
|
+
this.#transcripts.delete(id);
|
|
4335
|
+
this.#retiredTranscripts.delete(id);
|
|
4336
|
+
const session = this.#sessions.get(id);
|
|
4337
|
+
if (session) session.transcriptEvicted = true;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
#inheritBrowserTabOwnership(params) {
|
|
4341
|
+
if (!params.tab) return;
|
|
4342
|
+
const tab = params.tab;
|
|
4343
|
+
const createdTabKey = params.transport === "chrome-node" && params.nodeId ? `${params.nodeId}:${tab.targetId}` : void 0;
|
|
4344
|
+
const createdMeetingUrl = createdTabKey ? this.#createdBrowserTabs.get(createdTabKey) : void 0;
|
|
4345
|
+
const inheritedFromCreate = isSameMeetUrlForReuse(createdMeetingUrl, params.meetingUrl);
|
|
4346
|
+
if (createdMeetingUrl && createdTabKey) this.#createdBrowserTabs.delete(createdTabKey);
|
|
4347
|
+
const inheritedFromSession = [...this.#sessions.values()].some((session) => {
|
|
4348
|
+
const trackedTab = session.chrome?.browserTab;
|
|
4349
|
+
if (!trackedTab) return false;
|
|
4350
|
+
return session.transport === params.transport && isSameMeetUrlForReuse(session.url, params.meetingUrl) && session.chrome?.nodeId === params.nodeId && trackedTab.targetId === tab.targetId && trackedTab.openedByPlugin;
|
|
4351
|
+
});
|
|
4352
|
+
return inheritedFromCreate || inheritedFromSession ? {
|
|
4353
|
+
...tab,
|
|
4354
|
+
openedByPlugin: true
|
|
4355
|
+
} : tab;
|
|
4356
|
+
}
|
|
4357
|
+
async #settleRetainedBrowserTabs(retained, adopted) {
|
|
4358
|
+
let settled = true;
|
|
4359
|
+
for (const { session, tab } of retained.splice(0)) {
|
|
4360
|
+
if (adopted?.transport === session.transport && adopted.nodeId === session.chrome?.nodeId && adopted.tab.targetId === tab.targetId) {
|
|
4361
|
+
if (session.chrome) session.chrome.browserTab = void 0;
|
|
4362
|
+
continue;
|
|
4363
|
+
}
|
|
4364
|
+
if (await this.#releaseBrowserTab(session) === false) settled = false;
|
|
4365
|
+
}
|
|
4366
|
+
return settled;
|
|
4367
|
+
}
|
|
4368
|
+
async #releaseBrowserTab(session) {
|
|
4369
|
+
let browserLeft;
|
|
4370
|
+
try {
|
|
4371
|
+
browserLeft = await this.#leaveBrowserMeetTab(session);
|
|
4372
|
+
} catch (error) {
|
|
4373
|
+
noteSession(session, `Browser control could not leave the Meet tab: ${formatErrorMessage(error)}`);
|
|
4374
|
+
browserLeft = false;
|
|
4375
|
+
}
|
|
4376
|
+
session.browserLeft = browserLeft;
|
|
4377
|
+
if (session.chrome && browserLeft !== false) session.chrome.browserTab = void 0;
|
|
4378
|
+
return browserLeft;
|
|
4379
|
+
}
|
|
4380
|
+
async #leaveBrowserMeetTab(session) {
|
|
4381
|
+
if (!isBrowserTransport(session.transport)) return;
|
|
4382
|
+
const tab = session.chrome?.browserTab;
|
|
4383
|
+
if (!tab) {
|
|
4384
|
+
noteSession(session, "No tracked Meet browser tab for this session; close the Meet tab manually if it is still in the call.");
|
|
4385
|
+
return false;
|
|
4386
|
+
}
|
|
4387
|
+
if (this.list().some((other) => other.id !== session.id && other.state === "active" && isBrowserTransport(other.transport) && other.chrome?.browserTab?.targetId === tab.targetId && other.chrome?.nodeId === session.chrome?.nodeId)) {
|
|
4388
|
+
noteSession(session, "Kept the shared Meet tab open because another active session uses it.");
|
|
4389
|
+
return;
|
|
4390
|
+
}
|
|
4391
|
+
const result = session.transport === "chrome-node" ? await leaveChromeMeetOnNode({
|
|
4392
|
+
runtime: this.params.runtime,
|
|
4393
|
+
nodeId: session.chrome?.nodeId,
|
|
4394
|
+
config: this.params.config,
|
|
4395
|
+
meetingUrl: session.url,
|
|
4396
|
+
tab
|
|
4397
|
+
}) : await leaveChromeMeet({
|
|
4398
|
+
runtime: this.params.runtime,
|
|
4399
|
+
config: this.params.config,
|
|
4400
|
+
meetingUrl: session.url,
|
|
4401
|
+
tab
|
|
4402
|
+
});
|
|
4403
|
+
noteSession(session, result.note);
|
|
4404
|
+
return result.left;
|
|
4405
|
+
}
|
|
3442
4406
|
async speak(sessionId, instructions) {
|
|
3443
4407
|
const session = this.#sessions.get(sessionId);
|
|
3444
4408
|
if (!session) return {
|
|
@@ -3519,9 +4483,16 @@ var GoogleMeetRuntime = class {
|
|
|
3519
4483
|
const mode = requestedMode && isGoogleMeetTalkBackMode$1(requestedMode) ? requestedMode : isGoogleMeetTalkBackMode$1(this.params.config.defaultMode) ? this.params.config.defaultMode : "agent";
|
|
3520
4484
|
const url = normalizeMeetUrl(request.url);
|
|
3521
4485
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
4486
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
3522
4487
|
const beforeSessions = this.list();
|
|
3523
4488
|
const before = new Set(beforeSessions.map((session) => session.id));
|
|
3524
|
-
const
|
|
4489
|
+
const existingSession = beforeSessions.find((session) => isReusableMeetSession(session, {
|
|
4490
|
+
url,
|
|
4491
|
+
transport,
|
|
4492
|
+
mode,
|
|
4493
|
+
agentId
|
|
4494
|
+
}));
|
|
4495
|
+
const existingOutputBytes = existingSession?.chrome?.health?.lastOutputBytes ?? 0;
|
|
3525
4496
|
const result = await this.join({
|
|
3526
4497
|
...request,
|
|
3527
4498
|
transport,
|
|
@@ -3529,6 +4500,7 @@ var GoogleMeetRuntime = class {
|
|
|
3529
4500
|
mode,
|
|
3530
4501
|
message: request.message ?? "Say exactly: Google Meet speech test complete."
|
|
3531
4502
|
});
|
|
4503
|
+
const startOutputBytes = existingSession?.id === result.session.id ? existingOutputBytes : 0;
|
|
3532
4504
|
let health = result.session.chrome?.health;
|
|
3533
4505
|
const shouldWaitForOutput = result.spoken === true && health?.manualActionRequired !== true && this.#sessionHealth.has(result.session.id);
|
|
3534
4506
|
if (shouldWaitForOutput && !hasRealtimeAudioOutputAdvanced(health, startOutputBytes)) {
|
|
@@ -3564,9 +4536,16 @@ var GoogleMeetRuntime = class {
|
|
|
3564
4536
|
const url = normalizeMeetUrl(request.url);
|
|
3565
4537
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
3566
4538
|
if (transport === "twilio") throw new Error("test_listen supports chrome or chrome-node transports");
|
|
4539
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
3567
4540
|
const beforeSessions = this.list();
|
|
3568
4541
|
const before = new Set(beforeSessions.map((session) => session.id));
|
|
3569
|
-
const
|
|
4542
|
+
const existingSession = beforeSessions.find((session) => isReusableMeetSession(session, {
|
|
4543
|
+
url,
|
|
4544
|
+
transport,
|
|
4545
|
+
mode: "transcribe",
|
|
4546
|
+
agentId
|
|
4547
|
+
}));
|
|
4548
|
+
const existingStart = transcriptCheckpoint(existingSession?.chrome?.health);
|
|
3570
4549
|
const result = await this.join({
|
|
3571
4550
|
...request,
|
|
3572
4551
|
transport,
|
|
@@ -3574,6 +4553,7 @@ var GoogleMeetRuntime = class {
|
|
|
3574
4553
|
mode: "transcribe",
|
|
3575
4554
|
message: void 0
|
|
3576
4555
|
});
|
|
4556
|
+
const start = existingSession?.id === result.session.id ? existingStart : transcriptCheckpoint(void 0);
|
|
3577
4557
|
let health = result.session.chrome?.health;
|
|
3578
4558
|
const timeoutMs = resolveProbeTimeoutMs(request.timeoutMs, this.params.config.chrome.joinTimeoutMs);
|
|
3579
4559
|
const shouldWait = health?.manualActionRequired !== true && isManagedChromeBrowserSession(result.session);
|
|
@@ -3667,6 +4647,7 @@ var GoogleMeetRuntime = class {
|
|
|
3667
4647
|
readOnly: options.readOnly,
|
|
3668
4648
|
url: session.url
|
|
3669
4649
|
}) : await recoverCurrentMeetTab({
|
|
4650
|
+
runtime: this.params.runtime,
|
|
3670
4651
|
config: this.params.config,
|
|
3671
4652
|
mode: session.mode,
|
|
3672
4653
|
readOnly: options.readOnly,
|
|
@@ -3700,12 +4681,13 @@ var GoogleMeetRuntime = class {
|
|
|
3700
4681
|
if (!isGoogleMeetTalkBackMode$1(session.mode) || session.transport !== "chrome" || session.state !== "active" || !session.chrome || session.chrome.audioBridge) return;
|
|
3701
4682
|
const health = session.chrome.health;
|
|
3702
4683
|
if (health?.inCall !== true || health.micMuted === true || health.manualActionRequired === true) return;
|
|
4684
|
+
const sessionConfig = withSessionAgentConfig(this.params.config, session.agentId);
|
|
3703
4685
|
const result = await launchChromeMeet({
|
|
3704
4686
|
runtime: this.params.runtime,
|
|
3705
4687
|
config: {
|
|
3706
|
-
...
|
|
4688
|
+
...sessionConfig,
|
|
3707
4689
|
chrome: {
|
|
3708
|
-
...
|
|
4690
|
+
...sessionConfig.chrome,
|
|
3709
4691
|
launch: false
|
|
3710
4692
|
}
|
|
3711
4693
|
},
|
|
@@ -3744,154 +4726,9 @@ var GoogleMeetRuntime = class {
|
|
|
3744
4726
|
}
|
|
3745
4727
|
};
|
|
3746
4728
|
//#endregion
|
|
3747
|
-
//#region extensions/google-meet/src/node-invoke-policy.ts
|
|
3748
|
-
const GOOGLE_MEET_CHROME_NODE_COMMAND = "googlemeet.chrome";
|
|
3749
|
-
const START_MODES = /* @__PURE__ */ new Set([
|
|
3750
|
-
"agent",
|
|
3751
|
-
"bidi",
|
|
3752
|
-
"realtime",
|
|
3753
|
-
"transcribe"
|
|
3754
|
-
]);
|
|
3755
|
-
function asRecord$1(value) {
|
|
3756
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
3757
|
-
}
|
|
3758
|
-
function readString(value) {
|
|
3759
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3760
|
-
}
|
|
3761
|
-
function readPositiveNumber(value) {
|
|
3762
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
3763
|
-
}
|
|
3764
|
-
function copyCommand(command) {
|
|
3765
|
-
return command && command.length > 0 ? [...command] : void 0;
|
|
3766
|
-
}
|
|
3767
|
-
function denied(message, code = "GOOGLE_MEET_NODE_POLICY_DENIED") {
|
|
3768
|
-
return {
|
|
3769
|
-
ok: false,
|
|
3770
|
-
code,
|
|
3771
|
-
message
|
|
3772
|
-
};
|
|
3773
|
-
}
|
|
3774
|
-
function approved(params) {
|
|
3775
|
-
return {
|
|
3776
|
-
approved: true,
|
|
3777
|
-
params
|
|
3778
|
-
};
|
|
3779
|
-
}
|
|
3780
|
-
function buildStartParams(params, config) {
|
|
3781
|
-
let url;
|
|
3782
|
-
try {
|
|
3783
|
-
url = normalizeMeetUrl(params.url);
|
|
3784
|
-
} catch (error) {
|
|
3785
|
-
return {
|
|
3786
|
-
approved: false,
|
|
3787
|
-
result: denied(error instanceof Error ? error.message : "googlemeet.chrome start requires url")
|
|
3788
|
-
};
|
|
3789
|
-
}
|
|
3790
|
-
const mode = readString(params.mode);
|
|
3791
|
-
if (mode && !START_MODES.has(mode)) return {
|
|
3792
|
-
approved: false,
|
|
3793
|
-
result: denied(`googlemeet.chrome start mode is unsupported: ${mode}`)
|
|
3794
|
-
};
|
|
3795
|
-
const startParams = {
|
|
3796
|
-
action: "start",
|
|
3797
|
-
url,
|
|
3798
|
-
launch: params.launch === false ? false : config.chrome.launch,
|
|
3799
|
-
browserProfile: config.chrome.browserProfile,
|
|
3800
|
-
joinTimeoutMs: config.chrome.joinTimeoutMs
|
|
3801
|
-
};
|
|
3802
|
-
if (mode) startParams.mode = mode;
|
|
3803
|
-
const audioInputCommand = copyCommand(config.chrome.audioInputCommand);
|
|
3804
|
-
if (audioInputCommand) startParams.audioInputCommand = audioInputCommand;
|
|
3805
|
-
const audioOutputCommand = copyCommand(config.chrome.audioOutputCommand);
|
|
3806
|
-
if (audioOutputCommand) startParams.audioOutputCommand = audioOutputCommand;
|
|
3807
|
-
const audioBridgeCommand = copyCommand(config.chrome.audioBridgeCommand);
|
|
3808
|
-
if (audioBridgeCommand) startParams.audioBridgeCommand = audioBridgeCommand;
|
|
3809
|
-
const audioBridgeHealthCommand = copyCommand(config.chrome.audioBridgeHealthCommand);
|
|
3810
|
-
if (audioBridgeHealthCommand) startParams.audioBridgeHealthCommand = audioBridgeHealthCommand;
|
|
3811
|
-
return approved(startParams);
|
|
3812
|
-
}
|
|
3813
|
-
function buildForwardParams(params) {
|
|
3814
|
-
const action = readString(params.action);
|
|
3815
|
-
switch (action) {
|
|
3816
|
-
case "setup": return { action };
|
|
3817
|
-
case "status": {
|
|
3818
|
-
const bridgeId = readString(params.bridgeId);
|
|
3819
|
-
return bridgeId ? {
|
|
3820
|
-
action,
|
|
3821
|
-
bridgeId
|
|
3822
|
-
} : { action };
|
|
3823
|
-
}
|
|
3824
|
-
case "list": {
|
|
3825
|
-
const forwarded = { action };
|
|
3826
|
-
const url = readString(params.url);
|
|
3827
|
-
const mode = readString(params.mode);
|
|
3828
|
-
if (url) forwarded.url = url;
|
|
3829
|
-
if (mode) forwarded.mode = mode;
|
|
3830
|
-
return forwarded;
|
|
3831
|
-
}
|
|
3832
|
-
case "stopByUrl": {
|
|
3833
|
-
const forwarded = { action };
|
|
3834
|
-
const url = readString(params.url);
|
|
3835
|
-
const mode = readString(params.mode);
|
|
3836
|
-
const exceptBridgeId = readString(params.exceptBridgeId);
|
|
3837
|
-
if (url) forwarded.url = url;
|
|
3838
|
-
if (mode) forwarded.mode = mode;
|
|
3839
|
-
if (exceptBridgeId) forwarded.exceptBridgeId = exceptBridgeId;
|
|
3840
|
-
return forwarded;
|
|
3841
|
-
}
|
|
3842
|
-
case "pullAudio": {
|
|
3843
|
-
const forwarded = { action };
|
|
3844
|
-
const bridgeId = readString(params.bridgeId);
|
|
3845
|
-
const timeoutMs = readPositiveNumber(params.timeoutMs);
|
|
3846
|
-
if (bridgeId) forwarded.bridgeId = bridgeId;
|
|
3847
|
-
if (timeoutMs) forwarded.timeoutMs = timeoutMs;
|
|
3848
|
-
return forwarded;
|
|
3849
|
-
}
|
|
3850
|
-
case "pushAudio": {
|
|
3851
|
-
const forwarded = { action };
|
|
3852
|
-
const bridgeId = readString(params.bridgeId);
|
|
3853
|
-
const base64 = readString(params.base64);
|
|
3854
|
-
if (bridgeId) forwarded.bridgeId = bridgeId;
|
|
3855
|
-
if (base64) forwarded.base64 = base64;
|
|
3856
|
-
return forwarded;
|
|
3857
|
-
}
|
|
3858
|
-
case "clearAudio":
|
|
3859
|
-
case "stop": {
|
|
3860
|
-
const bridgeId = readString(params.bridgeId);
|
|
3861
|
-
return bridgeId ? {
|
|
3862
|
-
action,
|
|
3863
|
-
bridgeId
|
|
3864
|
-
} : { action };
|
|
3865
|
-
}
|
|
3866
|
-
default: return null;
|
|
3867
|
-
}
|
|
3868
|
-
}
|
|
3869
|
-
function createGoogleMeetChromeNodeInvokePolicy(config) {
|
|
3870
|
-
return {
|
|
3871
|
-
commands: [GOOGLE_MEET_CHROME_NODE_COMMAND],
|
|
3872
|
-
dangerous: true,
|
|
3873
|
-
async handle(ctx) {
|
|
3874
|
-
if (ctx.command !== "googlemeet.chrome") return denied(`unsupported Google Meet node command: ${ctx.command}`);
|
|
3875
|
-
const params = asRecord$1(ctx.params);
|
|
3876
|
-
const action = readString(params.action);
|
|
3877
|
-
let decision;
|
|
3878
|
-
if (action === "start") decision = buildStartParams(params, config);
|
|
3879
|
-
else {
|
|
3880
|
-
const forwardParams = buildForwardParams(params);
|
|
3881
|
-
decision = forwardParams ? approved(forwardParams) : {
|
|
3882
|
-
approved: false,
|
|
3883
|
-
result: denied("unsupported googlemeet.chrome action")
|
|
3884
|
-
};
|
|
3885
|
-
}
|
|
3886
|
-
if (!decision.approved) return decision.result;
|
|
3887
|
-
return await ctx.invokeNode({ params: decision.params });
|
|
3888
|
-
}
|
|
3889
|
-
};
|
|
3890
|
-
}
|
|
3891
|
-
//#endregion
|
|
3892
4729
|
//#region extensions/google-meet/index.ts
|
|
3893
|
-
const loadGoogleMeetCreateModule = createLazyRuntimeModule(() => import("./create-
|
|
3894
|
-
const loadGoogleMeetCliModule = createLazyRuntimeModule(() => import("./cli-
|
|
4730
|
+
const loadGoogleMeetCreateModule = createLazyRuntimeModule(() => import("./create-CWcFI0ub.js"));
|
|
4731
|
+
const loadGoogleMeetCliModule = createLazyRuntimeModule(() => import("./cli-SodPSOFX.js"));
|
|
3895
4732
|
const googleMeetConfigSchema = {
|
|
3896
4733
|
parse(value) {
|
|
3897
4734
|
return resolveGoogleMeetConfig(value);
|
|
@@ -4099,6 +4936,7 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
4099
4936
|
"join",
|
|
4100
4937
|
"create",
|
|
4101
4938
|
"status",
|
|
4939
|
+
"transcript",
|
|
4102
4940
|
"setup_status",
|
|
4103
4941
|
"resolve_space",
|
|
4104
4942
|
"preflight",
|
|
@@ -4150,6 +4988,10 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
4150
4988
|
pin: Type.Optional(Type.String({ description: "Meet phone PIN for Twilio; # is appended if omitted" })),
|
|
4151
4989
|
dtmfSequence: Type.Optional(Type.String({ description: "Explicit DTMF sequence for Twilio" })),
|
|
4152
4990
|
sessionId: Type.Optional(Type.String({ description: "Meet session ID" })),
|
|
4991
|
+
sinceIndex: Type.Optional(Type.Integer({
|
|
4992
|
+
description: "For transcript, resume from the previous response's nextIndex.",
|
|
4993
|
+
minimum: 0
|
|
4994
|
+
})),
|
|
4153
4995
|
message: Type.Optional(Type.String({ description: "Realtime instructions to speak now" })),
|
|
4154
4996
|
timeoutMs: optionalPositiveIntegerSchema({ description: "Probe timeout in milliseconds" }),
|
|
4155
4997
|
meeting: Type.Optional(Type.String({ description: "Meet URL, meeting code, or spaces/{id}" })),
|
|
@@ -4236,11 +5078,17 @@ function readGatewayErrorDetails(err) {
|
|
|
4236
5078
|
}
|
|
4237
5079
|
async function callGoogleMeetGatewayFromTool(params) {
|
|
4238
5080
|
try {
|
|
4239
|
-
if (params.runtime) return await params.runtime.gateway.request(googleMeetGatewayMethodForToolAction(params.action), params.raw, {
|
|
5081
|
+
if (params.runtime) return await params.runtime.gateway.request(googleMeetGatewayMethodForToolAction(params.action), params.raw, {
|
|
5082
|
+
timeoutMs: resolveGoogleMeetGatewayOperationTimeoutMs(params.config),
|
|
5083
|
+
scopes: ["operator.admin"]
|
|
5084
|
+
});
|
|
4240
5085
|
return await googleMeetToolDeps.callGatewayFromCli(googleMeetGatewayMethodForToolAction(params.action), {
|
|
4241
5086
|
json: true,
|
|
4242
5087
|
timeout: String(resolveGoogleMeetGatewayOperationTimeoutMs(params.config))
|
|
4243
|
-
}, params.raw, {
|
|
5088
|
+
}, params.raw, {
|
|
5089
|
+
progress: false,
|
|
5090
|
+
scopes: ["operator.admin"]
|
|
5091
|
+
});
|
|
4244
5092
|
} catch (err) {
|
|
4245
5093
|
const details = readGatewayErrorDetails(err);
|
|
4246
5094
|
if (details && typeof details === "object") return details;
|
|
@@ -4263,7 +5111,7 @@ async function createAndJoinMeetFromParams(params) {
|
|
|
4263
5111
|
return (await loadGoogleMeetCreateModule()).createAndJoinMeetFromParams(params);
|
|
4264
5112
|
}
|
|
4265
5113
|
async function resolveGoogleMeetTokenFromParams(config, raw) {
|
|
4266
|
-
const { resolveGoogleMeetAccessToken } = await import("./oauth-
|
|
5114
|
+
const { resolveGoogleMeetAccessToken } = await import("./oauth-DOfMKlGp.js");
|
|
4267
5115
|
return resolveGoogleMeetAccessToken({
|
|
4268
5116
|
clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
|
|
4269
5117
|
clientSecret: normalizeOptionalString(raw.clientSecret) ?? config.oauth.clientSecret,
|
|
@@ -4462,6 +5310,23 @@ var google_meet_default = definePluginEntry({
|
|
|
4462
5310
|
sendError(respond, err);
|
|
4463
5311
|
}
|
|
4464
5312
|
});
|
|
5313
|
+
api.registerGatewayMethod("googlemeet.transcript", async ({ params, respond }) => {
|
|
5314
|
+
try {
|
|
5315
|
+
const sessionId = normalizeOptionalString(params?.sessionId);
|
|
5316
|
+
if (!sessionId) {
|
|
5317
|
+
sendError(respond, /* @__PURE__ */ new Error("sessionId required"), ErrorCodes.INVALID_REQUEST);
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
const sinceIndex = params?.sinceIndex;
|
|
5321
|
+
if (sinceIndex !== void 0 && (typeof sinceIndex !== "number" || !Number.isSafeInteger(sinceIndex) || sinceIndex < 0)) {
|
|
5322
|
+
sendError(respond, /* @__PURE__ */ new Error("sinceIndex must be a non-negative safe integer"), ErrorCodes.INVALID_REQUEST);
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
5325
|
+
respond(true, await (await ensureRuntime()).transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex }));
|
|
5326
|
+
} catch (err) {
|
|
5327
|
+
sendError(respond, err);
|
|
5328
|
+
}
|
|
5329
|
+
});
|
|
4465
5330
|
api.registerGatewayMethod("googlemeet.recoverCurrentTab", async ({ params, respond }) => {
|
|
4466
5331
|
try {
|
|
4467
5332
|
respond(true, await (await ensureRuntime()).recoverCurrentTab({
|
|
@@ -4613,13 +5478,15 @@ var google_meet_default = definePluginEntry({
|
|
|
4613
5478
|
sendError(respond, err);
|
|
4614
5479
|
}
|
|
4615
5480
|
});
|
|
4616
|
-
api.registerGatewayMethod("googlemeet.testListen", async ({ params, respond }) => {
|
|
5481
|
+
api.registerGatewayMethod("googlemeet.testListen", async ({ params, client, respond }) => {
|
|
4617
5482
|
try {
|
|
5483
|
+
const trustedParams = keepTrustedToolAgentId(asParamRecord(params), client);
|
|
4618
5484
|
respond(true, await (await ensureRuntime()).testListen({
|
|
4619
|
-
url: resolveMeetingInput(config,
|
|
4620
|
-
transport: normalizeTransport(
|
|
4621
|
-
mode: normalizeMode(
|
|
4622
|
-
|
|
5485
|
+
url: resolveMeetingInput(config, trustedParams.url),
|
|
5486
|
+
transport: normalizeTransport(trustedParams.transport),
|
|
5487
|
+
mode: normalizeMode(trustedParams.mode),
|
|
5488
|
+
agentId: normalizeOptionalString(trustedParams.agentId),
|
|
5489
|
+
timeoutMs: readPositiveIntegerParam(trustedParams, "timeoutMs")
|
|
4623
5490
|
}));
|
|
4624
5491
|
} catch (err) {
|
|
4625
5492
|
sendError(respond, err);
|
|
@@ -4633,10 +5500,12 @@ var google_meet_default = definePluginEntry({
|
|
|
4633
5500
|
async execute(_toolCallId, params) {
|
|
4634
5501
|
const raw = asParamRecord(params);
|
|
4635
5502
|
const requesterSessionKey = normalizeOptionalString(toolContext.sessionKey);
|
|
4636
|
-
const
|
|
5503
|
+
const contextAgentId = toolContext.agentId ?? parseAgentSessionKey(requesterSessionKey)?.agentId;
|
|
5504
|
+
const agentId = contextAgentId ? normalizeAgentId(contextAgentId) : void 0;
|
|
4637
5505
|
try {
|
|
4638
|
-
const
|
|
4639
|
-
|
|
5506
|
+
const needsTrustedAgentRouting = Boolean(agentId && agentId !== "main");
|
|
5507
|
+
const useTrustedRuntime = needsTrustedAgentRouting ? await api.runtime.gateway.isAvailable() : false;
|
|
5508
|
+
if (needsTrustedAgentRouting && !useTrustedRuntime) throw new Error("Per-agent Google Meet routing requires a Gateway-hosted agent run.");
|
|
4640
5509
|
const rawWithRequester = {
|
|
4641
5510
|
...raw,
|
|
4642
5511
|
...requesterSessionKey ? { requesterSessionKey } : {},
|
|
@@ -4668,13 +5537,19 @@ var google_meet_default = definePluginEntry({
|
|
|
4668
5537
|
case "test_listen": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4669
5538
|
config,
|
|
4670
5539
|
action: "test_listen",
|
|
4671
|
-
raw
|
|
5540
|
+
raw: rawWithRequester,
|
|
5541
|
+
runtime: useTrustedRuntime ? api.runtime : void 0
|
|
4672
5542
|
}));
|
|
4673
5543
|
case "status": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4674
5544
|
config,
|
|
4675
5545
|
action: "status",
|
|
4676
5546
|
raw
|
|
4677
5547
|
}));
|
|
5548
|
+
case "transcript": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
5549
|
+
config,
|
|
5550
|
+
action: "transcript",
|
|
5551
|
+
raw
|
|
5552
|
+
}));
|
|
4678
5553
|
case "recover_current_tab": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4679
5554
|
config,
|
|
4680
5555
|
action: "recover_current_tab",
|