diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/__tests__/recall-bot-automatic-leave.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/__tests__/recall-bot-automatic-leave.test.ts new file mode 100644 index 0000000000..9270177c7b --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/__tests__/recall-bot-automatic-leave.test.ts @@ -0,0 +1,87 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { getRecallBotAutomaticLeave } from 'src/logic-functions/constants/recall-bot-automatic-leave'; + +const ENV_KEYS = [ + 'CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS', + 'CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS', + 'CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS', +] as const; +const AUTOMATIC_LEAVE_ARGUMENTS = { + botDetectionActivateAfterSeconds: 300, +}; + +describe('getRecallBotAutomaticLeave', () => { + const originalEnv: Record = {}; + + beforeEach(() => { + for (const key of ENV_KEYS) { + originalEnv[key] = process.env[key]; + delete process.env[key]; + } + }); + + afterEach(() => { + for (const key of ENV_KEYS) { + if (originalEnv[key] === undefined) { + delete process.env[key]; + } else { + process.env[key] = originalEnv[key]; + } + } + }); + + it('enables bot detection even when no timeout env vars are set', () => { + const automaticLeave = getRecallBotAutomaticLeave( + AUTOMATIC_LEAVE_ARGUMENTS, + ); + + expect(automaticLeave.everyone_left_timeout).toBeUndefined(); + expect( + automaticLeave.bot_detection.using_participant_names.matches, + ).toContain('notetaker'); + }); + + it('includes the configured bot name in the name matches so co-scheduled bots recognize each other', () => { + const automaticLeave = getRecallBotAutomaticLeave({ + ...AUTOMATIC_LEAVE_ARGUMENTS, + botName: 'Twenty.com', + }); + + expect( + automaticLeave.bot_detection.using_participant_names.matches, + ).toContain('Twenty.com'); + }); + + it('enables behavioral (participant events) detection', () => { + const automaticLeave = getRecallBotAutomaticLeave( + AUTOMATIC_LEAVE_ARGUMENTS, + ); + + expect(automaticLeave.bot_detection.using_participant_events).toBeDefined(); + }); + + it('enables silence detection as the fallback when neither bot detector matches', () => { + const automaticLeave = getRecallBotAutomaticLeave( + AUTOMATIC_LEAVE_ARGUMENTS, + ); + + expect(automaticLeave.silence_detection).toEqual({ + activate_after: 1200, + timeout: 300, + }); + }); + + it('still emits the existing everyone_left_timeout when its env var is set', () => { + process.env.CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS = '2'; + + const automaticLeave = getRecallBotAutomaticLeave( + AUTOMATIC_LEAVE_ARGUMENTS, + ); + + expect(automaticLeave.everyone_left_timeout).toEqual({ + timeout: 2, + activate_after: 1, + }); + }); +}); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-automatic-leave.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-automatic-leave.ts index 498805e065..24291ab226 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-automatic-leave.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-automatic-leave.ts @@ -4,9 +4,28 @@ import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/lo import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name'; import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name'; import { RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS } from 'src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds'; +import { + RECALL_BOT_DETECTION_USING_PARTICIPANT_EVENTS_TIMEOUT_SECONDS, + RECALL_BOT_DETECTION_USING_PARTICIPANT_NAMES_MIN_TIMEOUT_SECONDS, +} from 'src/logic-functions/constants/recall-bot-detection-timeouts'; +import { RECALL_BOT_SILENCE_DETECTION_ACTIVATE_AFTER_SECONDS } from 'src/logic-functions/constants/recall-bot-silence-detection-activate-after-seconds'; +import { RECALL_BOT_SILENCE_DETECTION_TIMEOUT_SECONDS } from 'src/logic-functions/constants/recall-bot-silence-detection-timeout-seconds'; import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util'; +import { getCallRecorderBotDetectionNameMatches } from 'src/logic-functions/utils/get-call-recorder-bot-detection-name-matches.util'; import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util'; +type RecallBotDetection = { + using_participant_names: { + matches: string[]; + activate_after: number; + timeout: number; + }; + using_participant_events: { + activate_after: number; + timeout: number; + }; +}; + type RecallBotAutomaticLeave = { waiting_room_timeout?: number; noone_joined_timeout?: number; @@ -14,11 +33,20 @@ type RecallBotAutomaticLeave = { timeout: number; activate_after: number; }; + bot_detection: RecallBotDetection; + silence_detection: { + activate_after: number; + timeout: number; + }; }; -export const getRecallBotAutomaticLeave = (): - | RecallBotAutomaticLeave - | undefined => { +export const getRecallBotAutomaticLeave = ({ + botDetectionActivateAfterSeconds, + botName, +}: { + botDetectionActivateAfterSeconds: number; + botName?: string; +}): RecallBotAutomaticLeave => { const waitingRoomTimeoutSeconds = getOptionalPositiveIntegerVariable( CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME, ); @@ -29,7 +57,16 @@ export const getRecallBotAutomaticLeave = (): CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME, ); - const automaticLeave: RecallBotAutomaticLeave = {}; + const automaticLeave: RecallBotAutomaticLeave = { + bot_detection: getRecallBotDetection({ + botDetectionActivateAfterSeconds, + botName, + }), + silence_detection: { + activate_after: RECALL_BOT_SILENCE_DETECTION_ACTIVATE_AFTER_SECONDS, + timeout: RECALL_BOT_SILENCE_DETECTION_TIMEOUT_SECONDS, + }, + }; if (!isUndefined(waitingRoomTimeoutSeconds)) { automaticLeave.waiting_room_timeout = waitingRoomTimeoutSeconds; @@ -46,9 +83,27 @@ export const getRecallBotAutomaticLeave = (): }; } - return Object.keys(automaticLeave).length === 0 ? undefined : automaticLeave; + return automaticLeave; }; +const getRecallBotDetection = ({ + botDetectionActivateAfterSeconds, + botName, +}: { + botDetectionActivateAfterSeconds: number; + botName?: string; +}): RecallBotDetection => ({ + using_participant_names: { + matches: getCallRecorderBotDetectionNameMatches(botName), + activate_after: botDetectionActivateAfterSeconds, + timeout: RECALL_BOT_DETECTION_USING_PARTICIPANT_NAMES_MIN_TIMEOUT_SECONDS, + }, + using_participant_events: { + activate_after: botDetectionActivateAfterSeconds, + timeout: RECALL_BOT_DETECTION_USING_PARTICIPANT_EVENTS_TIMEOUT_SECONDS, + }, +}); + const getOptionalPositiveIntegerVariable = ( variableName: string, ): number | undefined => { diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-name-matches.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-name-matches.ts new file mode 100644 index 0000000000..c4fc701840 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-name-matches.ts @@ -0,0 +1,21 @@ +// Substrings Recall matches (case-insensitive) against participant names to +// classify them as recording bots rather than humans. Covers our own bot plus +// common third-party notetakers so a call full of bots is treated as empty. +export const RECALL_BOT_DETECTION_DEFAULT_NAME_MATCHES = [ + 'notetaker', + 'note taker', + 'recorder', + 'recording', + 'transcriber', + 'otter', + 'fireflies', + 'tl;dv', + 'tldv', + 'grain', + 'read.ai', + 'fathom', + 'fellow', + 'avoma', + 'sembly', + 'nyota', +]; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-timeouts.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-timeouts.ts new file mode 100644 index 0000000000..a688319845 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-detection-timeouts.ts @@ -0,0 +1,3 @@ +export const RECALL_BOT_DETECTION_GRACE_AFTER_MEETING_START_SECONDS = 300; +export const RECALL_BOT_DETECTION_USING_PARTICIPANT_NAMES_MIN_TIMEOUT_SECONDS = 10; +export const RECALL_BOT_DETECTION_USING_PARTICIPANT_EVENTS_TIMEOUT_SECONDS = 10; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-activate-after-seconds.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-activate-after-seconds.ts new file mode 100644 index 0000000000..e63aba3b40 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-activate-after-seconds.ts @@ -0,0 +1 @@ +export const RECALL_BOT_SILENCE_DETECTION_ACTIVATE_AFTER_SECONDS = 1200; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-timeout-seconds.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-timeout-seconds.ts new file mode 100644 index 0000000000..7ac747ed55 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/constants/recall-bot-silence-detection-timeout-seconds.ts @@ -0,0 +1 @@ +export const RECALL_BOT_SILENCE_DETECTION_TIMEOUT_SECONDS = 300; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/__tests__/compute-recall-bot-detection-activate-after-seconds.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/__tests__/compute-recall-bot-detection-activate-after-seconds.test.ts new file mode 100644 index 0000000000..7405041821 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/__tests__/compute-recall-bot-detection-activate-after-seconds.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; + +import { computeRecallBotDetectionActivateAfterSeconds } from 'src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util'; + +describe('computeRecallBotDetectionActivateAfterSeconds', () => { + it('activates five minutes after the meeting starts when the bot joins one minute early', () => { + expect( + computeRecallBotDetectionActivateAfterSeconds({ + botJoinsAt: '2026-01-01T12:59:00.000Z', + meetingStartsAt: '2026-01-01T13:00:00.000Z', + }), + ).toBe(360); + }); + + it('adds the early-join duration to the grace period', () => { + expect( + computeRecallBotDetectionActivateAfterSeconds({ + botJoinsAt: '2026-01-01T12:45:00.000Z', + meetingStartsAt: '2026-01-01T13:00:00.000Z', + }), + ).toBe(1_200); + }); + + it('uses only the grace period when the bot joins after the meeting starts', () => { + expect( + computeRecallBotDetectionActivateAfterSeconds({ + botJoinsAt: '2026-01-01T13:05:00.000Z', + meetingStartsAt: '2026-01-01T13:00:00.000Z', + }), + ).toBe(300); + }); +}); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util.ts new file mode 100644 index 0000000000..1f779917dc --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util.ts @@ -0,0 +1,24 @@ +import { RECALL_BOT_DETECTION_GRACE_AFTER_MEETING_START_SECONDS } from 'src/logic-functions/constants/recall-bot-detection-timeouts'; + +const MILLISECONDS_PER_SECOND = 1_000; + +export const computeRecallBotDetectionActivateAfterSeconds = ({ + botJoinsAt, + meetingStartsAt, +}: { + botJoinsAt: string; + meetingStartsAt: string; +}): number => { + const millisecondsUntilMeetingStarts = + new Date(meetingStartsAt).getTime() - new Date(botJoinsAt).getTime(); + + const secondsUntilMeetingStarts = Math.max( + 0, + Math.ceil(millisecondsUntilMeetingStarts / MILLISECONDS_PER_SECOND), + ); + + return ( + secondsUntilMeetingStarts + + RECALL_BOT_DETECTION_GRACE_AFTER_MEETING_START_SECONDS + ); +}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts index a8134fed1b..6d60fa613c 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts @@ -42,6 +42,7 @@ export const rescheduleCallRecordingBot = async ( const rescheduleResult = await rescheduleRecallBot({ externalBotId, meetingUrl, + meetingStartsAt, joinAt, metadata: buildRecallRoutingMetadata({ callRecordingId: callRecording.id, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/schedule-recall-bot-for-call-recording.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/schedule-recall-bot-for-call-recording.util.ts index 6719047f7d..c0806d47cf 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/schedule-recall-bot-for-call-recording.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/schedule-recall-bot-for-call-recording.util.ts @@ -86,6 +86,7 @@ export const scheduleRecallBotForCallRecording = async ( const scheduleResult = await scheduleRecallBot({ meetingUrl, + meetingStartsAt, joinAt, metadata, automaticVideoOutput, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts index 4a26938158..4239e73b5b 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts @@ -16,6 +16,7 @@ import { RECALL_API_KEY_ENV_VAR_NAME } from 'src/logic-functions/constants/recal import { RECALL_REGION_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-region-env-var-name'; const NOW = new Date('2026-01-01T12:00:00.000Z'); +const MEETING_STARTS_AT = '2026-01-01T13:00:00.000Z'; const WORKSPACE_ID = '123e4567-e89b-12d3-a456-426614174000'; const RECALL_ROUTING_METADATA = { twentyWorkspaceId: WORKSPACE_ID, @@ -65,6 +66,7 @@ describe('recall bot api', () => { it('creates Recall bot requests with the Token authorization scheme', async () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -84,6 +86,23 @@ describe('recall bot api', () => { meeting_url: 'https://meet.google.com/abc-defg-hij', join_at: '2026-01-01T13:00:00.000Z', bot_name: 'Call Recorder', + automatic_leave: { + bot_detection: { + using_participant_names: { + matches: expect.arrayContaining(['Call Recorder', 'notetaker']), + activate_after: 300, + timeout: 10, + }, + using_participant_events: { + activate_after: 300, + timeout: 10, + }, + }, + silence_detection: { + activate_after: 1200, + timeout: 300, + }, + }, recording_config: { video_mixed_mp4: {}, audio_mixed_mp3: {}, @@ -98,6 +117,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -118,6 +138,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -141,6 +162,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -163,6 +185,7 @@ describe('recall bot api', () => { const result = await rescheduleRecallBot({ externalBotId: 'recall-bot-gone', meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -187,6 +210,7 @@ describe('recall bot api', () => { await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }); @@ -698,6 +722,7 @@ describe('recall bot api', () => { }); const scheduleArguments = { meetingUrl: 'https://meet.google.com/abc-defg-hij', + meetingStartsAt: MEETING_STARTS_AT, joinAt: '2026-01-01T13:00:00.000Z', metadata: RECALL_ROUTING_METADATA, }; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/reschedule-recall-bot.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/reschedule-recall-bot.util.ts index 6d35eb0936..f6b9860753 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/reschedule-recall-bot.util.ts @@ -1,5 +1,3 @@ -import { isUndefined } from '@sniptt/guards'; - import { getRecallBotAutomaticLeave } from 'src/logic-functions/constants/recall-bot-automatic-leave'; import { getRecallBotRecordingConfig } from 'src/logic-functions/constants/recall-bot-recording-config'; import { type RecallBotScheduleResult } from 'src/logic-functions/types/recall-bot-operation-result.type'; @@ -7,6 +5,7 @@ import { extractRecallBotId, type RecallBotResponse, } from 'src/logic-functions/recall-api/extract-recall-bot-id.util'; +import { computeRecallBotDetectionActivateAfterSeconds } from 'src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util'; import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util'; import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util'; import { type ScheduleRecallBotArgs } from 'src/logic-functions/recall-api/schedule-recall-bot.util'; @@ -19,6 +18,7 @@ type RescheduleRecallBotArgs = ScheduleRecallBotArgs & { export const rescheduleRecallBot = async ({ externalBotId, meetingUrl, + meetingStartsAt, joinAt, metadata, }: RescheduleRecallBotArgs): Promise => { @@ -28,7 +28,15 @@ export const rescheduleRecallBot = async ({ return { ok: false, status: null, errorMessage: configResult.error }; } - const automaticLeave = getRecallBotAutomaticLeave(); + const effectiveJoinAt = computeMaximumJoinAt(joinAt); + const automaticLeave = getRecallBotAutomaticLeave({ + botDetectionActivateAfterSeconds: + computeRecallBotDetectionActivateAfterSeconds({ + botJoinsAt: effectiveJoinAt, + meetingStartsAt, + }), + botName: configResult.config.botName, + }); const result = await recallBotApiRequest({ config: configResult.config, @@ -36,11 +44,9 @@ export const rescheduleRecallBot = async ({ method: 'PATCH', body: { meeting_url: meetingUrl, - join_at: computeMaximumJoinAt(joinAt), // We can't join in the past, so we floor this date 1s in the future + join_at: effectiveJoinAt, bot_name: configResult.config.botName, - ...(isUndefined(automaticLeave) - ? {} - : { automatic_leave: automaticLeave }), + automatic_leave: automaticLeave, recording_config: getRecallBotRecordingConfig(), metadata, }, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts index 2f1ab4baea..0629016142 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts @@ -11,12 +11,14 @@ import { extractRecallBotId, type RecallBotResponse, } from 'src/logic-functions/recall-api/extract-recall-bot-id.util'; +import { computeRecallBotDetectionActivateAfterSeconds } from 'src/logic-functions/domain/compute-recall-bot-detection-activate-after-seconds.util'; import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util'; import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util'; import { computeMaximumJoinAt } from 'src/logic-functions/recall-api/compute-maximum-join-at.utils'; export type ScheduleRecallBotArgs = { meetingUrl: string; + meetingStartsAt: string; joinAt: string; metadata: RecallRoutingMetadata; automaticVideoOutput?: RecallBotAutomaticVideoOutput; @@ -25,6 +27,7 @@ export type ScheduleRecallBotArgs = { export const scheduleRecallBot = async ({ meetingUrl, + meetingStartsAt, joinAt, metadata, automaticVideoOutput, @@ -40,7 +43,15 @@ export const scheduleRecallBot = async ({ return { ok: false, status: null, errorMessage: configResult.error }; } - const automaticLeave = getRecallBotAutomaticLeave(); + const effectiveJoinAt = computeMaximumJoinAt(joinAt); + const automaticLeave = getRecallBotAutomaticLeave({ + botDetectionActivateAfterSeconds: + computeRecallBotDetectionActivateAfterSeconds({ + botJoinsAt: effectiveJoinAt, + meetingStartsAt, + }), + botName: configResult.config.botName, + }); const result = await recallBotApiRequest({ config: configResult.config, @@ -49,11 +60,9 @@ export const scheduleRecallBot = async ({ idempotencyKey, body: { meeting_url: meetingUrl, - join_at: computeMaximumJoinAt(joinAt), // We can't join in the past, so we floor this date 1s in the future + join_at: effectiveJoinAt, bot_name: configResult.config.botName, - ...(isUndefined(automaticLeave) - ? {} - : { automatic_leave: automaticLeave }), + automatic_leave: automaticLeave, ...(isUndefined(automaticVideoOutput) ? {} : { automatic_video_output: automaticVideoOutput }), diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/utils/get-call-recorder-bot-detection-name-matches.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/utils/get-call-recorder-bot-detection-name-matches.util.ts new file mode 100644 index 0000000000..81b401d540 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/utils/get-call-recorder-bot-detection-name-matches.util.ts @@ -0,0 +1,9 @@ +import { RECALL_BOT_DETECTION_DEFAULT_NAME_MATCHES } from 'src/logic-functions/constants/recall-bot-detection-name-matches'; +import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util'; + +export const getCallRecorderBotDetectionNameMatches = ( + botName?: string, +): string[] => [ + ...(isNonEmptyString(botName) ? [botName.trim()] : []), + ...RECALL_BOT_DETECTION_DEFAULT_NAME_MATCHES, +];