Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Meeting BaaS API key (get yours at https://meetingbaas.com)
MEETING_BAAS_API_KEY=

# Twenty CRM API key
TWENTY_API_KEY=

# Twenty server URL (default: http://localhost:3000)
SERVER_URL=http://localhost:3000
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.yarn/install-state.gz
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { defineApplication } from 'twenty-sdk';
import { DEFAULT_ROLE_ID } from './src/roles/default.role';

export default defineApplication({
universalIdentifier: 'c522c3c7-cff8-5c08-8c87-d1481adbd4a9',
displayName: 'Meeting BaaS Recorder',
description: 'Record meetings via Meeting BaaS and sync recordings, transcripts, and participants into Twenty.',
icon: 'IconVideo',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_ID,
settingsCustomTabFrontComponentUniversalIdentifier:
'7f2c17b4-2cd2-5447-b7d1-83ef12040837',
applicationVariables: {
MEETING_BAAS_API_KEY: {
universalIdentifier: '32cd6297-bbd3-5beb-a0f6-1f5662590f66',
description: 'Meeting BaaS API key for authenticating requests and verifying webhooks',
isSecret: true,
value: '',
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const jestConfig = {
displayName: 'meeting-baas-recorder',
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'js'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
testMatch: [
'<rootDir>/src/**/__tests__/**/*.(test|spec).{js,ts}',
'<rootDir>/src/**/?(*.)(test|spec).{js,ts}',
],
Comment on lines +9 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Test runner does not match the test files' imports.

The test files added in this package (e.g., src/receive-recording-webhook.test.ts) import from vitest, but this config wires up Jest with ts-jest. Jest will fail to resolve vitest at runtime. Please either:

  • Replace this config with a Vitest config (and switch package.json's test script and deps accordingly), or
  • Convert all test files to use Jest's globals and drop vitest imports.

See the paired comment on src/receive-recording-webhook.test.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs` around
lines 9 - 12, The project is configured with Jest (jest.config.mjs) but the new
test files (e.g., src/receive-recording-webhook.test.ts) import from vitest,
causing runtime resolution failures; fix by switching to Vitest: replace
jest.config.mjs with an equivalent Vitest config (vite or vitest config file),
update package.json "test" script to run vitest, and adjust devDependencies to
include vitest (and remove ts-jest/jest if not needed); alternatively, if you
prefer Jest, update src/receive-recording-webhook.test.ts to remove vitest
imports and use Jest globals (convert assertions/mocks to Jest equivalents) and
ensure package.json and devDependencies remain configured for Jest.

setupFilesAfterEnv: [
'<rootDir>/src/__tests__/setup.ts'
],
collectCoverageFrom: [
'src/**/*.{ts,js}',
'!src/**/*.d.ts',
],
coverageDirectory: './coverage',
};

export default jestConfig;
30 changes: 30 additions & 0 deletions packages/twenty-apps/community/meeting-baas-recorder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "meeting-baas-recorder",
"version": "0.2.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
Comment on lines +5 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Overly strict Node engine range may break installs in the monorepo.

"node": "^24.5.0" restricts to Node 24.5.x and up within the 24.x major. If the Twenty monorepo root package.json pins a different/lower Node (commonly 18.x or 20.x in Nx/Twenty projects), Yarn will emit EBADENGINE warnings, and CI on LTS Node versions will outright fail the engine check. Please align this with the monorepo-wide Node version (and similarly for Yarn if necessary).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/twenty-apps/community/meeting-baas-recorder/package.json` around
lines 5 - 9, The package.json engines entry is too restrictive ("node":
"^24.5.0") and can cause EBADENGINE failures; update the "engines" field in this
package (the engines object in
packages/twenty-apps/community/meeting-baas-recorder/package.json) to match the
monorepo-wide Node version policy (for example use the same semver range as the
root package.json like "node": ">=18" or the exact range used in the repo) and
adjust the "yarn" entry if the monorepo requires a different Yarn major; ensure
you only change the engines values (the engines object) so local installs and CI
use the monorepo-approved Node/Yarn range.

"packageManager": "yarn@4.9.2",
"scripts": {
"test": "jest"
},
"dependencies": {
"@meeting-baas/sdk": "^6.0.5",
"axios": "^1.13.1",
"twenty-sdk": "0.8.0"
},
"devDependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@types/jest": "^29.5.5",
"@types/node": "^24.9.2",
"@types/react": "^18.2.0",
"jest": "^29.7.0",
"react": "^18.2.0",
"ts-jest": "^29.1.1",
"typescript": "^5.9.3"
}
}
46 changes: 46 additions & 0 deletions packages/twenty-apps/community/meeting-baas-recorder/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "meeting-baas-recorder",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/twenty-apps/community/meeting-baas-recorder/src",
"projectType": "application",
"tags": [
"scope:apps"
],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "packages/twenty-apps/community/meeting-baas-recorder/jest.config.mjs",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"coverageReporters": ["text"]
}
}
},
"typecheck": {
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"packages/twenty-apps/community/meeting-baas-recorder/**/*.{ts,tsx,js,jsx}"
]
},
"configurations": {
"fix": {
"fix": true
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { APPLICATION_UNIVERSAL_IDENTIFIER } from '../constants/universal-identifiers';
import {
ensureIntegrationTestEnvironment,
hasIntegrationTestEnvironment,
} from './setup-test';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

const APP_PATH = process.cwd();

describe.skipIf(!hasIntegrationTestEnvironment())('App installation', () => {
beforeAll(async () => {
await ensureIntegrationTestEnvironment();

const buildResult = await appBuild({
appPath: APP_PATH,
tarball: true,
onProgress: (message: string) => console.log(`[build] ${message}`),
});

if (!buildResult.success) {
throw new Error(
`Build failed: ${buildResult.error?.message ?? 'Unknown error'}`,
);
}

const deployResult = await appDeploy({
tarballPath: buildResult.data.tarballPath!,
Comment on lines +28 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Non-null assertion on tarballPath.

buildResult.data.tarballPath! relies on the success discriminant guaranteeing the tarball field. If the SDK return shape changes, this will silently pass undefined to deploy. A narrow guard would be safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts`
around lines 28 - 29, The test uses a non-null assertion on
buildResult.data.tarballPath which can pass undefined into appDeploy; instead
check the build result discriminant and the tarball path explicitly before
calling appDeploy—e.g., assert buildResult.success is true (or throw/fail the
test if not) and verify buildResult.data?.tarballPath is defined, then pass that
verified value to appDeploy (reference: buildResult, buildResult.data,
tarballPath, and appDeploy).

onProgress: (message: string) => console.log(`[deploy] ${message}`),
});

if (!deployResult.success) {
throw new Error(
`Deploy failed: ${deployResult.error?.message ?? 'Unknown error'}`,
);
}

const installResult = await appInstall({ appPath: APP_PATH });

if (!installResult.success) {
throw new Error(
`Install failed: ${installResult.error?.message ?? 'Unknown error'}`,
);
}
});

afterAll(async () => {
const uninstallResult = await appUninstall({ appPath: APP_PATH });

if (!uninstallResult.success) {
console.warn(
`App uninstall failed: ${uninstallResult.error?.message ?? 'Unknown error'}`,
);
}
});
Comment on lines +48 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Teardown may run uninstall when install never succeeded.

If beforeAll throws after build/deploy but before/during install, afterAll still runs appUninstall, which will attempt to uninstall an app that may not exist. Consider tracking install state and conditionally uninstalling, or ensure appUninstall is idempotent on a no-op.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/app-install.integration-test.ts`
around lines 48 - 56, The teardown calls appUninstall unconditionally in
afterAll which can run even if beforeAll failed; make uninstall conditional by
tracking install success (e.g., a boolean like let installed = false set to true
in the successful install path inside beforeAll or the test setup) and only call
appUninstall when installed is true, or update appUninstall to be
idempotent/no-op on non-existent apps; locate afterAll and the install logic
(beforeAll / install function) and add the install flag check before invoking
appUninstall (or make appUninstall tolerate missing installs).


it('should find the installed app in the applications list', async () => {
const metadataClient = new MetadataApiClient();

const result = await metadataClient.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});

const installedApp = result.findManyApplications.find(
(application: { universalIdentifier: string }) =>
application.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);

expect(installedApp).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

const CONFIG_DIR = path.join(os.homedir(), '.twenty');
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json');

export const hasIntegrationTestEnvironment = (): boolean =>
Boolean(process.env.TWENTY_API_URL && process.env.TWENTY_API_KEY);

export const ensureIntegrationTestEnvironment = async () => {
const apiUrl = process.env.TWENTY_API_URL!;
const token = process.env.TWENTY_API_KEY!;

if (!apiUrl || !token) {
throw new Error(
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
'Start a local server: yarn twenty server start\n' +
'Or set them in vitest env config.',
);
}
Comment on lines +12 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove misleading non-null assertions.

apiUrl/token are asserted with ! on lines 12-13, then re-validated on line 15. Either drop the assertions and narrow the types after the check, or drop the check. The current pattern misleads readers and defeats TS safety.

Proposed fix
-  const apiUrl = process.env.TWENTY_API_URL!;
-  const token = process.env.TWENTY_API_KEY!;
-
-  if (!apiUrl || !token) {
+  const apiUrl = process.env.TWENTY_API_URL;
+  const token = process.env.TWENTY_API_KEY;
+
+  if (!apiUrl || !token) {
     throw new Error(
       'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
         'Start a local server: yarn twenty server start\n' +
         'Or set them in vitest env config.',
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts`
around lines 12 - 21, Remove the misleading non-null assertions on apiUrl and
token: don't use process.env.TWENTY_API_URL! or
process.env.TWENTY_API_KEY!—declare them as possibly undefined (const apiUrl =
process.env.TWENTY_API_URL; const token = process.env.TWENTY_API_KEY;) keep the
existing runtime check (if (!apiUrl || !token) throw ...), and then rely on the
narrowed types for apiUrl and token afterwards (or assign them to new consts
like apiUrlVal/tokenVal after the check) so TypeScript knows they are strings
without using !; update any references to use the narrowed variables (apiUrl,
token or the new consts).


let response: Response;

try {
response = await fetch(`${apiUrl}/healthz`);
} catch {
throw new Error(
`Twenty server is not reachable at ${apiUrl}. ` +
'Make sure the server is running before executing integration tests.',
);
}

if (!response.ok) {
throw new Error(`Server at ${apiUrl} returned ${response.status}`);
}

fs.mkdirSync(CONFIG_DIR, { recursive: true });

fs.writeFileSync(
CONFIG_PATH,
JSON.stringify(
{
remotes: {
local: { apiUrl, apiKey: token },
},
defaultRemote: 'local',
},
null,
2,
),
);
Comment on lines +38 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Restrict permissions on the written config file.

config.test.json contains an API key. fs.writeFileSync uses the process umask default (often world-readable). On shared/CI hosts this is a credential leak. Write with mode: 0o600 and consider fs.chmodSync on the directory as well.

Proposed fix
-  fs.mkdirSync(CONFIG_DIR, { recursive: true });
-
-  fs.writeFileSync(
+  fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
+
+  fs.writeFileSync(
     CONFIG_PATH,
     JSON.stringify(
       {
         remotes: {
           local: { apiUrl, apiKey: token },
         },
         defaultRemote: 'local',
       },
       null,
       2,
     ),
+    { mode: 0o600 },
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
CONFIG_PATH,
JSON.stringify(
{
remotes: {
local: { apiUrl, apiKey: token },
},
defaultRemote: 'local',
},
null,
2,
),
);
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
fs.writeFileSync(
CONFIG_PATH,
JSON.stringify(
{
remotes: {
local: { apiUrl, apiKey: token },
},
defaultRemote: 'local',
},
null,
2,
),
{ mode: 0o600 },
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/__tests__/setup-test.ts`
around lines 38 - 52, The written config file CONFIG_PATH is created with
default permissions exposing the API key; change the fs.writeFileSync call to
pass a mode of 0o600 so the file is user-readable/writable only, and after
creating CONFIG_DIR with fs.mkdirSync consider calling fs.chmodSync(CONFIG_DIR,
0o700) to tighten directory permissions; update the setup-test.ts code that uses
fs.mkdirSync and fs.writeFileSync (look for CONFIG_DIR and CONFIG_PATH) to
include these permission changes.


process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineApplication } from 'twenty-sdk';

import {
APP_DESCRIPTION,
APP_DISPLAY_NAME,
APPLICATION_UNIVERSAL_IDENTIFIER,
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
} from './constants/universal-identifiers';

export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: APP_DISPLAY_NAME,
description: APP_DESCRIPTION,
icon: 'IconVideo',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
settingsCustomTabFrontComponentUniversalIdentifier: '4ea804f4-6c22-457b-b8a2-66673bb6fc76',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the exported constant instead of a hardcoded UUID.

The summary notes a SETTINGS_FRONT_COMPONENT_ID constant exists for the settings front component. Referencing a raw UUID here breaks the single-source-of-truth for identifiers and risks drift if the constant changes.

♻️ Proposed change
 import {
   APP_DESCRIPTION,
   APP_DISPLAY_NAME,
   APPLICATION_UNIVERSAL_IDENTIFIER,
   DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
+  SETTINGS_FRONT_COMPONENT_ID,
 } from './constants/universal-identifiers';
@@
-  settingsCustomTabFrontComponentUniversalIdentifier: '4ea804f4-6c22-457b-b8a2-66673bb6fc76',
+  settingsCustomTabFrontComponentUniversalIdentifier: SETTINGS_FRONT_COMPONENT_ID,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/application-config.ts`
at line 16, Replace the hardcoded UUID assigned to
settingsCustomTabFrontComponentUniversalIdentifier with the exported
SETTINGS_FRONT_COMPONENT_ID constant: import the SETTINGS_FRONT_COMPONENT_ID
from its module where it is defined and set
settingsCustomTabFrontComponentUniversalIdentifier = SETTINGS_FRONT_COMPONENT_ID
(instead of the raw UUID) so the identifier is sourced from the single exported
constant.

applicationVariables: {
MEETING_BAAS_API_KEY: {
universalIdentifier: 'c1d2e3f4-5a6b-7c8d-9e0f-a1b2c3d4e5f6',
description: 'Meeting BaaS API key for authenticating requests and verifying webhooks',
isSecret: true,
value: '',
},
AUTO_CREATE_CONTACTS: {
universalIdentifier: '9637bafd-5888-4f34-bf8f-a4c82dbc4942',
description: 'Whether to auto-create contacts for unknown participants (true/false)',
value: 'true',
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const APP_DISPLAY_NAME = 'Meeting BaaS Recorder';
export const APP_DESCRIPTION = 'Record meetings via Meeting BaaS and sync recordings, transcripts, and participants into Twenty.';
export const APPLICATION_UNIVERSAL_IDENTIFIER = 'b7e3a1f2-8d4c-4e6a-9f2b-1c5d7e8a3b4f';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = '3231cf40-5b90-4c2b-ae41-fcb5606299b4';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';

export const BOT_ENTRY_MESSAGE_FIELD_ID = '5d9be02b-138b-5437-9184-d72276f51f3d';

export default defineField({
universalIdentifier: BOT_ENTRY_MESSAGE_FIELD_ID,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.TEXT,
name: 'botEntryMessage',
label: 'Bot Entry Message',
icon: 'IconMessage',
description: 'Message the bot posts in the meeting chat when it joins (max 500 characters)',
defaultValue: "''",
Comment on lines +17 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if twenty-sdk FieldType.TEXT supports a maxLength/validation setting
rg -nP "FieldType\.TEXT|maxLength|universalSettings" --type=ts -g '!node_modules'

Repository: Meeting-BaaS/twenty

Length of output: 50375


🏁 Script executed:

# First, read the actual field definition file to see current state
cat -n packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts

Repository: Meeting-BaaS/twenty

Length of output: 791


🏁 Script executed:

# Find schedule-bot.ts to check for validation logic
fd -i schedule-bot.ts --type f

Repository: Meeting-BaaS/twenty

Length of output: 151


🏁 Script executed:

# Search for examples of TEXT field definitions with validation/maxLength settings
rg -A5 "FieldType\.TEXT" packages/twenty-apps --type=ts | head -50

Repository: Meeting-BaaS/twenty

Length of output: 3946


🏁 Script executed:

# Check schedule-bot.ts to see if there's validation for the message length
cat packages/twenty-apps/community/meeting-baas-recorder/src/logic-functions/schedule-bot.ts

Repository: Meeting-BaaS/twenty

Length of output: 6253


🏁 Script executed:

# Look for TEXT field definitions with universalSettings to see if maxLength is used
rg -B2 -A10 'type: FieldType\.TEXT' packages/twenty-apps --type=ts -g '*.object.ts|*.field.ts' | grep -A10 "universalSettings"

Repository: Meeting-BaaS/twenty

Length of output: 45


🏁 Script executed:

# Check the FieldType definitions and TEXT field documentation
rg -B5 -A15 "export.*FieldType\.TEXT|TEXT.*FieldType" packages/twenty-sdk/src --type=ts

Repository: Meeting-BaaS/twenty

Length of output: 45


Add validation to enforce the 500-character limit or remove the documentation claim.

The field description promises "max 500 characters" but the field definition lacks any length constraint in universalSettings, and schedule-bot.ts passes botEntryMessage directly to the Meeting BaaS API without validation. Oversized values will reach the API unvalidated and surface as opaque errors.

Either add a maxLength constraint to the field or add pre-flight validation in schedule-bot.ts (line ~131, before line 158-163 where botEntryMessage is passed to createScheduledBot).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/twenty-apps/community/meeting-baas-recorder/src/fields/bot-entry-message-on-workspace-member.field.ts`
around lines 17 - 18, The field claims a "max 500 characters" but no enforcement
is present; add validation to prevent oversized messages before calling the
Meeting BaaS API. Update the universalSettings entry in
bot-entry-message-on-workspace-member.field.ts to include a maxLength: 500 (or
equivalent validation rule) for the field, and/or add a pre-flight check in
schedule-bot.ts that inspects botEntryMessage before invoking
createScheduledBot: if botEntryMessage.length > 500 throw/return a clear error
or truncate per policy. Ensure the check references the same field name
(botEntryMessage) and runs prior to the createScheduledBot call so the API never
receives unvalidated input.

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';

export const BOT_NAME_FIELD_ID = '6a37564a-25e6-5bdb-a119-1522e3817ae6';

export default defineField({
universalIdentifier: BOT_NAME_FIELD_ID,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.TEXT,
name: 'botName',
label: 'Bot Name',
icon: 'IconRobot',
description: 'Name displayed for the recording bot when it joins meetings',
defaultValue: "'Twenty CRM Recorder'",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
import { RECORDING_UNIVERSAL_IDENTIFIER } from '../objects/recording';

export const CALENDAR_EVENT_ON_RECORDING_ID = '29fe48d1-7e7d-4253-9fea-0a876c2c116d';
export const RECORDINGS_ON_CALENDAR_EVENT_ID = '131a78b1-f3c9-4b2e-9808-f9eb64bfb832';

export default defineField({
universalIdentifier: CALENDAR_EVENT_ON_RECORDING_ID,
objectUniversalIdentifier: RECORDING_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'calendarEvent',
label: 'Calendar Event',
icon: 'IconCalendarEvent',
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: RECORDINGS_ON_CALENDAR_EVENT_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'calendarEventId',
},
});
Loading
Loading