rename twenty meeting bot to call recorder (#22093)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22093?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
nitin
2026-06-24 20:32:34 +05:30
committed by GitHub
parent 7d3cd5ed00
commit 0bccbb5035
258 changed files with 639 additions and 645 deletions
@@ -1,8 +1,8 @@
# Twenty Meeting Bot
# Call Recorder
Record your meetings automatically and keep every call inside your CRM. Twenty
Meeting Bot sends a recording bot to your team's calendar meetings, then stores
the video, audio, and a speaker-attributed transcript on the meeting's record —
Record your meetings automatically and keep every call inside your CRM. Call
Recorder sends a recording bot to your team's calendar meetings, then stores the
video, audio, and a speaker-attributed transcript on the meeting's record —
searchable, in context, and ready for people, AI agents, and workflows to act
on.
@@ -11,7 +11,7 @@ on.
1. A teammate has an upcoming meeting on a synced calendar with a video
conference link (Zoom, Google Meet, Microsoft Teams — anything Recall.ai
supports).
2. Because recording is on by default, the app schedules a meeting bot to join
2. Because recording is on by default, the app schedules a call recorder to join
that event shortly before it starts.
3. The bot joins under the configured display name and records the meeting's
audio and video for its duration.
@@ -34,7 +34,7 @@ on.
**CallRecording**: the mixed audio (MP3) and video (MP4), the transcript, the
call's actual start and end times, and a lifecycle status (`SCHEDULED`
`JOINING``RECORDING``PROCESSING``COMPLETED`, or `FAILED`), with a
Meeting Bot Failure Reason when failure details are available.
Call Recorder Failure Reason when failure details are available.
- **A default role.** A scoped application role that reads calendar events,
participants, people, and workspace members to decide attendance, and writes
the resulting CallRecording records, uploads recording media, and fills
@@ -71,7 +71,7 @@ the bot never recorded (opted out, canceled, or no one showed) is not charged.
## Installing
1. Open **Settings → Applications** in your Twenty workspace.
2. Find **Twenty Meeting Bot** and click **Install**.
2. Find **Call Recorder** and click **Install**.
3. A server admin completes the one-time
[Self-hosting setup](#self-hosting-setup-admin-only) below to wire up the
Recall.ai API key and webhook. On Twenty Cloud these may already be
@@ -108,7 +108,7 @@ What this app intentionally does **not** do in v1:
| Transcript empty, or marked pending/failed | Recall hasn't finished async transcription yet, or transcription failed for that call | Wait for the reconciliation job to ingest the transcript; a persistent failure leaves a marker in the transcript |
| Webhook rejected with `401` (Recall keeps retrying) | `RECALL_WEBHOOK_SECRET` doesn't match the Recall endpoint's signing secret | Re-copy the `whsec_…` secret from the Recall webhook endpoint into the `RECALL_WEBHOOK_SECRET` server variable |
| Webhook rejected with `500` about the secret | `RECALL_WEBHOOK_SECRET` is not set | Admin: set it on the application registration |
| Bot left almost immediately | No one was admitted before the lobby / no-one-joined timeout, or everyone left | Adjust `MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS` / `MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS` if too aggressive |
| Bot left almost immediately | No one was admitted before the lobby / no-one-joined timeout, or everyone left | Adjust `CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS` / `CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS` if too aggressive |
| Bot joined a meeting you didn't want recorded | Recording is on by default | Set the event's **Recording Bot** field to Off; the scheduled bot is canceled |
---
@@ -121,13 +121,13 @@ credentials may already be configured.
### Server variables
Set these on the application registration after installing (Settings →
Applications → Twenty Meeting Bot):
Applications → Call Recorder):
| Server variable | Required | Purpose |
|---|---|---|
| `RECALL_API_KEY` | Yes | Recall.ai API key for the configured region; used to schedule, update, and cancel bots. |
| `RECALL_REGION` | No | Recall.ai region for API requests. Defaults to `eu-central-1` (Europe / Frankfurt). |
| `MEETING_BOT_RECORDING_RETENTION_HOURS` | No | How long Recall.ai retains the source media after processing. Defaults to `166` hours (6 days 22 hours), just under Recall's 168-hour free-storage window. Values above `168` may incur Recall storage charges. Twenty's ingested copy is unaffected. |
| `CALL_RECORDER_RECORDING_RETENTION_HOURS` | No | How long Recall.ai retains the source media after processing. Defaults to `166` hours (6 days 22 hours), just under Recall's 168-hour free-storage window. Values above `168` may incur Recall storage charges. Twenty's ingested copy is unaffected. |
| `RECALL_WEBHOOK_SECRET` | Yes | Svix signing secret (`whsec_…`) used to verify incoming Recall webhooks. |
### Application variables
@@ -136,11 +136,11 @@ A workspace admin can tune bot behavior through application variables:
| Application variable | Default | Purpose |
|---|---|---|
| `MEETING_BOT_NAME` | `Twenty Meeting Bot` | Display name the bot uses when it joins a call. |
| `MEETING_BOT_JOIN_EARLY_MINUTES` | `1` | Minutes before the start time the bot joins. Set to `0` to join at the scheduled start. |
| `MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS` | `1200` | Seconds the bot waits in the lobby before giving up and leaving. |
| `MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS` | `1200` | Seconds the bot stays in an empty meeting when no one else ever joins. |
| `MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS` | `2` | Seconds the bot keeps recording after everyone else leaves. |
| `CALL_RECORDER_NAME` | `Twenty.com` | Display name the bot uses when it joins a call. |
| `CALL_RECORDER_JOIN_EARLY_MINUTES` | `1` | Minutes before the start time the bot joins. Set to `0` to join at the scheduled start. |
| `CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS` | `1200` | Seconds the bot waits in the lobby before giving up and leaving. |
| `CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS` | `1200` | Seconds the bot stays in an empty meeting when no one else ever joins. |
| `CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS` | `2` | Seconds the bot keeps recording after everyone else leaves. |
### Configuring the Recall webhook
@@ -157,7 +157,7 @@ Use this URL on your deployment, replacing only the host:
https://<your-twenty-host>/webhooks/server/8da4b8b5-5edf-4880-b51f-ab6e679ec617/9215afe6-1497-4149-a49d-e608e239bbaf
```
The first ID is the **Twenty Meeting Bot application registration**. The second
The first ID is the **Call Recorder application registration**. The second
ID is the **Recall webhook logic function**.
1. In the Recall.ai dashboard, create a webhook endpoint pointing at your
@@ -168,6 +168,6 @@ ID is the **Recall webhook logic function**.
transcript events trigger media and transcript ingestion. Subscribing to
status changes alone leaves ingestion to the reconciliation backstop.
2. Copy the endpoint's signing secret — it starts with `whsec_`.
3. Set it as the `RECALL_WEBHOOK_SECRET` server variable on the **Twenty
Meeting Bot** application registration.
3. Set it as the `RECALL_WEBHOOK_SECRET` server variable on the
**Call Recorder** application registration.
4. Set `RECALL_API_KEY` (and optionally `RECALL_REGION`) the same way.
@@ -1,5 +1,5 @@
{
"name": "twenty-meeting-bot",
"name": "@twentyhq/call-recorder",
"version": "0.1.4",
"license": "MIT",
"engines": {

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -115,9 +115,8 @@ type GeneratedCoreSchemaRuntime = {
};
const getServerCallRecordingStatuses = async (): Promise<string[]> => {
const generatedCoreSchema = (await import(
'twenty-client-sdk/core'
)) as unknown as GeneratedCoreSchemaRuntime;
const generatedCoreSchema =
(await import('twenty-client-sdk/core')) as unknown as GeneratedCoreSchemaRuntime;
return Object.values(generatedCoreSchema.enumCallRecordingStatusEnum);
};
@@ -0,0 +1,96 @@
import { defineApplication } from 'twenty-sdk/define';
import { APP_DESCRIPTION } from 'src/constants/app-description';
import { APP_DISPLAY_NAME } from 'src/constants/app-display-name';
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/application-universal-identifier';
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier';
import { CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier';
import { CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-name-app-variable-universal-identifier';
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier';
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier';
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds';
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name';
import { CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name';
import { CALL_RECORDER_NAME_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-name-env-var-name';
import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds';
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_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name';
import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds';
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 { DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES } from 'src/logic-functions/constants/default-call-recorder-join-early-minutes';
import { DEFAULT_CALL_RECORDER_NAME } from 'src/logic-functions/constants/default-call-recorder-name';
import { DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-call-recorder-recording-retention-hours';
import { DEFAULT_RECALL_REGION } from 'src/logic-functions/constants/default-recall-region';
import { RECALL_API_KEY_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-api-key-env-var-name';
import { RECALL_REGION_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-region-env-var-name';
import { RECALL_WEBHOOK_SECRET_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-webhook-secret-env-var-name';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: APP_DISPLAY_NAME,
description: APP_DESCRIPTION,
logoUrl: 'public/logo.svg',
screenshots: ['public/gallery/call-recorder-cover.png'],
applicationVariables: {
[CALL_RECORDER_NAME_ENV_VAR_NAME]: {
universalIdentifier: CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
description: 'Display name the call recorder uses when it joins a call.',
isSecret: false,
value: DEFAULT_CALL_RECORDER_NAME,
},
[CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME]: {
universalIdentifier:
CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
description:
'How many minutes before the meeting start time the bot should join. Set to 0 to join at the scheduled start time.',
isSecret: false,
value: String(DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES),
},
[CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
universalIdentifier:
CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
description:
'How many seconds the bot waits in a meeting lobby before giving up and leaving.',
isSecret: false,
value: String(CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS),
},
[CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
universalIdentifier:
CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
description:
'How many seconds the bot stays in an empty meeting when no one else ever joins.',
isSecret: false,
value: String(CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS),
},
[CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
universalIdentifier:
CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
description:
'How many seconds the bot keeps recording after everyone else leaves the meeting.',
isSecret: false,
value: String(CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS),
},
},
serverVariables: {
[RECALL_API_KEY_ENV_VAR_NAME]: {
description:
'Recall.ai API key for the configured region. Set by the server admin on this registration after installation; used to create, update, and cancel scheduled recording bots.',
isSecret: true,
isRequired: true,
},
[RECALL_REGION_ENV_VAR_NAME]: {
description: `Recall.ai region used for API requests. Defaults to ${DEFAULT_RECALL_REGION} when unset. Europe Frankfurt is eu-central-1.`,
isSecret: false,
},
[CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME]: {
description: `How many hours Recall.ai retains recording media after processing. Defaults to ${DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS} hours (6 days and 22 hours) to stay below Recall.ai's 7-day free storage window. Values above 168 hours may incur Recall.ai storage charges.`,
isSecret: false,
},
[RECALL_WEBHOOK_SECRET_ENV_VAR_NAME]: {
description:
'Recall.ai webhook signing secret (whsec_...). Set by the server admin from the Recall webhook endpoint settings; used to verify the Svix signature of incoming Recall webhook deliveries.',
isSecret: true,
isRequired: true,
},
},
});
@@ -0,0 +1,2 @@
export const APP_DESCRIPTION =
'Capture every customer conversation automatically. A call recorder joins eligible meetings and records calls for you.';
@@ -0,0 +1 @@
export const APP_DISPLAY_NAME = 'Call Recorder';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
'c866ddd4-fb7b-4cb4-8ad1-5599755e495c';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER =
'33a577e4-02f5-48bd-8bed-d365949caa72';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
'0568ebb2-3f64-47de-8c0d-d367dfbb7462';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
'c54cbacd-ad10-40b4-9056-7aaf23846d64';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
'241180e8-d864-4160-ad02-db44a9e8d395';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_PREFERENCE_OFF_OPTION_ID =
'cc7de62a-08b6-46c8-aa69-f8117e7dd722';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER =
'8ee9444a-2437-4def-8e61-6e493862a4fd';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER =
'e8c7e9c5-2b4f-4a75-ae46-ea331809106c';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_PREFERENCE_ON_OPTION_ID =
'72431216-49c4-47c8-99af-de4c3831b0be';
@@ -0,0 +1,4 @@
export enum CallRecorderPreference {
ON = 'ON',
OFF = 'OFF',
}
@@ -0,0 +1,2 @@
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER =
'12e4e14d-d539-4d07-b477-4773539dd20b';
@@ -11,7 +11,7 @@ export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: `${APP_DISPLAY_NAME} default role`,
description:
'Reads calendar events to decide whether the meeting bot should attend a meeting; writes the resulting CallRecording records, uploads recording media, and fills transcripts.',
'Reads calendar events to decide whether the call recorder should attend a meeting; writes the resulting CallRecording records, uploads recording media, and fills transcripts.',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
@@ -0,0 +1,22 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier';
export default defineField({
universalIdentifier:
CALL_RECORDER_FAILURE_REASON_ON_CALL_RECORDING_FIELD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.callRecording.universalIdentifier,
type: FieldType.TEXT,
name: 'callRecorderFailureReason',
label: 'Call Recorder Failure Reason',
description:
'Provider-specific reason the call recorder could not produce a recording.',
icon: 'IconAlertTriangle',
isNullable: true,
isUIEditable: false,
});
@@ -0,0 +1,41 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { CallRecorderPreference } from 'src/constants/call-recorder-preference';
import { CALL_RECORDER_PREFERENCE_OFF_OPTION_ID } from 'src/constants/call-recorder-preference-off-option-id';
import { CALL_RECORDER_PREFERENCE_ON_OPTION_ID } from 'src/constants/call-recorder-preference-on-option-id';
import { CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier';
export default defineField({
universalIdentifier:
CALL_RECORDER_PREFERENCE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.universalIdentifier,
type: FieldType.SELECT,
name: 'callRecorderPreference',
label: 'Recording Bot',
description:
'Call recording is on by default when the app is installed. Turn it off for this event when needed.',
icon: 'IconRobot',
isNullable: false,
defaultValue: `'${CallRecorderPreference.ON}'`,
options: [
{
id: CALL_RECORDER_PREFERENCE_ON_OPTION_ID,
value: CallRecorderPreference.ON,
label: 'On',
position: 0,
color: 'green',
},
{
id: CALL_RECORDER_PREFERENCE_OFF_OPTION_ID,
value: CallRecorderPreference.OFF,
label: 'Off',
position: 1,
color: 'red',
},
],
});
@@ -74,9 +74,9 @@ export const TranscriptSpeakerAvatar = ({
avatarUrl,
placeholderColorSeed,
}: TranscriptSpeakerAvatarProps) => {
const [erroredAvatarUrl, setErroredAvatarUrl] = useState<
string | undefined
>(undefined);
const [erroredAvatarUrl, setErroredAvatarUrl] = useState<string | undefined>(
undefined,
);
const shouldShowAvatarImage =
isNonEmptyString(avatarUrl) && erroredAvatarUrl !== avatarUrl;
@@ -98,9 +98,7 @@ export const useCalendarEventParticipants = (
setCalendarEventParticipants(
calendarEventParticipantEdges.map((calendarEventParticipantEdge) =>
mapCalendarEventParticipantNode(
calendarEventParticipantEdge.node,
),
mapCalendarEventParticipantNode(calendarEventParticipantEdge.node),
),
);
} catch {
@@ -93,9 +93,8 @@ export const useCalendarEventRecording = (
const callRecordingNodes = callRecordingEdges.map(
(callRecordingEdge) => callRecordingEdge.node,
);
const callRecordingNode = selectCalendarEventRecording(
callRecordingNodes,
);
const callRecordingNode =
selectCalendarEventRecording(callRecordingNodes);
setState({
transcript: callRecordingNode?.transcript ?? undefined,
@@ -6,8 +6,9 @@ export const getSpeakerNameMatchKeys = (speakerName: string): string[] => {
const normalizedSpeakerName = normalizeSpeakerName(speakerName);
const compactSpeakerName = getCompactSpeakerName(normalizedSpeakerName);
const compactSpeakerNameWithoutDigits = compactSpeakerName.replace(/\d/g, '');
const abbreviatedSpeakerNameMatchKey =
getAbbreviatedSpeakerNameMatchKey(normalizedSpeakerName);
const abbreviatedSpeakerNameMatchKey = getAbbreviatedSpeakerNameMatchKey(
normalizedSpeakerName,
);
return [
...new Set(
@@ -50,8 +51,7 @@ const getAbbreviatedSpeakerNameMatchKey = (
4,
)}${lastSpeakerNamePart.slice(0, 4)}`;
return abbreviatedSpeakerNameMatchKey.length >=
MINIMUM_FUZZY_MATCH_KEY_LENGTH
return abbreviatedSpeakerNameMatchKey.length >= MINIMUM_FUZZY_MATCH_KEY_LENGTH
? abbreviatedSpeakerNameMatchKey
: undefined;
};
@@ -37,9 +37,7 @@ const readTranscriptWord = (
};
};
const readSpeakerName = (
participant: TranscriptRecord | undefined,
): string => {
const readSpeakerName = (participant: TranscriptRecord | undefined): string => {
const name = participant?.name;
return isNonEmptyString(name) ? name.trim() : 'Unknown speaker';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME =
'CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS';
@@ -0,0 +1 @@
export const CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS = 2;
@@ -0,0 +1,2 @@
export const CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME =
'CALL_RECORDER_JOIN_EARLY_MINUTES';
@@ -0,0 +1 @@
export const CALL_RECORDER_NAME_ENV_VAR_NAME = 'CALL_RECORDER_NAME';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME =
'CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS';
@@ -0,0 +1 @@
export const CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS = 20 * 60;
@@ -0,0 +1,2 @@
export const CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME =
'CALL_RECORDER_RECORDING_RETENTION_HOURS';
@@ -0,0 +1,2 @@
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME =
'CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS';
@@ -0,0 +1 @@
export const CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS = 1200;
@@ -0,0 +1 @@
export const DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES = 1;
@@ -0,0 +1 @@
export const DEFAULT_CALL_RECORDER_NAME = 'Twenty.com';
@@ -1,2 +1,2 @@
// Twenty stores ingested recording artifacts, so Recall.ai media is temporary. Keep the default below Recall.ai's 168-hour free storage window.
export const DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS = 166;
export const DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS = 166;
@@ -1,8 +1,8 @@
import { isUndefined } from '@sniptt/guards';
import { MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-everyone-left-timeout-seconds-env-var-name';
import { MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-noone-joined-timeout-seconds-env-var-name';
import { MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-waiting-room-timeout-seconds-env-var-name';
import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name';
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 { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
@@ -20,13 +20,13 @@ export const getRecallBotAutomaticLeave = ():
| RecallBotAutomaticLeave
| undefined => {
const waitingRoomTimeoutSeconds = getOptionalPositiveIntegerVariable(
MEETING_BOT_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME,
CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME,
);
const nooneJoinedTimeoutSeconds = getOptionalPositiveIntegerVariable(
MEETING_BOT_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME,
CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME,
);
const everyoneLeftTimeoutSeconds = getOptionalPositiveIntegerVariable(
MEETING_BOT_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME,
CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME,
);
const automaticLeave: RecallBotAutomaticLeave = {};
@@ -1,5 +1,5 @@
import { DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-meeting-bot-recording-retention-hours';
import { MEETING_BOT_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/meeting-bot-recording-retention-hours-env-var-name';
import { CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name';
import { DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-call-recorder-recording-retention-hours';
import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
@@ -12,7 +12,7 @@ type RecallBotRecordingConfig = {
// Recall only produces artifacts declared at bot creation; both gate COMPLETED.
export const getRecallBotRecordingConfig = (): RecallBotRecordingConfig => {
const configuredRecordingRetentionHours = getApplicationVariableValue(
MEETING_BOT_RECORDING_RETENTION_HOURS_ENV_VAR_NAME,
CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME,
);
const recordingRetentionHours = isNonEmptyString(
@@ -24,7 +24,7 @@ export const getRecallBotRecordingConfig = (): RecallBotRecordingConfig => {
const resolvedRecordingRetentionHours =
Number.isInteger(recordingRetentionHours) && recordingRetentionHours > 0
? recordingRetentionHours
: DEFAULT_MEETING_BOT_RECORDING_RETENTION_HOURS;
: DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS;
return {
video_mixed_mp4: {},

Some files were not shown because too many files have changed in this diff Show More