@openclaw/google-meet 2026.7.1 → 2026.7.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{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 +1235 -359
- 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,23 @@ 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
2360
|
const chromeTransportDeps = { callGatewayFromCli };
|
|
2361
|
+
const GOOGLE_MEET_CAPTION_SETTLE_MS = 1e3;
|
|
2178
2362
|
function isGoogleMeetTalkBackMode$2(mode) {
|
|
2179
2363
|
return mode === "agent" || mode === "bidi";
|
|
2180
2364
|
}
|
|
2365
|
+
function readMeetAuthUser(url) {
|
|
2366
|
+
if (!url) return;
|
|
2367
|
+
try {
|
|
2368
|
+
return new URL(url).searchParams.get("authuser") ?? void 0;
|
|
2369
|
+
} catch {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2181
2373
|
async function assertBlackHole2chAvailable(params) {
|
|
2182
2374
|
if (process.platform !== "darwin") throw new Error("Chrome Meet transport with blackhole-2ch audio is currently macOS-only");
|
|
2183
2375
|
const result = await params.runtime.system.runCommandWithTimeout([GOOGLE_MEET_SYSTEM_PROFILER_COMMAND, "SPAudioDataType"], { timeoutMs: params.timeoutMs });
|
|
@@ -2250,9 +2442,10 @@ async function launchChromeMeet(params) {
|
|
|
2250
2442
|
audioBridge: await startRealtimeAudioBridge()
|
|
2251
2443
|
};
|
|
2252
2444
|
const result = await openMeetWithBrowserRequest({
|
|
2253
|
-
callBrowser:
|
|
2445
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
2254
2446
|
config: params.config,
|
|
2255
2447
|
mode: params.mode,
|
|
2448
|
+
meetingSessionId: params.meetingSessionId,
|
|
2256
2449
|
url: params.url
|
|
2257
2450
|
});
|
|
2258
2451
|
const audioBridge = isGoogleMeetTalkBackMode$2(params.mode) && result.browser?.inCall === true && result.browser.micMuted !== true && result.browser.manualActionRequired !== true ? await startRealtimeAudioBridge() : void 0;
|
|
@@ -2310,6 +2503,18 @@ async function callLocalBrowserRequest(params) {
|
|
|
2310
2503
|
timeoutMs: params.timeoutMs
|
|
2311
2504
|
}, { progress: false });
|
|
2312
2505
|
}
|
|
2506
|
+
async function resolveLocalBrowserRequest(runtime) {
|
|
2507
|
+
if (!await runtime.gateway.isAvailable()) return callLocalBrowserRequest;
|
|
2508
|
+
return async (params) => await runtime.gateway.request("browser.request", {
|
|
2509
|
+
method: params.method,
|
|
2510
|
+
path: params.path,
|
|
2511
|
+
body: params.body,
|
|
2512
|
+
timeoutMs: params.timeoutMs
|
|
2513
|
+
}, {
|
|
2514
|
+
timeoutMs: resolveBrowserGatewayTimeoutMs(params.timeoutMs),
|
|
2515
|
+
scopes: ["operator.admin"]
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2313
2518
|
function resolveBrowserGatewayTimeoutMs(timeoutMs) {
|
|
2314
2519
|
return addTimerTimeoutGraceMs(timeoutMs) ?? 1;
|
|
2315
2520
|
}
|
|
@@ -2350,6 +2555,7 @@ function meetStatusScript(params) {
|
|
|
2350
2555
|
return `async () => {
|
|
2351
2556
|
const text = (node) => (node?.innerText || node?.textContent || "").trim();
|
|
2352
2557
|
const allowMicrophone = ${JSON.stringify(params.allowMicrophone)};
|
|
2558
|
+
const captionSessionId = ${JSON.stringify(params.captionSessionId)};
|
|
2353
2559
|
const captureCaptions = ${JSON.stringify(params.captureCaptions)};
|
|
2354
2560
|
const readOnly = ${JSON.stringify(Boolean(params.readOnly))};
|
|
2355
2561
|
const buttons = [...document.querySelectorAll('button')];
|
|
@@ -2405,8 +2611,9 @@ function meetStatusScript(params) {
|
|
|
2405
2611
|
mic.click();
|
|
2406
2612
|
notes.push("Muted Meet microphone for observe-only mode.");
|
|
2407
2613
|
}
|
|
2614
|
+
const joinElsewhere = findButton(/join here too/i);
|
|
2408
2615
|
const join = !readOnly && ${JSON.stringify(params.autoJoin)}
|
|
2409
|
-
? findButton(/join now|ask to join
|
|
2616
|
+
? findButton(/join now|ask to join/i)
|
|
2410
2617
|
: null;
|
|
2411
2618
|
if (join) join.click();
|
|
2412
2619
|
const microphoneChoice = findButton(/\\buse microphone\\b/i);
|
|
@@ -2480,42 +2687,127 @@ function meetStatusScript(params) {
|
|
|
2480
2687
|
if (!captureCaptions) return undefined;
|
|
2481
2688
|
const w = window;
|
|
2482
2689
|
if (!inCall && !w.__openclawMeetCaptions) return undefined;
|
|
2483
|
-
|
|
2690
|
+
// A reused tab starts a fresh logical transcript for each OpenClaw session.
|
|
2691
|
+
// Status refreshes omit the id, so they preserve the active page-owned buffer.
|
|
2692
|
+
if (!w.__openclawMeetCaptions || (captionSessionId && w.__openclawMeetCaptions.sessionId !== captionSessionId)) {
|
|
2693
|
+
if (w.__openclawMeetCaptions?.settleTimer !== undefined) {
|
|
2694
|
+
clearTimeout(w.__openclawMeetCaptions.settleTimer);
|
|
2695
|
+
}
|
|
2696
|
+
w.__openclawMeetCaptions?.observer?.disconnect?.();
|
|
2484
2697
|
w.__openclawMeetCaptions = {
|
|
2698
|
+
sessionId: captionSessionId,
|
|
2699
|
+
// Epochs cross document lifetimes in the runtime transcript cursor.
|
|
2700
|
+
// Strong UUIDs keep a reloaded page distinct from its prior buffer.
|
|
2701
|
+
epoch: crypto.randomUUID(),
|
|
2485
2702
|
enabledAttempted: false,
|
|
2486
2703
|
observerInstalled: false,
|
|
2704
|
+
observer: undefined,
|
|
2705
|
+
droppedLines: 0,
|
|
2487
2706
|
lines: [],
|
|
2488
|
-
|
|
2707
|
+
settleTimer: undefined,
|
|
2708
|
+
visible: []
|
|
2489
2709
|
};
|
|
2490
2710
|
}
|
|
2491
2711
|
return w.__openclawMeetCaptions;
|
|
2492
2712
|
})();
|
|
2493
|
-
const
|
|
2713
|
+
const normalizeCaption = (speaker, captionText) => {
|
|
2494
2714
|
if (!captionState) return;
|
|
2495
2715
|
const clean = String(captionText || "").replace(/\\s+/g, " ").trim();
|
|
2496
2716
|
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
|
-
|
|
2717
|
+
if (!clean || clean.length < 2) return undefined;
|
|
2718
|
+
if (/^(turn on captions|turn off captions|captions)$/i.test(clean)) return undefined;
|
|
2719
|
+
return { speaker: cleanSpeaker || undefined, text: clean };
|
|
2720
|
+
};
|
|
2721
|
+
const commitLines = (state, entries) => {
|
|
2722
|
+
state.lines.push(...entries.map((entry) => ({
|
|
2723
|
+
at: entry.at,
|
|
2724
|
+
speaker: entry.speaker,
|
|
2725
|
+
text: entry.text
|
|
2726
|
+
})));
|
|
2727
|
+
const excess = state.lines.length - ${GOOGLE_MEET_TRANSCRIPT_MAX_LINES};
|
|
2728
|
+
if (excess > 0) {
|
|
2729
|
+
state.lines.splice(0, excess);
|
|
2730
|
+
state.droppedLines = (state.droppedLines || 0) + excess;
|
|
2731
|
+
}
|
|
2505
2732
|
};
|
|
2506
2733
|
const scrapeCaptions = () => {
|
|
2507
2734
|
if (!captionState) return;
|
|
2508
2735
|
const regions = [...document.querySelectorAll(captionSelector)];
|
|
2736
|
+
const rows = [];
|
|
2509
2737
|
for (const region of regions) {
|
|
2510
2738
|
const raw = text(region);
|
|
2511
2739
|
if (!raw) continue;
|
|
2512
2740
|
const pieces = raw.split(/\\n+/).map((part) => part.trim()).filter(Boolean);
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2741
|
+
const row = pieces.length >= 2
|
|
2742
|
+
? normalizeCaption(pieces[0], pieces.slice(1).join(" "))
|
|
2743
|
+
: normalizeCaption("", pieces[0] || raw);
|
|
2744
|
+
if (row) rows.push({ ...row, node: region });
|
|
2745
|
+
}
|
|
2746
|
+
if (rows.length === 0) {
|
|
2747
|
+
// Meet briefly removes caption rows while rerendering. Keep them mutable
|
|
2748
|
+
// for one settle window so a DOM gap cannot fabricate a repeated line.
|
|
2749
|
+
if (captionState.visible.length > 0 && captionState.settleTimer === undefined) {
|
|
2750
|
+
const pendingState = captionState;
|
|
2751
|
+
pendingState.settleTimer = setTimeout(() => {
|
|
2752
|
+
if (window.__openclawMeetCaptions !== pendingState) return;
|
|
2753
|
+
commitLines(pendingState, pendingState.visible);
|
|
2754
|
+
pendingState.visible = [];
|
|
2755
|
+
pendingState.settleTimer = undefined;
|
|
2756
|
+
}, ${GOOGLE_MEET_CAPTION_SETTLE_MS});
|
|
2517
2757
|
}
|
|
2758
|
+
return;
|
|
2518
2759
|
}
|
|
2760
|
+
if (captionState.settleTimer !== undefined) {
|
|
2761
|
+
clearTimeout(captionState.settleTimer);
|
|
2762
|
+
captionState.settleTimer = undefined;
|
|
2763
|
+
}
|
|
2764
|
+
const previous = Array.isArray(captionState.visible) ? captionState.visible : [];
|
|
2765
|
+
const unmatchedPrevious = [...previous];
|
|
2766
|
+
const nextVisible = [];
|
|
2767
|
+
const now = Date.now();
|
|
2768
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
2769
|
+
const row = rows[index];
|
|
2770
|
+
const priorIndex = unmatchedPrevious.findIndex((candidate) => {
|
|
2771
|
+
const sameTextLifecycle =
|
|
2772
|
+
candidate.text === row.text ||
|
|
2773
|
+
row.text.startsWith(candidate.text) ||
|
|
2774
|
+
candidate.text.startsWith(row.text);
|
|
2775
|
+
const sameDomLifecycle =
|
|
2776
|
+
candidate.node === row.node || now - candidate.seenAt <= ${GOOGLE_MEET_CAPTION_SETTLE_MS};
|
|
2777
|
+
return candidate.speaker === row.speaker && sameTextLifecycle && sameDomLifecycle;
|
|
2778
|
+
});
|
|
2779
|
+
const prior = priorIndex >= 0 ? unmatchedPrevious.splice(priorIndex, 1)[0] : undefined;
|
|
2780
|
+
const sameSpeaker = Boolean(prior) && prior.speaker === row.speaker;
|
|
2781
|
+
if (sameSpeaker && prior.text === row.text) {
|
|
2782
|
+
prior.node = row.node;
|
|
2783
|
+
prior.seenAt = now;
|
|
2784
|
+
nextVisible.push(prior);
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
if (sameSpeaker && row.text.startsWith(prior.text)) {
|
|
2788
|
+
prior.text = row.text;
|
|
2789
|
+
prior.node = row.node;
|
|
2790
|
+
prior.seenAt = now;
|
|
2791
|
+
nextVisible.push(prior);
|
|
2792
|
+
continue;
|
|
2793
|
+
}
|
|
2794
|
+
if (sameSpeaker && prior.text.startsWith(row.text)) {
|
|
2795
|
+
prior.node = row.node;
|
|
2796
|
+
prior.seenAt = now;
|
|
2797
|
+
nextVisible.push(prior);
|
|
2798
|
+
continue;
|
|
2799
|
+
}
|
|
2800
|
+
const entry = {
|
|
2801
|
+
at: new Date().toISOString(),
|
|
2802
|
+
node: row.node,
|
|
2803
|
+
seenAt: now,
|
|
2804
|
+
speaker: row.speaker,
|
|
2805
|
+
text: row.text
|
|
2806
|
+
};
|
|
2807
|
+
nextVisible.push(entry);
|
|
2808
|
+
}
|
|
2809
|
+
commitLines(captionState, unmatchedPrevious);
|
|
2810
|
+
captionState.visible = nextVisible;
|
|
2519
2811
|
};
|
|
2520
2812
|
if (captionState) {
|
|
2521
2813
|
if (!readOnly && inCall && !captionState.enabledAttempted) {
|
|
@@ -2534,7 +2826,8 @@ function meetStatusScript(params) {
|
|
|
2534
2826
|
}
|
|
2535
2827
|
if (inCall && !captionState.observerInstalled) {
|
|
2536
2828
|
captionState.observerInstalled = true;
|
|
2537
|
-
new MutationObserver(scrapeCaptions)
|
|
2829
|
+
captionState.observer = new MutationObserver(scrapeCaptions);
|
|
2830
|
+
captionState.observer.observe(document.body, {
|
|
2538
2831
|
childList: true,
|
|
2539
2832
|
subtree: true,
|
|
2540
2833
|
characterData: true
|
|
@@ -2544,17 +2837,19 @@ function meetStatusScript(params) {
|
|
|
2544
2837
|
if (inCall) {
|
|
2545
2838
|
scrapeCaptions();
|
|
2546
2839
|
}
|
|
2547
|
-
const
|
|
2840
|
+
const committedLines = Array.isArray(captionState.lines) ? captionState.lines : [];
|
|
2841
|
+
const visibleLines = Array.isArray(captionState.visible) ? captionState.visible : [];
|
|
2842
|
+
const lines = [...committedLines, ...visibleLines];
|
|
2548
2843
|
const last = lines[lines.length - 1];
|
|
2549
2844
|
captioning = document.querySelector(captionSelector) !== null || lines.length > 0;
|
|
2550
|
-
transcriptLines = lines.length;
|
|
2845
|
+
transcriptLines = (captionState.droppedLines || 0) + lines.length;
|
|
2551
2846
|
lastCaptionAt = last?.at;
|
|
2552
2847
|
lastCaptionSpeaker = last?.speaker;
|
|
2553
2848
|
lastCaptionText = last?.text;
|
|
2554
2849
|
recentTranscript = lines.slice(-5);
|
|
2555
2850
|
}
|
|
2556
2851
|
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)
|
|
2852
|
+
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
2853
|
? 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
2854
|
: undefined;
|
|
2560
2855
|
let manualActionReason;
|
|
@@ -2562,6 +2857,9 @@ function meetStatusScript(params) {
|
|
|
2562
2857
|
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
2858
|
manualActionReason = "google-login-required";
|
|
2564
2859
|
manualActionMessage = "Sign in to Google in the OpenClaw browser profile, then retry the Meet join.";
|
|
2860
|
+
} else if (!inCall && joinElsewhere) {
|
|
2861
|
+
manualActionReason = "meet-session-conflict";
|
|
2862
|
+
manualActionMessage = "Meet is already active in another tab or device. Leave that session or reuse an English-pinned tab before retrying.";
|
|
2565
2863
|
} 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
2864
|
manualActionReason = "meet-admission-required";
|
|
2567
2865
|
manualActionMessage = "Admit the OpenClaw browser participant in Google Meet, then retry speech.";
|
|
@@ -2602,63 +2900,404 @@ function meetStatusScript(params) {
|
|
|
2602
2900
|
});
|
|
2603
2901
|
}`;
|
|
2604
2902
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2903
|
+
function meetTranscriptScript(meetingUrl, meetingSessionId, finalize) {
|
|
2904
|
+
const expectedMeetingUrl = normalizeMeetUrlForReuse(meetingUrl);
|
|
2905
|
+
return `() => {
|
|
2906
|
+
const expectedMeetingUrl = ${JSON.stringify(expectedMeetingUrl)};
|
|
2907
|
+
const expectedSessionId = ${JSON.stringify(meetingSessionId)};
|
|
2908
|
+
let currentMeetingUrl;
|
|
2909
|
+
try {
|
|
2910
|
+
const currentUrl = new URL(location.href);
|
|
2911
|
+
currentMeetingUrl = currentUrl.origin + currentUrl.pathname.toLowerCase().replace(/\\/$/, "");
|
|
2912
|
+
} catch {
|
|
2913
|
+
return JSON.stringify({ urlMatched: false });
|
|
2914
|
+
}
|
|
2915
|
+
if (!expectedMeetingUrl || currentMeetingUrl !== expectedMeetingUrl) {
|
|
2916
|
+
return JSON.stringify({ urlMatched: false });
|
|
2917
|
+
}
|
|
2918
|
+
const state = window.__openclawMeetCaptions;
|
|
2919
|
+
if (state?.sessionId && state.sessionId !== expectedSessionId) {
|
|
2920
|
+
return JSON.stringify({ urlMatched: true, sessionMatched: false });
|
|
2921
|
+
}
|
|
2922
|
+
if (${JSON.stringify(finalize)} && Array.isArray(state?.visible) && state.visible.length > 0) {
|
|
2923
|
+
if (state.settleTimer !== undefined) clearTimeout(state.settleTimer);
|
|
2924
|
+
state.settleTimer = undefined;
|
|
2925
|
+
state.lines = Array.isArray(state.lines) ? state.lines : [];
|
|
2926
|
+
state.lines.push(...state.visible.map((entry) => ({
|
|
2927
|
+
at: entry.at,
|
|
2928
|
+
speaker: entry.speaker,
|
|
2929
|
+
text: entry.text
|
|
2930
|
+
})));
|
|
2931
|
+
state.visible = [];
|
|
2932
|
+
const excess = state.lines.length - ${GOOGLE_MEET_TRANSCRIPT_MAX_LINES};
|
|
2933
|
+
if (excess > 0) {
|
|
2934
|
+
state.lines.splice(0, excess);
|
|
2935
|
+
state.droppedLines = (state.droppedLines || 0) + excess;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
const lines = Array.isArray(state?.lines) ? state.lines : [];
|
|
2939
|
+
return JSON.stringify({
|
|
2940
|
+
urlMatched: true,
|
|
2941
|
+
sessionMatched: true,
|
|
2942
|
+
epoch: typeof state?.epoch === "string" ? state.epoch : undefined,
|
|
2943
|
+
droppedLines: Number.isFinite(state?.droppedLines) ? Math.max(0, Math.trunc(state.droppedLines)) : 0,
|
|
2944
|
+
lines: lines.map((line) => ({
|
|
2945
|
+
at: typeof line?.at === "string" ? line.at : undefined,
|
|
2946
|
+
speaker: typeof line?.speaker === "string" ? line.speaker : undefined,
|
|
2947
|
+
text: typeof line?.text === "string" ? line.text : ""
|
|
2948
|
+
})).filter((line) => line.text)
|
|
2949
|
+
});
|
|
2950
|
+
}`;
|
|
2619
2951
|
}
|
|
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;
|
|
2952
|
+
function parseMeetTranscriptSnapshot(result) {
|
|
2953
|
+
const raw = (result && typeof result === "object" ? result : {}).result;
|
|
2954
|
+
if (typeof raw !== "string" || !raw.trim()) return {
|
|
2955
|
+
droppedLines: 0,
|
|
2956
|
+
lines: []
|
|
2957
|
+
};
|
|
2958
|
+
let parsed;
|
|
2959
|
+
try {
|
|
2960
|
+
parsed = JSON.parse(raw);
|
|
2961
|
+
} catch {
|
|
2962
|
+
throw new Error("Google Meet transcript JSON is malformed.");
|
|
2647
2963
|
}
|
|
2648
|
-
if (!
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2964
|
+
if (!parsed || typeof parsed !== "object") throw new Error("Google Meet transcript payload is invalid.");
|
|
2965
|
+
const payload = parsed;
|
|
2966
|
+
const droppedLines = typeof payload.droppedLines === "number" && Number.isSafeInteger(payload.droppedLines) ? Math.max(0, payload.droppedLines) : 0;
|
|
2967
|
+
const lines = Array.isArray(payload.lines) ? payload.lines.flatMap((value) => {
|
|
2968
|
+
if (!value || typeof value !== "object") return [];
|
|
2969
|
+
const line = value;
|
|
2970
|
+
if (typeof line.text !== "string" || !line.text.trim()) return [];
|
|
2971
|
+
return [{
|
|
2972
|
+
...typeof line.at === "string" ? { at: line.at } : {},
|
|
2973
|
+
...typeof line.speaker === "string" ? { speaker: line.speaker } : {},
|
|
2974
|
+
text: line.text
|
|
2975
|
+
}];
|
|
2976
|
+
}) : [];
|
|
2977
|
+
return {
|
|
2978
|
+
droppedLines,
|
|
2979
|
+
...typeof payload.epoch === "string" ? { epoch: payload.epoch } : {},
|
|
2980
|
+
lines,
|
|
2981
|
+
...typeof payload.urlMatched === "boolean" ? { urlMatched: payload.urlMatched } : {},
|
|
2982
|
+
...typeof payload.sessionMatched === "boolean" ? { sessionMatched: payload.sessionMatched } : {}
|
|
2656
2983
|
};
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2984
|
+
}
|
|
2985
|
+
async function readMeetTranscriptWithBrowserRequest(params) {
|
|
2986
|
+
const snapshot = parseMeetTranscriptSnapshot(await params.callBrowser({
|
|
2987
|
+
method: "POST",
|
|
2988
|
+
path: "/act",
|
|
2989
|
+
body: {
|
|
2990
|
+
kind: "evaluate",
|
|
2991
|
+
targetId: params.tab.targetId,
|
|
2992
|
+
fn: meetTranscriptScript(params.meetingUrl, params.meetingSessionId, params.finalize)
|
|
2993
|
+
},
|
|
2994
|
+
timeoutMs: params.timeoutMs
|
|
2995
|
+
}));
|
|
2996
|
+
if (snapshot.urlMatched === false) throw new Error("The tracked Meet tab no longer shows this session's meeting URL.");
|
|
2997
|
+
if (snapshot.sessionMatched === false) throw new Error("The tracked Meet tab now belongs to another OpenClaw meeting session.");
|
|
2998
|
+
return {
|
|
2999
|
+
droppedLines: snapshot.droppedLines,
|
|
3000
|
+
...snapshot.epoch ? { epoch: snapshot.epoch } : {},
|
|
3001
|
+
lines: snapshot.lines
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
function meetLeaveScript(meetingUrl) {
|
|
3005
|
+
const expectedMeetingUrl = normalizeMeetUrlForReuse(meetingUrl);
|
|
3006
|
+
return `() => {
|
|
3007
|
+
const expectedMeetingUrl = ${JSON.stringify(expectedMeetingUrl)};
|
|
3008
|
+
let currentMeetingUrl;
|
|
3009
|
+
try {
|
|
3010
|
+
const currentUrl = new URL(location.href);
|
|
3011
|
+
currentMeetingUrl = currentUrl.origin + currentUrl.pathname.toLowerCase().replace(/\\/$/, "");
|
|
3012
|
+
} catch {
|
|
3013
|
+
return JSON.stringify({ departed: false });
|
|
3014
|
+
}
|
|
3015
|
+
if (!expectedMeetingUrl || currentMeetingUrl !== expectedMeetingUrl) {
|
|
3016
|
+
return JSON.stringify({ departed: true, urlMatched: false });
|
|
3017
|
+
}
|
|
3018
|
+
const text = (node) => (node?.innerText || node?.textContent || "").trim();
|
|
3019
|
+
// Locale-independent fallback: Meet renders the leave control as a Material
|
|
3020
|
+
// Symbols icon whose ligature text is "call_end" in every UI language, so a
|
|
3021
|
+
// localized aria-label (e.g. "Anruf verlassen") still resolves to the button.
|
|
3022
|
+
const hasLeaveIcon = (button) => {
|
|
3023
|
+
const icon = button.querySelector ? button.querySelector("i") : null;
|
|
3024
|
+
return icon ? (icon.textContent || "").trim() === "call_end" : false;
|
|
3025
|
+
};
|
|
3026
|
+
const buttons = [...document.querySelectorAll('button')];
|
|
3027
|
+
const label = (button) => [
|
|
3028
|
+
button.getAttribute("aria-label"),
|
|
3029
|
+
button.getAttribute("data-tooltip"),
|
|
3030
|
+
text(button),
|
|
3031
|
+
]
|
|
3032
|
+
.filter(Boolean)
|
|
3033
|
+
.join(" ");
|
|
3034
|
+
const postCall = buttons.some((button) => /\\b(rejoin|return to home screen)\\b/i.test(label(button)));
|
|
3035
|
+
if (postCall) {
|
|
3036
|
+
return JSON.stringify({ departed: true, urlMatched: true });
|
|
3037
|
+
}
|
|
3038
|
+
// Managed join tabs are reused only after the English-tab gate or opened
|
|
3039
|
+
// through the English-UI helper, so follow-up labels are pinned to English.
|
|
3040
|
+
const confirmation = buttons.find((button) => {
|
|
3041
|
+
return !button.disabled && /\\bleave meeting\\b/i.test(label(button));
|
|
3042
|
+
});
|
|
3043
|
+
if (confirmation) {
|
|
3044
|
+
confirmation.click();
|
|
3045
|
+
return JSON.stringify({ departed: false, leaveAction: "confirm", urlMatched: true });
|
|
3046
|
+
}
|
|
3047
|
+
const leave = buttons.find((button) => {
|
|
3048
|
+
if (button.disabled) return false;
|
|
3049
|
+
return /leave call/i.test(label(button)) || hasLeaveIcon(button);
|
|
3050
|
+
});
|
|
3051
|
+
if (leave) {
|
|
3052
|
+
leave.click();
|
|
3053
|
+
return JSON.stringify({ departed: false, leaveAction: "leave", urlMatched: true });
|
|
3054
|
+
}
|
|
3055
|
+
return JSON.stringify({ departed: false, urlMatched: true });
|
|
3056
|
+
}`;
|
|
3057
|
+
}
|
|
3058
|
+
function parseMeetLeaveResult(result) {
|
|
3059
|
+
const raw = (result && typeof result === "object" ? result : {}).result;
|
|
3060
|
+
if (typeof raw !== "string" || !raw.trim()) return { departed: false };
|
|
3061
|
+
try {
|
|
3062
|
+
const parsed = JSON.parse(raw);
|
|
3063
|
+
const leaveAction = parsed.leaveAction === "leave" || parsed.leaveAction === "confirm" ? parsed.leaveAction : void 0;
|
|
3064
|
+
return {
|
|
3065
|
+
departed: parsed.departed === true,
|
|
3066
|
+
...leaveAction ? { leaveAction } : {},
|
|
3067
|
+
...typeof parsed.urlMatched === "boolean" ? { urlMatched: parsed.urlMatched } : {}
|
|
3068
|
+
};
|
|
3069
|
+
} catch {
|
|
3070
|
+
return { departed: false };
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
async function leaveMeetInPage(params) {
|
|
3074
|
+
const deadline = Date.now() + params.timeoutMs;
|
|
3075
|
+
let clickedLeave = false;
|
|
3076
|
+
let clickedConfirmation = false;
|
|
3077
|
+
do {
|
|
3078
|
+
const step = parseMeetLeaveResult(await params.callBrowser({
|
|
3079
|
+
method: "POST",
|
|
3080
|
+
path: "/act",
|
|
3081
|
+
body: {
|
|
3082
|
+
kind: "evaluate",
|
|
3083
|
+
targetId: params.targetId,
|
|
3084
|
+
fn: meetLeaveScript(params.meetingUrl)
|
|
3085
|
+
},
|
|
3086
|
+
timeoutMs: params.timeoutMs
|
|
3087
|
+
}));
|
|
3088
|
+
clickedLeave ||= step.leaveAction === "leave";
|
|
3089
|
+
clickedConfirmation ||= step.leaveAction === "confirm";
|
|
3090
|
+
if (step.departed || step.urlMatched !== true) return {
|
|
3091
|
+
departed: step.departed,
|
|
3092
|
+
clickedLeave,
|
|
3093
|
+
clickedConfirmation,
|
|
3094
|
+
urlMatched: step.urlMatched
|
|
3095
|
+
};
|
|
3096
|
+
if (!step.leaveAction && !clickedLeave) return {
|
|
3097
|
+
departed: false,
|
|
3098
|
+
clickedLeave,
|
|
3099
|
+
clickedConfirmation,
|
|
3100
|
+
urlMatched: true
|
|
3101
|
+
};
|
|
3102
|
+
if (!step.leaveAction) await new Promise((resolve) => {
|
|
3103
|
+
setTimeout(resolve, 100);
|
|
3104
|
+
});
|
|
3105
|
+
} while (Date.now() < deadline);
|
|
3106
|
+
return {
|
|
3107
|
+
departed: false,
|
|
3108
|
+
clickedLeave,
|
|
3109
|
+
clickedConfirmation,
|
|
3110
|
+
urlMatched: true
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
async function leaveMeetWithBrowserRequest(params) {
|
|
3114
|
+
if (!params.config.chrome.launch) return {
|
|
3115
|
+
left: false,
|
|
3116
|
+
note: "Browser leave skipped because chrome.launch is disabled."
|
|
3117
|
+
};
|
|
3118
|
+
const timeoutMs = Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 5e3);
|
|
3119
|
+
const { targetId, openedByPlugin } = params.tab;
|
|
3120
|
+
try {
|
|
3121
|
+
if (!asBrowserTabs(await params.callBrowser({
|
|
3122
|
+
method: "GET",
|
|
3123
|
+
path: "/tabs",
|
|
3124
|
+
timeoutMs
|
|
3125
|
+
})).find((entry) => entry.targetId === targetId)) return {
|
|
3126
|
+
left: true,
|
|
3127
|
+
note: "Meet tab is already closed."
|
|
3128
|
+
};
|
|
3129
|
+
let leaveResult;
|
|
3130
|
+
try {
|
|
3131
|
+
leaveResult = await leaveMeetInPage({
|
|
3132
|
+
callBrowser: params.callBrowser,
|
|
3133
|
+
meetingUrl: params.meetingUrl,
|
|
3134
|
+
targetId,
|
|
3135
|
+
timeoutMs
|
|
3136
|
+
});
|
|
3137
|
+
} catch (error) {
|
|
3138
|
+
return {
|
|
3139
|
+
left: false,
|
|
3140
|
+
note: `Browser control could not verify the Meet tab before leaving: ${error instanceof Error ? error.message : String(error)}`
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
if (leaveResult.urlMatched === false) return {
|
|
3144
|
+
left: true,
|
|
3145
|
+
note: "Meet tab moved away from this session; left its current page untouched."
|
|
3146
|
+
};
|
|
3147
|
+
if (leaveResult.urlMatched !== true) return {
|
|
3148
|
+
left: false,
|
|
3149
|
+
note: "Browser control could not verify that the tracked tab still showed this meeting."
|
|
3150
|
+
};
|
|
3151
|
+
const { clickedLeave, departed } = leaveResult;
|
|
3152
|
+
if (!openedByPlugin) return {
|
|
3153
|
+
left: departed,
|
|
3154
|
+
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."
|
|
3155
|
+
};
|
|
3156
|
+
await params.callBrowser({
|
|
3157
|
+
method: "DELETE",
|
|
3158
|
+
path: `/tabs/${targetId}`,
|
|
3159
|
+
timeoutMs
|
|
3160
|
+
});
|
|
3161
|
+
return {
|
|
3162
|
+
left: true,
|
|
3163
|
+
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)."
|
|
3164
|
+
};
|
|
3165
|
+
} catch (error) {
|
|
3166
|
+
return {
|
|
3167
|
+
left: false,
|
|
3168
|
+
note: `Browser control could not leave the Meet tab: ${error instanceof Error ? error.message : String(error)}`
|
|
3169
|
+
};
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
async function leaveChromeMeet(params) {
|
|
3173
|
+
return await leaveMeetWithBrowserRequest({
|
|
3174
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
3175
|
+
config: params.config,
|
|
3176
|
+
meetingUrl: params.meetingUrl,
|
|
3177
|
+
tab: params.tab
|
|
3178
|
+
});
|
|
3179
|
+
}
|
|
3180
|
+
async function readChromeMeetTranscript(params) {
|
|
3181
|
+
return await readMeetTranscriptWithBrowserRequest({
|
|
3182
|
+
callBrowser: await resolveLocalBrowserRequest(params.runtime),
|
|
3183
|
+
finalize: params.finalize === true,
|
|
3184
|
+
meetingUrl: params.meetingUrl,
|
|
3185
|
+
meetingSessionId: params.meetingSessionId,
|
|
3186
|
+
tab: params.tab,
|
|
3187
|
+
timeoutMs: Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 1e4)
|
|
3188
|
+
});
|
|
3189
|
+
}
|
|
3190
|
+
async function readChromeMeetTranscriptOnNode(params) {
|
|
3191
|
+
const nodeId = params.nodeId ?? await resolveChromeNode({
|
|
3192
|
+
runtime: params.runtime,
|
|
3193
|
+
requestedNode: params.config.chromeNode.node
|
|
3194
|
+
});
|
|
3195
|
+
const timeoutMs = Math.min(Math.max(1e3, params.config.chrome.joinTimeoutMs), 1e4);
|
|
3196
|
+
return await readMeetTranscriptWithBrowserRequest({
|
|
3197
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3198
|
+
runtime: params.runtime,
|
|
3199
|
+
nodeId,
|
|
3200
|
+
method: request.method,
|
|
3201
|
+
path: request.path,
|
|
3202
|
+
body: request.body,
|
|
3203
|
+
timeoutMs: request.timeoutMs
|
|
3204
|
+
}),
|
|
3205
|
+
finalize: params.finalize === true,
|
|
3206
|
+
meetingUrl: params.meetingUrl,
|
|
3207
|
+
meetingSessionId: params.meetingSessionId,
|
|
3208
|
+
tab: params.tab,
|
|
3209
|
+
timeoutMs
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
async function leaveChromeMeetOnNode(params) {
|
|
3213
|
+
const nodeId = params.nodeId ?? await resolveChromeNode({
|
|
3214
|
+
runtime: params.runtime,
|
|
3215
|
+
requestedNode: params.config.chromeNode.node
|
|
3216
|
+
});
|
|
3217
|
+
return await leaveMeetWithBrowserRequest({
|
|
3218
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3219
|
+
runtime: params.runtime,
|
|
3220
|
+
nodeId,
|
|
3221
|
+
method: request.method,
|
|
3222
|
+
path: request.path,
|
|
3223
|
+
body: request.body,
|
|
3224
|
+
timeoutMs: request.timeoutMs
|
|
3225
|
+
}),
|
|
3226
|
+
config: params.config,
|
|
3227
|
+
meetingUrl: params.meetingUrl,
|
|
3228
|
+
tab: params.tab
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
async function openMeetWithBrowserProxy(params) {
|
|
3232
|
+
return await openMeetWithBrowserRequest({
|
|
3233
|
+
callBrowser: async (request) => await callBrowserProxyOnNode({
|
|
3234
|
+
runtime: params.runtime,
|
|
3235
|
+
nodeId: params.nodeId,
|
|
3236
|
+
method: request.method,
|
|
3237
|
+
path: request.path,
|
|
3238
|
+
body: request.body,
|
|
3239
|
+
timeoutMs: request.timeoutMs
|
|
3240
|
+
}),
|
|
3241
|
+
config: params.config,
|
|
3242
|
+
mode: params.mode,
|
|
3243
|
+
meetingSessionId: params.meetingSessionId,
|
|
3244
|
+
url: params.url
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
async function openMeetWithBrowserRequest(params) {
|
|
3248
|
+
if (!params.config.chrome.launch) return { launched: false };
|
|
3249
|
+
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
3250
|
+
let targetId;
|
|
3251
|
+
let tab;
|
|
3252
|
+
let openUrl = params.url;
|
|
3253
|
+
let openedByPlugin = false;
|
|
3254
|
+
if (params.config.chrome.reuseExistingTab) {
|
|
3255
|
+
const matchingTabs = asBrowserTabs(await params.callBrowser({
|
|
3256
|
+
method: "GET",
|
|
3257
|
+
path: "/tabs",
|
|
3258
|
+
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
3259
|
+
})).filter((entry) => isSameMeetUrlForReuse(entry.url, params.url));
|
|
3260
|
+
const requestedAuthUser = readMeetAuthUser(params.url);
|
|
3261
|
+
tab = matchingTabs.find((entry) => isEnglishMeetTab(entry.url) && (!requestedAuthUser || readMeetAuthUser(entry.url) === requestedAuthUser));
|
|
3262
|
+
if (!tab) {
|
|
3263
|
+
if (!new URL(params.url).searchParams.has("authuser")) openUrl = matchingTabs.find((entry) => entry.url)?.url ?? params.url;
|
|
3264
|
+
}
|
|
3265
|
+
targetId = tab?.targetId;
|
|
3266
|
+
if (tab && targetId) await params.callBrowser({
|
|
3267
|
+
method: "POST",
|
|
3268
|
+
path: "/tabs/focus",
|
|
3269
|
+
body: { targetId },
|
|
3270
|
+
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
if (!targetId) {
|
|
3274
|
+
tab = readBrowserTab(await params.callBrowser({
|
|
3275
|
+
method: "POST",
|
|
3276
|
+
path: "/tabs/open",
|
|
3277
|
+
body: { url: forceMeetEnglishUi(openUrl) },
|
|
3278
|
+
timeoutMs
|
|
3279
|
+
}));
|
|
3280
|
+
targetId = tab?.targetId;
|
|
3281
|
+
openedByPlugin = Boolean(targetId);
|
|
3282
|
+
}
|
|
3283
|
+
if (!targetId) return {
|
|
3284
|
+
launched: true,
|
|
3285
|
+
browser: {
|
|
3286
|
+
status: "browser-control",
|
|
3287
|
+
notes: ["Browser proxy opened Meet but did not return a targetId."],
|
|
3288
|
+
browserUrl: tab?.url,
|
|
3289
|
+
browserTitle: tab?.title
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
const tabIdentity = {
|
|
3293
|
+
targetId,
|
|
3294
|
+
openedByPlugin
|
|
3295
|
+
};
|
|
3296
|
+
const permissionNotes = await grantMeetMediaPermissions({
|
|
3297
|
+
allowMicrophone: isGoogleMeetTalkBackMode$2(params.mode),
|
|
3298
|
+
callBrowser: params.callBrowser,
|
|
3299
|
+
targetId,
|
|
3300
|
+
timeoutMs
|
|
2662
3301
|
});
|
|
2663
3302
|
const deadline = Date.now() + Math.max(0, params.config.chrome.waitForInCallMs);
|
|
2664
3303
|
let browser = {
|
|
@@ -2677,6 +3316,7 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2677
3316
|
targetId,
|
|
2678
3317
|
fn: meetStatusScript({
|
|
2679
3318
|
allowMicrophone: isGoogleMeetTalkBackMode$2(params.mode),
|
|
3319
|
+
captionSessionId: params.meetingSessionId,
|
|
2680
3320
|
captureCaptions: params.mode === "transcribe",
|
|
2681
3321
|
guestName: params.config.chrome.guestName,
|
|
2682
3322
|
autoJoin: params.config.chrome.autoJoin
|
|
@@ -2686,11 +3326,13 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2686
3326
|
})) ?? browser, permissionNotes);
|
|
2687
3327
|
if (browser?.inCall === true && (!isGoogleMeetTalkBackMode$2(params.mode) || browser.micMuted !== true)) return {
|
|
2688
3328
|
launched: true,
|
|
2689
|
-
browser
|
|
3329
|
+
browser,
|
|
3330
|
+
tab: tabIdentity
|
|
2690
3331
|
};
|
|
2691
3332
|
if (browser?.manualActionRequired === true) return {
|
|
2692
3333
|
launched: true,
|
|
2693
|
-
browser
|
|
3334
|
+
browser,
|
|
3335
|
+
tab: tabIdentity
|
|
2694
3336
|
};
|
|
2695
3337
|
} catch (error) {
|
|
2696
3338
|
browser = {
|
|
@@ -2710,7 +3352,8 @@ async function openMeetWithBrowserRequest(params) {
|
|
|
2710
3352
|
} while (Date.now() < deadline);
|
|
2711
3353
|
return {
|
|
2712
3354
|
launched: true,
|
|
2713
|
-
browser
|
|
3355
|
+
browser,
|
|
3356
|
+
tab: tabIdentity
|
|
2714
3357
|
};
|
|
2715
3358
|
}
|
|
2716
3359
|
function isRecoverableMeetTab(tab, url) {
|
|
@@ -2718,6 +3361,13 @@ function isRecoverableMeetTab(tab, url) {
|
|
|
2718
3361
|
if (normalizeMeetUrlForReuse(tab.url)) return true;
|
|
2719
3362
|
return (tab.url ?? "").startsWith("https://accounts.google.com/") && /sign in|google accounts|meet/i.test(tab.title ?? "");
|
|
2720
3363
|
}
|
|
3364
|
+
function findRecoverableMeetTab(tabs, url) {
|
|
3365
|
+
const candidates = tabs.filter((tab) => isRecoverableMeetTab(tab, url));
|
|
3366
|
+
if (!url) return candidates[0];
|
|
3367
|
+
const requestedAuthUser = readMeetAuthUser(url);
|
|
3368
|
+
const accountCandidates = requestedAuthUser ? candidates.filter((tab) => readMeetAuthUser(tab.url) === requestedAuthUser) : candidates;
|
|
3369
|
+
return accountCandidates.find((tab) => isEnglishMeetTab(tab.url)) ?? accountCandidates[0];
|
|
3370
|
+
}
|
|
2721
3371
|
async function inspectRecoverableMeetTab(params) {
|
|
2722
3372
|
const allowMicrophone = params.mode !== "transcribe";
|
|
2723
3373
|
await params.callBrowser({
|
|
@@ -2726,6 +3376,23 @@ async function inspectRecoverableMeetTab(params) {
|
|
|
2726
3376
|
body: { targetId: params.targetId },
|
|
2727
3377
|
timeoutMs: Math.min(params.timeoutMs, 5e3)
|
|
2728
3378
|
});
|
|
3379
|
+
if (normalizeMeetUrlForReuse(params.tab.url) && !isEnglishMeetTab(params.tab.url)) {
|
|
3380
|
+
const manualActionMessage = "The existing Meet tab is not pinned to English. Open the meeting with ?hl=en, then retry recovery.";
|
|
3381
|
+
return {
|
|
3382
|
+
found: true,
|
|
3383
|
+
targetId: params.targetId,
|
|
3384
|
+
tab: params.tab,
|
|
3385
|
+
browser: {
|
|
3386
|
+
status: "browser-control",
|
|
3387
|
+
browserUrl: params.tab.url,
|
|
3388
|
+
browserTitle: params.tab.title,
|
|
3389
|
+
manualActionRequired: true,
|
|
3390
|
+
manualActionReason: "meet-locale-required",
|
|
3391
|
+
manualActionMessage
|
|
3392
|
+
},
|
|
3393
|
+
message: manualActionMessage
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
2729
3396
|
const permissionNotes = params.readOnly ? [] : await grantMeetMediaPermissions({
|
|
2730
3397
|
allowMicrophone,
|
|
2731
3398
|
callBrowser: params.callBrowser,
|
|
@@ -2763,11 +3430,12 @@ async function inspectRecoverableMeetTab(params) {
|
|
|
2763
3430
|
}
|
|
2764
3431
|
async function recoverCurrentMeetTab(params) {
|
|
2765
3432
|
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
2766
|
-
const
|
|
3433
|
+
const callBrowser = await resolveLocalBrowserRequest(params.runtime);
|
|
3434
|
+
const tab = findRecoverableMeetTab(asBrowserTabs(await callBrowser({
|
|
2767
3435
|
method: "GET",
|
|
2768
3436
|
path: "/tabs",
|
|
2769
3437
|
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
2770
|
-
}))
|
|
3438
|
+
})), params.url);
|
|
2771
3439
|
const targetId = tab?.targetId;
|
|
2772
3440
|
if (!tab || !targetId) return {
|
|
2773
3441
|
transport: "chrome",
|
|
@@ -2778,7 +3446,7 @@ async function recoverCurrentMeetTab(params) {
|
|
|
2778
3446
|
return {
|
|
2779
3447
|
transport: "chrome",
|
|
2780
3448
|
...await inspectRecoverableMeetTab({
|
|
2781
|
-
callBrowser
|
|
3449
|
+
callBrowser,
|
|
2782
3450
|
config: params.config,
|
|
2783
3451
|
mode: params.mode,
|
|
2784
3452
|
readOnly: params.readOnly,
|
|
@@ -2794,13 +3462,13 @@ async function recoverCurrentMeetTabOnNode(params) {
|
|
|
2794
3462
|
requestedNode: params.config.chromeNode.node
|
|
2795
3463
|
});
|
|
2796
3464
|
const timeoutMs = Math.max(1e3, params.config.chrome.joinTimeoutMs);
|
|
2797
|
-
const tab = asBrowserTabs(await callBrowserProxyOnNode({
|
|
3465
|
+
const tab = findRecoverableMeetTab(asBrowserTabs(await callBrowserProxyOnNode({
|
|
2798
3466
|
runtime: params.runtime,
|
|
2799
3467
|
nodeId,
|
|
2800
3468
|
method: "GET",
|
|
2801
3469
|
path: "/tabs",
|
|
2802
3470
|
timeoutMs: Math.min(timeoutMs, 5e3)
|
|
2803
|
-
}))
|
|
3471
|
+
})), params.url);
|
|
2804
3472
|
const targetId = tab?.targetId;
|
|
2805
3473
|
if (!tab || !targetId) return {
|
|
2806
3474
|
transport: "chrome-node",
|
|
@@ -2854,6 +3522,7 @@ async function launchChromeMeetOnNode(params) {
|
|
|
2854
3522
|
nodeId,
|
|
2855
3523
|
config: params.config,
|
|
2856
3524
|
mode: params.mode,
|
|
3525
|
+
meetingSessionId: params.meetingSessionId,
|
|
2857
3526
|
url: params.url
|
|
2858
3527
|
});
|
|
2859
3528
|
const result = parseNodeStartResult(await params.runtime.nodes.invoke({
|
|
@@ -2895,19 +3564,22 @@ async function launchChromeMeetOnNode(params) {
|
|
|
2895
3564
|
nodeId,
|
|
2896
3565
|
launched: browserControl.launched || result.launched === true,
|
|
2897
3566
|
audioBridge: bridge,
|
|
2898
|
-
browser: browserControl.browser ?? result.browser
|
|
3567
|
+
browser: browserControl.browser ?? result.browser,
|
|
3568
|
+
tab: browserControl.tab
|
|
2899
3569
|
};
|
|
2900
3570
|
}
|
|
2901
3571
|
if (result.audioBridge?.type === "external-command") return {
|
|
2902
3572
|
nodeId,
|
|
2903
3573
|
launched: browserControl.launched || result.launched === true,
|
|
2904
3574
|
audioBridge: { type: "external-command" },
|
|
2905
|
-
browser: browserControl.browser ?? result.browser
|
|
3575
|
+
browser: browserControl.browser ?? result.browser,
|
|
3576
|
+
tab: browserControl.tab
|
|
2906
3577
|
};
|
|
2907
3578
|
return {
|
|
2908
3579
|
nodeId,
|
|
2909
3580
|
launched: browserControl.launched || result.launched === true,
|
|
2910
|
-
browser: browserControl.browser ?? result.browser
|
|
3581
|
+
browser: browserControl.browser ?? result.browser,
|
|
3582
|
+
tab: browserControl.tab
|
|
2911
3583
|
};
|
|
2912
3584
|
}
|
|
2913
3585
|
//#endregion
|
|
@@ -3075,28 +3747,33 @@ function nowIso() {
|
|
|
3075
3747
|
function buildTwilioVoiceCallSessionKey(meetingSessionId) {
|
|
3076
3748
|
return `voice:google-meet:${meetingSessionId}`;
|
|
3077
3749
|
}
|
|
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
3750
|
function resolveTransport(input, config) {
|
|
3092
3751
|
return input ?? config.defaultTransport;
|
|
3093
3752
|
}
|
|
3094
3753
|
function resolveMode(input, config) {
|
|
3095
3754
|
return input === "realtime" ? "agent" : input ?? config.defaultMode;
|
|
3096
3755
|
}
|
|
3756
|
+
function resolveSessionAgentId(request, config) {
|
|
3757
|
+
return normalizeAgentId(request.agentId ?? config.realtime.agentId);
|
|
3758
|
+
}
|
|
3759
|
+
function withSessionAgentConfig(config, agentId) {
|
|
3760
|
+
return config.realtime.agentId === agentId ? config : {
|
|
3761
|
+
...config,
|
|
3762
|
+
realtime: {
|
|
3763
|
+
...config.realtime,
|
|
3764
|
+
agentId
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3097
3768
|
function isGoogleMeetTalkBackMode$1(mode) {
|
|
3098
3769
|
return mode === "agent" || mode === "bidi";
|
|
3099
3770
|
}
|
|
3771
|
+
function isBrowserTransport(transport) {
|
|
3772
|
+
return transport === "chrome" || transport === "chrome-node";
|
|
3773
|
+
}
|
|
3774
|
+
function isReusableMeetSession(session, params) {
|
|
3775
|
+
return session.state === "active" && isSameMeetUrlForReuse(session.url, params.url) && session.transport === params.transport && session.mode === params.mode && session.agentId === params.agentId;
|
|
3776
|
+
}
|
|
3100
3777
|
function hasRealtimeAudioOutputAdvanced(health, startOutputBytes) {
|
|
3101
3778
|
return (health?.lastOutputBytes ?? 0) > startOutputBytes;
|
|
3102
3779
|
}
|
|
@@ -3181,9 +3858,16 @@ async function commandExists(runtime, command) {
|
|
|
3181
3858
|
}
|
|
3182
3859
|
var GoogleMeetRuntime = class {
|
|
3183
3860
|
#sessions = /* @__PURE__ */ new Map();
|
|
3861
|
+
#sessionLeaves = /* @__PURE__ */ new Map();
|
|
3862
|
+
#browserMeetingLocks = /* @__PURE__ */ new Map();
|
|
3863
|
+
#createdBrowserTabs = /* @__PURE__ */ new Map();
|
|
3184
3864
|
#sessionStops = /* @__PURE__ */ new Map();
|
|
3185
3865
|
#sessionSpeakers = /* @__PURE__ */ new Map();
|
|
3186
3866
|
#sessionHealth = /* @__PURE__ */ new Map();
|
|
3867
|
+
#transcripts = /* @__PURE__ */ new Map();
|
|
3868
|
+
#transcriptCaptures = /* @__PURE__ */ new Map();
|
|
3869
|
+
#transcriptFinalizing = /* @__PURE__ */ new Set();
|
|
3870
|
+
#retiredTranscripts = /* @__PURE__ */ new Set();
|
|
3187
3871
|
#voiceCallGateway;
|
|
3188
3872
|
constructor(params) {
|
|
3189
3873
|
this.params = params;
|
|
@@ -3210,6 +3894,28 @@ var GoogleMeetRuntime = class {
|
|
|
3210
3894
|
session
|
|
3211
3895
|
} : { found: false };
|
|
3212
3896
|
}
|
|
3897
|
+
async transcript(sessionId, options = {}) {
|
|
3898
|
+
const session = this.#sessions.get(sessionId);
|
|
3899
|
+
if (!session) return { found: false };
|
|
3900
|
+
if (session.mode !== "transcribe") throw new Error("transcript is only available for transcribe-mode sessions");
|
|
3901
|
+
const sinceIndex = options.sinceIndex ?? 0;
|
|
3902
|
+
if (!Number.isSafeInteger(sinceIndex) || sinceIndex < 0) throw new Error("sinceIndex must be a non-negative safe integer");
|
|
3903
|
+
if (session.state === "active" && !this.#transcriptFinalizing.has(session.id)) await this.#captureTranscriptSnapshot(session);
|
|
3904
|
+
const snapshot = this.#transcripts.get(sessionId) ?? {
|
|
3905
|
+
droppedLines: 0,
|
|
3906
|
+
lines: []
|
|
3907
|
+
};
|
|
3908
|
+
const startIndex = Math.max(sinceIndex, snapshot.droppedLines);
|
|
3909
|
+
return {
|
|
3910
|
+
found: true,
|
|
3911
|
+
sessionId,
|
|
3912
|
+
startIndex,
|
|
3913
|
+
nextIndex: snapshot.droppedLines + snapshot.lines.length,
|
|
3914
|
+
droppedLines: snapshot.droppedLines,
|
|
3915
|
+
...session.transcriptEvicted ? { evicted: true } : {},
|
|
3916
|
+
lines: snapshot.lines.slice(startIndex - snapshot.droppedLines)
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3213
3919
|
async setupStatus(options = {}) {
|
|
3214
3920
|
const transport = resolveTransport(options.transport, this.params.config);
|
|
3215
3921
|
const mode = resolveMode(options.mode, this.params.config);
|
|
@@ -3273,10 +3979,12 @@ var GoogleMeetRuntime = class {
|
|
|
3273
3979
|
return status;
|
|
3274
3980
|
}
|
|
3275
3981
|
async createViaBrowser() {
|
|
3276
|
-
|
|
3982
|
+
const result = await createMeetWithBrowserProxyOnNode({
|
|
3277
3983
|
runtime: this.params.runtime,
|
|
3278
3984
|
config: this.params.config
|
|
3279
3985
|
});
|
|
3986
|
+
if (result.openedByPlugin && result.targetId) this.#createdBrowserTabs.set(`${result.nodeId}:${result.targetId}`, result.meetingUri);
|
|
3987
|
+
return result;
|
|
3280
3988
|
}
|
|
3281
3989
|
async recoverCurrentTab(request = {}) {
|
|
3282
3990
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
@@ -3288,6 +3996,7 @@ var GoogleMeetRuntime = class {
|
|
|
3288
3996
|
url
|
|
3289
3997
|
});
|
|
3290
3998
|
return recoverCurrentMeetTab({
|
|
3999
|
+
runtime: this.params.runtime,
|
|
3291
4000
|
config: this.params.config,
|
|
3292
4001
|
url
|
|
3293
4002
|
});
|
|
@@ -3296,7 +4005,51 @@ var GoogleMeetRuntime = class {
|
|
|
3296
4005
|
const url = normalizeMeetUrl(request.url);
|
|
3297
4006
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
3298
4007
|
const mode = resolveMode(request.mode, this.params.config);
|
|
3299
|
-
|
|
4008
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
4009
|
+
if (!isBrowserTransport(transport)) return await this.#joinUnlocked(request, {
|
|
4010
|
+
url,
|
|
4011
|
+
transport,
|
|
4012
|
+
mode,
|
|
4013
|
+
agentId
|
|
4014
|
+
});
|
|
4015
|
+
return await this.#withBrowserMeetingLock(transport, url, async () => await this.#joinUnlocked(request, {
|
|
4016
|
+
url,
|
|
4017
|
+
transport,
|
|
4018
|
+
mode,
|
|
4019
|
+
agentId
|
|
4020
|
+
}));
|
|
4021
|
+
}
|
|
4022
|
+
async #joinUnlocked(request, resolved) {
|
|
4023
|
+
const { url, transport, mode, agentId } = resolved;
|
|
4024
|
+
const activeSessions = this.list().filter((session) => session.state === "active" && isSameMeetUrlForReuse(session.url, url) && session.transport === transport);
|
|
4025
|
+
const retainedBrowserTabs = [];
|
|
4026
|
+
if (isBrowserTransport(transport) && isGoogleMeetTalkBackMode$1(mode)) for (const session of activeSessions) {
|
|
4027
|
+
if (!isGoogleMeetTalkBackMode$1(session.mode) || isReusableMeetSession(session, {
|
|
4028
|
+
url,
|
|
4029
|
+
transport,
|
|
4030
|
+
mode,
|
|
4031
|
+
agentId
|
|
4032
|
+
})) continue;
|
|
4033
|
+
const tab = this.params.config.chrome.reuseExistingTab ? session.chrome?.browserTab : void 0;
|
|
4034
|
+
const keepBrowserParticipant = Boolean(tab) || session.chrome?.launched === false;
|
|
4035
|
+
if (tab) retainedBrowserTabs.push({
|
|
4036
|
+
session,
|
|
4037
|
+
tab
|
|
4038
|
+
});
|
|
4039
|
+
try {
|
|
4040
|
+
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.");
|
|
4041
|
+
} catch (error) {
|
|
4042
|
+
await this.#settleRetainedBrowserTabs(retainedBrowserTabs);
|
|
4043
|
+
throw error;
|
|
4044
|
+
}
|
|
4045
|
+
noteSession(session, "Ended before the same Meet tab was reassigned to another agent.");
|
|
4046
|
+
}
|
|
4047
|
+
let reusable = activeSessions.find((session) => isReusableMeetSession(session, {
|
|
4048
|
+
url,
|
|
4049
|
+
transport,
|
|
4050
|
+
mode,
|
|
4051
|
+
agentId
|
|
4052
|
+
}));
|
|
3300
4053
|
if (reusable?.transport === "twilio") {
|
|
3301
4054
|
await this.#refreshTwilioVoiceCallStatus(reusable);
|
|
3302
4055
|
if (reusable.state !== "active") reusable = void 0;
|
|
@@ -3319,6 +4072,7 @@ var GoogleMeetRuntime = class {
|
|
|
3319
4072
|
url,
|
|
3320
4073
|
transport,
|
|
3321
4074
|
mode,
|
|
4075
|
+
agentId,
|
|
3322
4076
|
state: "active",
|
|
3323
4077
|
createdAt,
|
|
3324
4078
|
updatedAt: createdAt,
|
|
@@ -3335,40 +4089,60 @@ var GoogleMeetRuntime = class {
|
|
|
3335
4089
|
};
|
|
3336
4090
|
try {
|
|
3337
4091
|
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
|
-
|
|
4092
|
+
const chromeConfig = withSessionAgentConfig(this.params.config, agentId);
|
|
4093
|
+
let result;
|
|
4094
|
+
try {
|
|
4095
|
+
result = transport === "chrome-node" ? await launchChromeMeetOnNode({
|
|
4096
|
+
runtime: this.params.runtime,
|
|
4097
|
+
config: chromeConfig,
|
|
4098
|
+
fullConfig: this.params.fullConfig,
|
|
4099
|
+
meetingSessionId: session.id,
|
|
4100
|
+
requesterSessionKey: request.requesterSessionKey,
|
|
4101
|
+
mode,
|
|
4102
|
+
url,
|
|
4103
|
+
logger: this.params.logger
|
|
4104
|
+
}) : await launchChromeMeet({
|
|
4105
|
+
runtime: this.params.runtime,
|
|
4106
|
+
config: chromeConfig,
|
|
4107
|
+
fullConfig: this.params.fullConfig,
|
|
4108
|
+
meetingSessionId: session.id,
|
|
4109
|
+
requesterSessionKey: request.requesterSessionKey,
|
|
4110
|
+
mode,
|
|
4111
|
+
url,
|
|
4112
|
+
logger: this.params.logger
|
|
4113
|
+
});
|
|
4114
|
+
} catch (error) {
|
|
4115
|
+
await this.#settleRetainedBrowserTabs(retainedBrowserTabs);
|
|
4116
|
+
throw error;
|
|
4117
|
+
}
|
|
4118
|
+
const resultNodeId = "nodeId" in result ? result.nodeId : void 0;
|
|
4119
|
+
const browserTab = this.#inheritBrowserTabOwnership({
|
|
4120
|
+
transport,
|
|
4121
|
+
nodeId: resultNodeId,
|
|
4122
|
+
meetingUrl: url,
|
|
4123
|
+
tab: result.tab
|
|
3363
4124
|
});
|
|
3364
4125
|
session.chrome = {
|
|
3365
4126
|
audioBackend: this.params.config.chrome.audioBackend,
|
|
3366
4127
|
launched: result.launched,
|
|
3367
|
-
nodeId:
|
|
4128
|
+
nodeId: resultNodeId,
|
|
3368
4129
|
browserProfile: this.params.config.chrome.browserProfile,
|
|
4130
|
+
browserTab,
|
|
3369
4131
|
health: "browser" in result ? result.browser : void 0
|
|
3370
4132
|
};
|
|
3371
4133
|
this.#attachChromeAudioBridge(session, result.audioBridge);
|
|
4134
|
+
if (!await this.#settleRetainedBrowserTabs(retainedBrowserTabs, browserTab ? {
|
|
4135
|
+
transport,
|
|
4136
|
+
nodeId: resultNodeId,
|
|
4137
|
+
tab: browserTab
|
|
4138
|
+
} : void 0)) {
|
|
4139
|
+
try {
|
|
4140
|
+
await this.#leaveSession(session);
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
this.params.logger.warn(`[google-meet] replacement cleanup failed: ${formatErrorMessage(error)}`);
|
|
4143
|
+
}
|
|
4144
|
+
throw new Error("Could not leave the previous Meet browser tab before reassignment.");
|
|
4145
|
+
}
|
|
3372
4146
|
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
4147
|
this.#refreshSpeechReadiness(session);
|
|
3374
4148
|
} else {
|
|
@@ -3379,6 +4153,7 @@ var GoogleMeetRuntime = class {
|
|
|
3379
4153
|
dtmfSequence: request.dtmfSequence ?? this.params.config.twilio.defaultDtmfSequence
|
|
3380
4154
|
});
|
|
3381
4155
|
const dtmfSequence = request.dtmfSequence || this.params.config.twilio.defaultDtmfSequence ? rawDtmfSequence : prefixDtmfWait(rawDtmfSequence, this.params.config.voiceCall.dtmfDelayMs);
|
|
4156
|
+
const delegatedAgentId = Boolean(normalizeOptionalString(request.agentId) || normalizeOptionalString(this.params.config.realtime.agentId)) ? agentId : void 0;
|
|
3382
4157
|
const voiceCallResult = this.params.config.voiceCall.enabled ? await joinMeetViaVoiceCallGateway({
|
|
3383
4158
|
config: this.params.config,
|
|
3384
4159
|
gateway: this.#voiceCallGateway,
|
|
@@ -3386,8 +4161,8 @@ var GoogleMeetRuntime = class {
|
|
|
3386
4161
|
dtmfSequence,
|
|
3387
4162
|
logger: this.params.logger,
|
|
3388
4163
|
...request.requesterSessionKey ? { requesterSessionKey: request.requesterSessionKey } : {},
|
|
3389
|
-
agentId:
|
|
3390
|
-
sessionKey:
|
|
4164
|
+
agentId: delegatedAgentId,
|
|
4165
|
+
sessionKey: delegatedAgentId ? `agent:${delegatedAgentId}:google-meet:${session.id}` : buildTwilioVoiceCallSessionKey(session.id),
|
|
3391
4166
|
message: isGoogleMeetTalkBackMode$1(mode) ? request.message ?? this.params.config.voiceCall.introMessage ?? this.params.config.realtime.introMessage : void 0
|
|
3392
4167
|
}) : void 0;
|
|
3393
4168
|
delegatedTwilioSpoken = Boolean(voiceCallResult?.introSent);
|
|
@@ -3417,28 +4192,218 @@ var GoogleMeetRuntime = class {
|
|
|
3417
4192
|
spoken: transport === "twilio" ? delegatedTwilioSpoken : isGoogleMeetTalkBackMode$1(mode) && speechInstructions ? await this.#speakWhenReady(session, speechInstructions) : false
|
|
3418
4193
|
};
|
|
3419
4194
|
}
|
|
3420
|
-
async leave(sessionId) {
|
|
4195
|
+
async leave(sessionId, opts) {
|
|
4196
|
+
const session = this.#sessions.get(sessionId);
|
|
4197
|
+
if (!session) return { found: false };
|
|
4198
|
+
if (!isBrowserTransport(session.transport)) return await this.#leaveUnlocked(sessionId, opts);
|
|
4199
|
+
return await this.#withBrowserMeetingLock(session.transport, session.url, async () => await this.#leaveUnlocked(sessionId, opts));
|
|
4200
|
+
}
|
|
4201
|
+
async #leaveUnlocked(sessionId, opts) {
|
|
4202
|
+
const inFlight = this.#sessionLeaves.get(sessionId);
|
|
4203
|
+
if (inFlight) return await inFlight;
|
|
3421
4204
|
const session = this.#sessions.get(sessionId);
|
|
3422
4205
|
if (!session) return { found: false };
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
4206
|
+
if (session.state === "ended") return {
|
|
4207
|
+
found: true,
|
|
4208
|
+
session,
|
|
4209
|
+
...session.browserLeft === void 0 ? {} : { browserLeft: session.browserLeft }
|
|
4210
|
+
};
|
|
4211
|
+
const leave = this.#leaveSession(session, opts);
|
|
4212
|
+
this.#sessionLeaves.set(sessionId, leave);
|
|
4213
|
+
try {
|
|
4214
|
+
return await leave;
|
|
4215
|
+
} finally {
|
|
4216
|
+
if (this.#sessionLeaves.get(sessionId) === leave) this.#sessionLeaves.delete(sessionId);
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
async #withBrowserMeetingLock(transport, url, operation) {
|
|
4220
|
+
const key = `${transport}:${normalizeMeetUrlForReuse(url) ?? url}`;
|
|
4221
|
+
const previous = this.#browserMeetingLocks.get(key) ?? Promise.resolve();
|
|
4222
|
+
let release;
|
|
4223
|
+
const gate = new Promise((resolve) => {
|
|
4224
|
+
release = resolve;
|
|
4225
|
+
});
|
|
4226
|
+
const tail = previous.then(() => gate);
|
|
4227
|
+
this.#browserMeetingLocks.set(key, tail);
|
|
4228
|
+
await previous;
|
|
4229
|
+
try {
|
|
4230
|
+
return await operation();
|
|
4231
|
+
} finally {
|
|
4232
|
+
release?.();
|
|
4233
|
+
if (this.#browserMeetingLocks.get(key) === tail) this.#browserMeetingLocks.delete(key);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
async #leaveSession(session, opts) {
|
|
4237
|
+
if (session.mode === "transcribe") {
|
|
4238
|
+
this.#transcriptFinalizing.add(session.id);
|
|
4239
|
+
await this.#captureTranscriptSnapshot(session, { finalize: true }).catch((error) => {
|
|
4240
|
+
this.params.logger.debug?.(`[google-meet] final transcript snapshot ignored: ${formatErrorMessage(error)}`);
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
session.state = "ended";
|
|
4244
|
+
session.updatedAt = nowIso();
|
|
4245
|
+
const stop = this.#sessionStops.get(session.id);
|
|
4246
|
+
this.#sessionStops.delete(session.id);
|
|
4247
|
+
this.#sessionSpeakers.delete(session.id);
|
|
4248
|
+
this.#sessionHealth.delete(session.id);
|
|
4249
|
+
let browserLeft;
|
|
4250
|
+
try {
|
|
4251
|
+
await stop?.();
|
|
4252
|
+
} finally {
|
|
3428
4253
|
try {
|
|
3429
|
-
await
|
|
4254
|
+
if (!opts?.keepBrowserTab) browserLeft = await this.#releaseBrowserTab(session);
|
|
3430
4255
|
} finally {
|
|
3431
|
-
session.
|
|
3432
|
-
session.
|
|
4256
|
+
this.#retireTranscript(session.id);
|
|
4257
|
+
this.#transcriptFinalizing.delete(session.id);
|
|
3433
4258
|
}
|
|
3434
4259
|
}
|
|
3435
|
-
session.state = "ended";
|
|
3436
|
-
session.updatedAt = nowIso();
|
|
3437
4260
|
return {
|
|
3438
4261
|
found: true,
|
|
3439
|
-
session
|
|
4262
|
+
session,
|
|
4263
|
+
...browserLeft === void 0 ? {} : { browserLeft }
|
|
3440
4264
|
};
|
|
3441
4265
|
}
|
|
4266
|
+
async #captureTranscriptSnapshot(session, options = {}) {
|
|
4267
|
+
const capture = (this.#transcriptCaptures.get(session.id) ?? Promise.resolve()).catch(() => {}).then(async () => {
|
|
4268
|
+
if (!isBrowserTransport(session.transport) || session.mode !== "transcribe") return;
|
|
4269
|
+
const tab = session.chrome?.browserTab;
|
|
4270
|
+
if (!tab) return;
|
|
4271
|
+
const snapshot = session.transport === "chrome-node" ? await readChromeMeetTranscriptOnNode({
|
|
4272
|
+
runtime: this.params.runtime,
|
|
4273
|
+
nodeId: session.chrome?.nodeId,
|
|
4274
|
+
config: this.params.config,
|
|
4275
|
+
...options.finalize === void 0 ? {} : { finalize: options.finalize },
|
|
4276
|
+
meetingUrl: session.url,
|
|
4277
|
+
meetingSessionId: session.id,
|
|
4278
|
+
tab
|
|
4279
|
+
}) : await readChromeMeetTranscript({
|
|
4280
|
+
runtime: this.params.runtime,
|
|
4281
|
+
config: this.params.config,
|
|
4282
|
+
...options.finalize === void 0 ? {} : { finalize: options.finalize },
|
|
4283
|
+
meetingUrl: session.url,
|
|
4284
|
+
meetingSessionId: session.id,
|
|
4285
|
+
tab
|
|
4286
|
+
});
|
|
4287
|
+
this.#mergeTranscriptSnapshot(session.id, snapshot);
|
|
4288
|
+
});
|
|
4289
|
+
this.#transcriptCaptures.set(session.id, capture);
|
|
4290
|
+
try {
|
|
4291
|
+
await capture;
|
|
4292
|
+
} finally {
|
|
4293
|
+
if (this.#transcriptCaptures.get(session.id) === capture) this.#transcriptCaptures.delete(session.id);
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
#mergeTranscriptSnapshot(sessionId, snapshot) {
|
|
4297
|
+
const pageNextIndex = snapshot.droppedLines + snapshot.lines.length;
|
|
4298
|
+
const retained = this.#transcripts.get(sessionId);
|
|
4299
|
+
if (!retained) {
|
|
4300
|
+
this.#transcripts.set(sessionId, {
|
|
4301
|
+
droppedLines: snapshot.droppedLines,
|
|
4302
|
+
lines: snapshot.lines,
|
|
4303
|
+
pageEpoch: snapshot.epoch,
|
|
4304
|
+
pageNextIndex
|
|
4305
|
+
});
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
if (retained.pageEpoch !== snapshot.epoch) {
|
|
4309
|
+
retained.droppedLines += snapshot.droppedLines;
|
|
4310
|
+
retained.lines.push(...snapshot.lines);
|
|
4311
|
+
retained.pageEpoch = snapshot.epoch;
|
|
4312
|
+
retained.pageNextIndex = pageNextIndex;
|
|
4313
|
+
} else if (pageNextIndex > retained.pageNextIndex) {
|
|
4314
|
+
const appendFrom = Math.max(retained.pageNextIndex, snapshot.droppedLines);
|
|
4315
|
+
retained.droppedLines += Math.max(0, snapshot.droppedLines - retained.pageNextIndex);
|
|
4316
|
+
retained.lines.push(...snapshot.lines.slice(appendFrom - snapshot.droppedLines));
|
|
4317
|
+
retained.pageNextIndex = pageNextIndex;
|
|
4318
|
+
}
|
|
4319
|
+
const excess = retained.lines.length - GOOGLE_MEET_TRANSCRIPT_MAX_LINES;
|
|
4320
|
+
if (excess > 0) {
|
|
4321
|
+
retained.lines.splice(0, excess);
|
|
4322
|
+
retained.droppedLines += excess;
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
#retireTranscript(sessionId) {
|
|
4326
|
+
const snapshot = this.#transcripts.get(sessionId);
|
|
4327
|
+
if (snapshot) {
|
|
4328
|
+
this.#transcripts.delete(sessionId);
|
|
4329
|
+
this.#transcripts.set(sessionId, snapshot);
|
|
4330
|
+
this.#retiredTranscripts.delete(sessionId);
|
|
4331
|
+
this.#retiredTranscripts.add(sessionId);
|
|
4332
|
+
}
|
|
4333
|
+
const retainedIds = [...this.#retiredTranscripts].filter((id) => this.#transcripts.has(id)).toSorted((a, b) => (this.#sessions.get(a)?.updatedAt ?? "").localeCompare(this.#sessions.get(b)?.updatedAt ?? ""));
|
|
4334
|
+
for (const id of retainedIds.slice(0, -4)) {
|
|
4335
|
+
this.#transcripts.delete(id);
|
|
4336
|
+
this.#retiredTranscripts.delete(id);
|
|
4337
|
+
const session = this.#sessions.get(id);
|
|
4338
|
+
if (session) session.transcriptEvicted = true;
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
#inheritBrowserTabOwnership(params) {
|
|
4342
|
+
if (!params.tab) return;
|
|
4343
|
+
const tab = params.tab;
|
|
4344
|
+
const createdTabKey = params.transport === "chrome-node" && params.nodeId ? `${params.nodeId}:${tab.targetId}` : void 0;
|
|
4345
|
+
const createdMeetingUrl = createdTabKey ? this.#createdBrowserTabs.get(createdTabKey) : void 0;
|
|
4346
|
+
const inheritedFromCreate = isSameMeetUrlForReuse(createdMeetingUrl, params.meetingUrl);
|
|
4347
|
+
if (createdMeetingUrl && createdTabKey) this.#createdBrowserTabs.delete(createdTabKey);
|
|
4348
|
+
const inheritedFromSession = [...this.#sessions.values()].some((session) => {
|
|
4349
|
+
const trackedTab = session.chrome?.browserTab;
|
|
4350
|
+
if (!trackedTab) return false;
|
|
4351
|
+
return session.transport === params.transport && isSameMeetUrlForReuse(session.url, params.meetingUrl) && session.chrome?.nodeId === params.nodeId && trackedTab.targetId === tab.targetId && trackedTab.openedByPlugin;
|
|
4352
|
+
});
|
|
4353
|
+
return inheritedFromCreate || inheritedFromSession ? {
|
|
4354
|
+
...tab,
|
|
4355
|
+
openedByPlugin: true
|
|
4356
|
+
} : tab;
|
|
4357
|
+
}
|
|
4358
|
+
async #settleRetainedBrowserTabs(retained, adopted) {
|
|
4359
|
+
let settled = true;
|
|
4360
|
+
for (const { session, tab } of retained.splice(0)) {
|
|
4361
|
+
if (adopted?.transport === session.transport && adopted.nodeId === session.chrome?.nodeId && adopted.tab.targetId === tab.targetId) {
|
|
4362
|
+
if (session.chrome) session.chrome.browserTab = void 0;
|
|
4363
|
+
continue;
|
|
4364
|
+
}
|
|
4365
|
+
if (await this.#releaseBrowserTab(session) === false) settled = false;
|
|
4366
|
+
}
|
|
4367
|
+
return settled;
|
|
4368
|
+
}
|
|
4369
|
+
async #releaseBrowserTab(session) {
|
|
4370
|
+
let browserLeft;
|
|
4371
|
+
try {
|
|
4372
|
+
browserLeft = await this.#leaveBrowserMeetTab(session);
|
|
4373
|
+
} catch (error) {
|
|
4374
|
+
noteSession(session, `Browser control could not leave the Meet tab: ${formatErrorMessage(error)}`);
|
|
4375
|
+
browserLeft = false;
|
|
4376
|
+
}
|
|
4377
|
+
session.browserLeft = browserLeft;
|
|
4378
|
+
if (session.chrome && browserLeft !== false) session.chrome.browserTab = void 0;
|
|
4379
|
+
return browserLeft;
|
|
4380
|
+
}
|
|
4381
|
+
async #leaveBrowserMeetTab(session) {
|
|
4382
|
+
if (!isBrowserTransport(session.transport)) return;
|
|
4383
|
+
const tab = session.chrome?.browserTab;
|
|
4384
|
+
if (!tab) {
|
|
4385
|
+
noteSession(session, "No tracked Meet browser tab for this session; close the Meet tab manually if it is still in the call.");
|
|
4386
|
+
return false;
|
|
4387
|
+
}
|
|
4388
|
+
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)) {
|
|
4389
|
+
noteSession(session, "Kept the shared Meet tab open because another active session uses it.");
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
const result = session.transport === "chrome-node" ? await leaveChromeMeetOnNode({
|
|
4393
|
+
runtime: this.params.runtime,
|
|
4394
|
+
nodeId: session.chrome?.nodeId,
|
|
4395
|
+
config: this.params.config,
|
|
4396
|
+
meetingUrl: session.url,
|
|
4397
|
+
tab
|
|
4398
|
+
}) : await leaveChromeMeet({
|
|
4399
|
+
runtime: this.params.runtime,
|
|
4400
|
+
config: this.params.config,
|
|
4401
|
+
meetingUrl: session.url,
|
|
4402
|
+
tab
|
|
4403
|
+
});
|
|
4404
|
+
noteSession(session, result.note);
|
|
4405
|
+
return result.left;
|
|
4406
|
+
}
|
|
3442
4407
|
async speak(sessionId, instructions) {
|
|
3443
4408
|
const session = this.#sessions.get(sessionId);
|
|
3444
4409
|
if (!session) return {
|
|
@@ -3519,9 +4484,16 @@ var GoogleMeetRuntime = class {
|
|
|
3519
4484
|
const mode = requestedMode && isGoogleMeetTalkBackMode$1(requestedMode) ? requestedMode : isGoogleMeetTalkBackMode$1(this.params.config.defaultMode) ? this.params.config.defaultMode : "agent";
|
|
3520
4485
|
const url = normalizeMeetUrl(request.url);
|
|
3521
4486
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
4487
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
3522
4488
|
const beforeSessions = this.list();
|
|
3523
4489
|
const before = new Set(beforeSessions.map((session) => session.id));
|
|
3524
|
-
const
|
|
4490
|
+
const existingSession = beforeSessions.find((session) => isReusableMeetSession(session, {
|
|
4491
|
+
url,
|
|
4492
|
+
transport,
|
|
4493
|
+
mode,
|
|
4494
|
+
agentId
|
|
4495
|
+
}));
|
|
4496
|
+
const existingOutputBytes = existingSession?.chrome?.health?.lastOutputBytes ?? 0;
|
|
3525
4497
|
const result = await this.join({
|
|
3526
4498
|
...request,
|
|
3527
4499
|
transport,
|
|
@@ -3529,6 +4501,7 @@ var GoogleMeetRuntime = class {
|
|
|
3529
4501
|
mode,
|
|
3530
4502
|
message: request.message ?? "Say exactly: Google Meet speech test complete."
|
|
3531
4503
|
});
|
|
4504
|
+
const startOutputBytes = existingSession?.id === result.session.id ? existingOutputBytes : 0;
|
|
3532
4505
|
let health = result.session.chrome?.health;
|
|
3533
4506
|
const shouldWaitForOutput = result.spoken === true && health?.manualActionRequired !== true && this.#sessionHealth.has(result.session.id);
|
|
3534
4507
|
if (shouldWaitForOutput && !hasRealtimeAudioOutputAdvanced(health, startOutputBytes)) {
|
|
@@ -3564,9 +4537,16 @@ var GoogleMeetRuntime = class {
|
|
|
3564
4537
|
const url = normalizeMeetUrl(request.url);
|
|
3565
4538
|
const transport = resolveTransport(request.transport, this.params.config);
|
|
3566
4539
|
if (transport === "twilio") throw new Error("test_listen supports chrome or chrome-node transports");
|
|
4540
|
+
const agentId = resolveSessionAgentId(request, this.params.config);
|
|
3567
4541
|
const beforeSessions = this.list();
|
|
3568
4542
|
const before = new Set(beforeSessions.map((session) => session.id));
|
|
3569
|
-
const
|
|
4543
|
+
const existingSession = beforeSessions.find((session) => isReusableMeetSession(session, {
|
|
4544
|
+
url,
|
|
4545
|
+
transport,
|
|
4546
|
+
mode: "transcribe",
|
|
4547
|
+
agentId
|
|
4548
|
+
}));
|
|
4549
|
+
const existingStart = transcriptCheckpoint(existingSession?.chrome?.health);
|
|
3570
4550
|
const result = await this.join({
|
|
3571
4551
|
...request,
|
|
3572
4552
|
transport,
|
|
@@ -3574,6 +4554,7 @@ var GoogleMeetRuntime = class {
|
|
|
3574
4554
|
mode: "transcribe",
|
|
3575
4555
|
message: void 0
|
|
3576
4556
|
});
|
|
4557
|
+
const start = existingSession?.id === result.session.id ? existingStart : transcriptCheckpoint(void 0);
|
|
3577
4558
|
let health = result.session.chrome?.health;
|
|
3578
4559
|
const timeoutMs = resolveProbeTimeoutMs(request.timeoutMs, this.params.config.chrome.joinTimeoutMs);
|
|
3579
4560
|
const shouldWait = health?.manualActionRequired !== true && isManagedChromeBrowserSession(result.session);
|
|
@@ -3667,6 +4648,7 @@ var GoogleMeetRuntime = class {
|
|
|
3667
4648
|
readOnly: options.readOnly,
|
|
3668
4649
|
url: session.url
|
|
3669
4650
|
}) : await recoverCurrentMeetTab({
|
|
4651
|
+
runtime: this.params.runtime,
|
|
3670
4652
|
config: this.params.config,
|
|
3671
4653
|
mode: session.mode,
|
|
3672
4654
|
readOnly: options.readOnly,
|
|
@@ -3700,12 +4682,13 @@ var GoogleMeetRuntime = class {
|
|
|
3700
4682
|
if (!isGoogleMeetTalkBackMode$1(session.mode) || session.transport !== "chrome" || session.state !== "active" || !session.chrome || session.chrome.audioBridge) return;
|
|
3701
4683
|
const health = session.chrome.health;
|
|
3702
4684
|
if (health?.inCall !== true || health.micMuted === true || health.manualActionRequired === true) return;
|
|
4685
|
+
const sessionConfig = withSessionAgentConfig(this.params.config, session.agentId);
|
|
3703
4686
|
const result = await launchChromeMeet({
|
|
3704
4687
|
runtime: this.params.runtime,
|
|
3705
4688
|
config: {
|
|
3706
|
-
...
|
|
4689
|
+
...sessionConfig,
|
|
3707
4690
|
chrome: {
|
|
3708
|
-
...
|
|
4691
|
+
...sessionConfig.chrome,
|
|
3709
4692
|
launch: false
|
|
3710
4693
|
}
|
|
3711
4694
|
},
|
|
@@ -3744,154 +4727,9 @@ var GoogleMeetRuntime = class {
|
|
|
3744
4727
|
}
|
|
3745
4728
|
};
|
|
3746
4729
|
//#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
4730
|
//#region extensions/google-meet/index.ts
|
|
3893
|
-
const loadGoogleMeetCreateModule = createLazyRuntimeModule(() => import("./create-
|
|
3894
|
-
const loadGoogleMeetCliModule = createLazyRuntimeModule(() => import("./cli-
|
|
4731
|
+
const loadGoogleMeetCreateModule = createLazyRuntimeModule(() => import("./create-CWcFI0ub.js"));
|
|
4732
|
+
const loadGoogleMeetCliModule = createLazyRuntimeModule(() => import("./cli-SodPSOFX.js"));
|
|
3895
4733
|
const googleMeetConfigSchema = {
|
|
3896
4734
|
parse(value) {
|
|
3897
4735
|
return resolveGoogleMeetConfig(value);
|
|
@@ -4099,6 +4937,7 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
4099
4937
|
"join",
|
|
4100
4938
|
"create",
|
|
4101
4939
|
"status",
|
|
4940
|
+
"transcript",
|
|
4102
4941
|
"setup_status",
|
|
4103
4942
|
"resolve_space",
|
|
4104
4943
|
"preflight",
|
|
@@ -4150,6 +4989,10 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
4150
4989
|
pin: Type.Optional(Type.String({ description: "Meet phone PIN for Twilio; # is appended if omitted" })),
|
|
4151
4990
|
dtmfSequence: Type.Optional(Type.String({ description: "Explicit DTMF sequence for Twilio" })),
|
|
4152
4991
|
sessionId: Type.Optional(Type.String({ description: "Meet session ID" })),
|
|
4992
|
+
sinceIndex: Type.Optional(Type.Integer({
|
|
4993
|
+
description: "For transcript, resume from the previous response's nextIndex.",
|
|
4994
|
+
minimum: 0
|
|
4995
|
+
})),
|
|
4153
4996
|
message: Type.Optional(Type.String({ description: "Realtime instructions to speak now" })),
|
|
4154
4997
|
timeoutMs: optionalPositiveIntegerSchema({ description: "Probe timeout in milliseconds" }),
|
|
4155
4998
|
meeting: Type.Optional(Type.String({ description: "Meet URL, meeting code, or spaces/{id}" })),
|
|
@@ -4236,11 +5079,17 @@ function readGatewayErrorDetails(err) {
|
|
|
4236
5079
|
}
|
|
4237
5080
|
async function callGoogleMeetGatewayFromTool(params) {
|
|
4238
5081
|
try {
|
|
4239
|
-
if (params.runtime) return await params.runtime.gateway.request(googleMeetGatewayMethodForToolAction(params.action), params.raw, {
|
|
5082
|
+
if (params.runtime) return await params.runtime.gateway.request(googleMeetGatewayMethodForToolAction(params.action), params.raw, {
|
|
5083
|
+
timeoutMs: resolveGoogleMeetGatewayOperationTimeoutMs(params.config),
|
|
5084
|
+
scopes: ["operator.admin"]
|
|
5085
|
+
});
|
|
4240
5086
|
return await googleMeetToolDeps.callGatewayFromCli(googleMeetGatewayMethodForToolAction(params.action), {
|
|
4241
5087
|
json: true,
|
|
4242
5088
|
timeout: String(resolveGoogleMeetGatewayOperationTimeoutMs(params.config))
|
|
4243
|
-
}, params.raw, {
|
|
5089
|
+
}, params.raw, {
|
|
5090
|
+
progress: false,
|
|
5091
|
+
scopes: ["operator.admin"]
|
|
5092
|
+
});
|
|
4244
5093
|
} catch (err) {
|
|
4245
5094
|
const details = readGatewayErrorDetails(err);
|
|
4246
5095
|
if (details && typeof details === "object") return details;
|
|
@@ -4263,7 +5112,7 @@ async function createAndJoinMeetFromParams(params) {
|
|
|
4263
5112
|
return (await loadGoogleMeetCreateModule()).createAndJoinMeetFromParams(params);
|
|
4264
5113
|
}
|
|
4265
5114
|
async function resolveGoogleMeetTokenFromParams(config, raw) {
|
|
4266
|
-
const { resolveGoogleMeetAccessToken } = await import("./oauth-
|
|
5115
|
+
const { resolveGoogleMeetAccessToken } = await import("./oauth-DOfMKlGp.js");
|
|
4267
5116
|
return resolveGoogleMeetAccessToken({
|
|
4268
5117
|
clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
|
|
4269
5118
|
clientSecret: normalizeOptionalString(raw.clientSecret) ?? config.oauth.clientSecret,
|
|
@@ -4462,6 +5311,23 @@ var google_meet_default = definePluginEntry({
|
|
|
4462
5311
|
sendError(respond, err);
|
|
4463
5312
|
}
|
|
4464
5313
|
});
|
|
5314
|
+
api.registerGatewayMethod("googlemeet.transcript", async ({ params, respond }) => {
|
|
5315
|
+
try {
|
|
5316
|
+
const sessionId = normalizeOptionalString(params?.sessionId);
|
|
5317
|
+
if (!sessionId) {
|
|
5318
|
+
sendError(respond, /* @__PURE__ */ new Error("sessionId required"), ErrorCodes.INVALID_REQUEST);
|
|
5319
|
+
return;
|
|
5320
|
+
}
|
|
5321
|
+
const sinceIndex = params?.sinceIndex;
|
|
5322
|
+
if (sinceIndex !== void 0 && (typeof sinceIndex !== "number" || !Number.isSafeInteger(sinceIndex) || sinceIndex < 0)) {
|
|
5323
|
+
sendError(respond, /* @__PURE__ */ new Error("sinceIndex must be a non-negative safe integer"), ErrorCodes.INVALID_REQUEST);
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
respond(true, await (await ensureRuntime()).transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex }));
|
|
5327
|
+
} catch (err) {
|
|
5328
|
+
sendError(respond, err);
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
4465
5331
|
api.registerGatewayMethod("googlemeet.recoverCurrentTab", async ({ params, respond }) => {
|
|
4466
5332
|
try {
|
|
4467
5333
|
respond(true, await (await ensureRuntime()).recoverCurrentTab({
|
|
@@ -4613,13 +5479,15 @@ var google_meet_default = definePluginEntry({
|
|
|
4613
5479
|
sendError(respond, err);
|
|
4614
5480
|
}
|
|
4615
5481
|
});
|
|
4616
|
-
api.registerGatewayMethod("googlemeet.testListen", async ({ params, respond }) => {
|
|
5482
|
+
api.registerGatewayMethod("googlemeet.testListen", async ({ params, client, respond }) => {
|
|
4617
5483
|
try {
|
|
5484
|
+
const trustedParams = keepTrustedToolAgentId(asParamRecord(params), client);
|
|
4618
5485
|
respond(true, await (await ensureRuntime()).testListen({
|
|
4619
|
-
url: resolveMeetingInput(config,
|
|
4620
|
-
transport: normalizeTransport(
|
|
4621
|
-
mode: normalizeMode(
|
|
4622
|
-
|
|
5486
|
+
url: resolveMeetingInput(config, trustedParams.url),
|
|
5487
|
+
transport: normalizeTransport(trustedParams.transport),
|
|
5488
|
+
mode: normalizeMode(trustedParams.mode),
|
|
5489
|
+
agentId: normalizeOptionalString(trustedParams.agentId),
|
|
5490
|
+
timeoutMs: readPositiveIntegerParam(trustedParams, "timeoutMs")
|
|
4623
5491
|
}));
|
|
4624
5492
|
} catch (err) {
|
|
4625
5493
|
sendError(respond, err);
|
|
@@ -4633,10 +5501,12 @@ var google_meet_default = definePluginEntry({
|
|
|
4633
5501
|
async execute(_toolCallId, params) {
|
|
4634
5502
|
const raw = asParamRecord(params);
|
|
4635
5503
|
const requesterSessionKey = normalizeOptionalString(toolContext.sessionKey);
|
|
4636
|
-
const
|
|
5504
|
+
const contextAgentId = toolContext.agentId ?? parseAgentSessionKey(requesterSessionKey)?.agentId;
|
|
5505
|
+
const agentId = contextAgentId ? normalizeAgentId(contextAgentId) : void 0;
|
|
4637
5506
|
try {
|
|
4638
|
-
const
|
|
4639
|
-
|
|
5507
|
+
const needsTrustedAgentRouting = Boolean(agentId && agentId !== "main");
|
|
5508
|
+
const useTrustedRuntime = needsTrustedAgentRouting ? await api.runtime.gateway.isAvailable() : false;
|
|
5509
|
+
if (needsTrustedAgentRouting && !useTrustedRuntime) throw new Error("Per-agent Google Meet routing requires a Gateway-hosted agent run.");
|
|
4640
5510
|
const rawWithRequester = {
|
|
4641
5511
|
...raw,
|
|
4642
5512
|
...requesterSessionKey ? { requesterSessionKey } : {},
|
|
@@ -4668,13 +5538,19 @@ var google_meet_default = definePluginEntry({
|
|
|
4668
5538
|
case "test_listen": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4669
5539
|
config,
|
|
4670
5540
|
action: "test_listen",
|
|
4671
|
-
raw
|
|
5541
|
+
raw: rawWithRequester,
|
|
5542
|
+
runtime: useTrustedRuntime ? api.runtime : void 0
|
|
4672
5543
|
}));
|
|
4673
5544
|
case "status": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4674
5545
|
config,
|
|
4675
5546
|
action: "status",
|
|
4676
5547
|
raw
|
|
4677
5548
|
}));
|
|
5549
|
+
case "transcript": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
5550
|
+
config,
|
|
5551
|
+
action: "transcript",
|
|
5552
|
+
raw
|
|
5553
|
+
}));
|
|
4678
5554
|
case "recover_current_tab": return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
4679
5555
|
config,
|
|
4680
5556
|
action: "recover_current_tab",
|