feat: ensure mic ON on pre-join + voice greeting via Gateway TTS

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-18 00:17:41 +01:00
parent aec15602c2
commit ad8c858ce4

View file

@ -137,6 +137,9 @@ export class BotOrchestrator {
// STEP 1: Navigate to meeting URL and click "Continue on this browser" // STEP 1: Navigate to meeting URL and click "Continue on this browser"
await this._joinProcedure!.startMeetingLauncherFlow(this._meetingUrl); await this._joinProcedure!.startMeetingLauncherFlow(this._meetingUrl);
// Ensure microphone is ON (required for voice playback)
await this._ensureMicOn();
// STEP 2: Enter bot name and click "Join now" // STEP 2: Enter bot name and click "Join now"
await this._joinProcedure!.joinMeetingLobbyFlow(); await this._joinProcedure!.joinMeetingLobbyFlow();
@ -282,6 +285,9 @@ export class BotOrchestrator {
// Camera stays OFF — no video injection, focus on communication stability // Camera stays OFF — no video injection, focus on communication stability
this._logger.info('Camera left OFF (video disabled for stability)'); this._logger.info('Camera left OFF (video disabled for stability)');
// Ensure microphone is ON (required for voice playback)
await this._ensureMicOn();
await this._page!.waitForTimeout(2000); await this._page!.waitForTimeout(2000);
const joinNowSelectors = [ const joinNowSelectors = [
@ -470,6 +476,67 @@ export class BotOrchestrator {
} }
} }
/**
* Ensure the microphone is turned on in the pre-join screen.
* Required for voice playback (TTS audio is injected into the mic stream).
*
* Teams pre-join uses a fui-Switch input:
* <input data-tid="toggle-audio" role="switch" type="checkbox" checked>
* - checked present = mic ON
* - checked absent = mic OFF
*/
private async _ensureMicOn(): Promise<void> {
try {
let micToggle = await this._page!.$('input[data-tid="toggle-audio"]');
if (!micToggle) {
const fallbacks = [
'[data-tid="toggle-audio"]',
'input[role="switch"][title*="microphone" i]',
'input[role="switch"][title*="Mikrofon" i]',
'input[role="switch"][title*="mic" i]',
'input[role="switch"][title*="audio" i]',
];
for (const sel of fallbacks) {
micToggle = await this._page!.$(sel);
if (micToggle) {
this._logger.info(`Mic toggle found via fallback: ${sel}`);
break;
}
}
}
if (!micToggle) {
this._logger.warn('Mic toggle not found on pre-join screen');
return;
}
const state = await micToggle.evaluate((el: HTMLInputElement) => ({
checked: el.checked,
dataCid: el.getAttribute('data-cid') || '',
title: el.getAttribute('title') || '',
}));
this._logger.info(`Mic state: checked=${state.checked}, data-cid="${state.dataCid}", title="${state.title}"`);
if (!state.checked) {
await micToggle.click();
this._logger.info('Mic toggled ON');
await this._page!.waitForTimeout(1000);
const afterState = await micToggle.evaluate((el: HTMLInputElement) => ({
checked: el.checked,
dataCid: el.getAttribute('data-cid') || '',
}));
this._logger.info(`Mic after toggle: checked=${afterState.checked}, data-cid="${afterState.dataCid}"`);
} else {
this._logger.info('Mic already ON');
}
} catch (err) {
this._logger.warn(`Could not toggle mic: ${err}`);
}
}
/** /**
* Start a keepalive timer that periodically moves the mouse and sends * Start a keepalive timer that periodically moves the mouse and sends
* a WebSocket ping. Prevents Teams from detecting the bot as idle * a WebSocket ping. Prevents Teams from detecting the bot as idle
@ -1055,8 +1122,9 @@ export class BotOrchestrator {
} }
/** /**
* Send a greeting message in the meeting chat after joining. * Send a greeting message in the meeting chat AND via voice after joining.
* Uses the bot's display name and the configured language. * Uses the bot's display name and the configured language.
* Voice greeting confirms that the audio pipeline (TTS -> mic) is working.
*/ */
private async _sendJoinGreeting(): Promise<void> { private async _sendJoinGreeting(): Promise<void> {
try { try {
@ -1074,8 +1142,18 @@ export class BotOrchestrator {
greeting = `Hello, this is ${firstName}. I'm ready.`; greeting = `Hello, this is ${firstName}. I'm ready.`;
} }
this._logger.info(`Sending join greeting: ${greeting}`); this._logger.info(`Sending join greeting (chat + voice): ${greeting}`);
// Chat greeting
await this.sendChatMessageToMeeting(greeting); await this.sendChatMessageToMeeting(greeting);
// Voice greeting — ask Gateway to generate TTS and send back playAudio
this._sendToGateway({
type: 'voiceGreeting',
sessionId: this._sessionId,
text: greeting,
language: this._options.language || 'de-DE',
});
} catch (error) { } catch (error) {
this._logger.warn('Could not send join greeting:', error); this._logger.warn('Could not send join greeting:', error);
} }