@twentyhq/call-recorder 1.0.0

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.
Files changed (229) hide show
  1. package/.env.example +5 -0
  2. package/.nvmrc +1 -0
  3. package/.oxlintrc.json +20 -0
  4. package/.yarnrc.yml +1 -0
  5. package/AGENTS.md +67 -0
  6. package/CLAUDE.md +67 -0
  7. package/README.md +24 -0
  8. package/SETUP.md +95 -0
  9. package/package.json +42 -0
  10. package/public/gallery/call-recorder-cover.png +0 -0
  11. package/public/logo.svg +5 -0
  12. package/src/__tests__/global-setup.ts +100 -0
  13. package/src/__tests__/schema.integration-test.ts +104 -0
  14. package/src/application-config.ts +96 -0
  15. package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +19 -0
  16. package/src/constants/app-description.ts +2 -0
  17. package/src/constants/app-display-name.ts +1 -0
  18. package/src/constants/application-universal-identifier.ts +2 -0
  19. package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +2 -0
  20. package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +2 -0
  21. package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +2 -0
  22. package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +2 -0
  23. package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +2 -0
  24. package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +2 -0
  25. package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +2 -0
  26. package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +2 -0
  27. package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +2 -0
  28. package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +2 -0
  29. package/src/constants/call-recorder-preference-off-option-id.ts +2 -0
  30. package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +2 -0
  31. package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +2 -0
  32. package/src/constants/call-recorder-preference-on-option-id.ts +2 -0
  33. package/src/constants/call-recorder-preference.ts +4 -0
  34. package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +2 -0
  35. package/src/constants/call-recording-audio-field-universal-identifier.ts +2 -0
  36. package/src/constants/call-recording-video-field-universal-identifier.ts +2 -0
  37. package/src/constants/default-role-universal-identifier.ts +2 -0
  38. package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +2 -0
  39. package/src/constants/recall-webhook-logic-function-universal-identifier.ts +2 -0
  40. package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +2 -0
  41. package/src/default-role.ts +69 -0
  42. package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +22 -0
  43. package/src/fields/call-recorder-preference-on-calendar-event.field.ts +41 -0
  44. package/src/front-components/calendar-event-recording.front-component.tsx +13 -0
  45. package/src/front-components/components/CalendarEventRecording.tsx +39 -0
  46. package/src/front-components/components/CalendarEventRecordingBody.tsx +96 -0
  47. package/src/front-components/components/CalendarEventRecordingContent.tsx +111 -0
  48. package/src/front-components/components/RecordingTranscript.tsx +92 -0
  49. package/src/front-components/components/RecordingVideoPlayer.tsx +52 -0
  50. package/src/front-components/components/TranscriptEntryList.tsx +61 -0
  51. package/src/front-components/components/TranscriptEntryListItem.tsx +115 -0
  52. package/src/front-components/components/TranscriptErrorBox.tsx +48 -0
  53. package/src/front-components/components/TranscriptSpeakerAvatar.tsx +141 -0
  54. package/src/front-components/components/TranscriptSpeakerChip.tsx +51 -0
  55. package/src/front-components/constants/recording-theme-css-variables.ts +40 -0
  56. package/src/front-components/hooks/use-calendar-event-participants.ts +172 -0
  57. package/src/front-components/hooks/use-calendar-event-recording.ts +155 -0
  58. package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +6 -0
  59. package/src/front-components/types/calendar-event-recording-participant.type.ts +7 -0
  60. package/src/front-components/types/transcript-entry.type.ts +13 -0
  61. package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +66 -0
  62. package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +29 -0
  63. package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +22 -0
  64. package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +162 -0
  65. package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +45 -0
  66. package/src/front-components/utils/find-active-transcript-entry-index.util.ts +77 -0
  67. package/src/front-components/utils/format-transcript-timestamp.util.ts +16 -0
  68. package/src/front-components/utils/get-absolute-avatar-url.util.ts +48 -0
  69. package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +24 -0
  70. package/src/front-components/utils/get-speaker-name-match-keys.util.ts +64 -0
  71. package/src/front-components/utils/get-video-file-extension.util.ts +23 -0
  72. package/src/front-components/utils/parse-transcript-entries.util.ts +85 -0
  73. package/src/logic-functions/__tests__/process-recall-webhook.test.ts +62 -0
  74. package/src/logic-functions/__tests__/recall-webhook.test.ts +180 -0
  75. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +2 -0
  76. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +1 -0
  77. package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +2 -0
  78. package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +1 -0
  79. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +2 -0
  80. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +1 -0
  81. package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +2 -0
  82. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +2 -0
  83. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +1 -0
  84. package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +1 -0
  85. package/src/logic-functions/constants/call-recording-request-status.ts +5 -0
  86. package/src/logic-functions/constants/call-recording-status.ts +9 -0
  87. package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +1 -0
  88. package/src/logic-functions/constants/default-call-recorder-name.ts +1 -0
  89. package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +2 -0
  90. package/src/logic-functions/constants/default-recall-region.ts +1 -0
  91. package/src/logic-functions/constants/milliseconds-per-minute.ts +1 -0
  92. package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +8 -0
  93. package/src/logic-functions/constants/recall-api-key-env-var-name.ts +1 -0
  94. package/src/logic-functions/constants/recall-api-max-attempts.ts +1 -0
  95. package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +1 -0
  96. package/src/logic-functions/constants/recall-bot-automatic-leave.ts +74 -0
  97. package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +1 -0
  98. package/src/logic-functions/constants/recall-bot-recording-config.ts +34 -0
  99. package/src/logic-functions/constants/recall-region-env-var-name.ts +1 -0
  100. package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +1 -0
  101. package/src/logic-functions/constants/restricted-field-placeholder.ts +3 -0
  102. package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +1 -0
  103. package/src/logic-functions/constants/twenty-page-size.ts +1 -0
  104. package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +55 -0
  105. package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +43 -0
  106. package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +38 -0
  107. package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +22 -0
  108. package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +24 -0
  109. package/src/logic-functions/data/create-call-recording.util.ts +41 -0
  110. package/src/logic-functions/data/fetch-all-nodes.util.ts +44 -0
  111. package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +80 -0
  112. package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +20 -0
  113. package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +19 -0
  114. package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +17 -0
  115. package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +102 -0
  116. package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +17 -0
  117. package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +14 -0
  118. package/src/logic-functions/data/get-current-workspace-id.util.ts +36 -0
  119. package/src/logic-functions/data/strip-restricted-field-value.util.ts +6 -0
  120. package/src/logic-functions/data/update-call-recording.util.ts +24 -0
  121. package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +47 -0
  122. package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +71 -0
  123. package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +37 -0
  124. package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +88 -0
  125. package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +59 -0
  126. package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +37 -0
  127. package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +120 -0
  128. package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +102 -0
  129. package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +42 -0
  130. package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +53 -0
  131. package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +13 -0
  132. package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +13 -0
  133. package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +12 -0
  134. package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +7 -0
  135. package/src/logic-functions/domain/compute-call-recording-charge.util.ts +41 -0
  136. package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +16 -0
  137. package/src/logic-functions/domain/compute-real-meeting-key.util.ts +48 -0
  138. package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +34 -0
  139. package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +19 -0
  140. package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +37 -0
  141. package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +13 -0
  142. package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +26 -0
  143. package/src/logic-functions/domain/parse-transcript-marker.util.ts +29 -0
  144. package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +72 -0
  145. package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +32 -0
  146. package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +45 -0
  147. package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +61 -0
  148. package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +727 -0
  149. package/src/logic-functions/flows/__tests__/download-transcript.test.ts +74 -0
  150. package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +1301 -0
  151. package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +225 -0
  152. package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +153 -0
  153. package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +425 -0
  154. package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +1007 -0
  155. package/src/logic-functions/flows/cancel-call-recording-request.util.ts +46 -0
  156. package/src/logic-functions/flows/charge-completed-call-recording.util.ts +31 -0
  157. package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +29 -0
  158. package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +8 -0
  159. package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +447 -0
  160. package/src/logic-functions/flows/download-transcript.util.ts +67 -0
  161. package/src/logic-functions/flows/ensure-call-recorder.util.ts +73 -0
  162. package/src/logic-functions/flows/handle-recall-webhook.util.ts +672 -0
  163. package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +82 -0
  164. package/src/logic-functions/flows/ingest-call-recording-media.util.ts +128 -0
  165. package/src/logic-functions/flows/persist-call-recording-progress.util.ts +58 -0
  166. package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +183 -0
  167. package/src/logic-functions/flows/reconcile-call-recorder.util.ts +495 -0
  168. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +11 -0
  169. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +182 -0
  170. package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +69 -0
  171. package/src/logic-functions/process-recall-webhook.ts +23 -0
  172. package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +153 -0
  173. package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +67 -0
  174. package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +744 -0
  175. package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +122 -0
  176. package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +28 -0
  177. package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +47 -0
  178. package/src/logic-functions/recall-api/eject-recall-bot.util.ts +28 -0
  179. package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +149 -0
  180. package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +10 -0
  181. package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +30 -0
  182. package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +8 -0
  183. package/src/logic-functions/recall-api/get-recall-api-config.util.ts +59 -0
  184. package/src/logic-functions/recall-api/get-recall-bot.util.ts +42 -0
  185. package/src/logic-functions/recall-api/get-recall-recording.util.ts +31 -0
  186. package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +18 -0
  187. package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +141 -0
  188. package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +106 -0
  189. package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +14 -0
  190. package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +88 -0
  191. package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +165 -0
  192. package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +5 -0
  193. package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +56 -0
  194. package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +71 -0
  195. package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +68 -0
  196. package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +109 -0
  197. package/src/logic-functions/recall-webhook.ts +90 -0
  198. package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +178 -0
  199. package/src/logic-functions/reconcile-stale-bot-state.ts +106 -0
  200. package/src/logic-functions/types/calendar-event-record.type.ts +5 -0
  201. package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +10 -0
  202. package/src/logic-functions/types/call-recorder-policy-input.type.ts +9 -0
  203. package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +5 -0
  204. package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +1 -0
  205. package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +9 -0
  206. package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +6 -0
  207. package/src/logic-functions/types/call-recorder-policy-result.type.ts +12 -0
  208. package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +16 -0
  209. package/src/logic-functions/types/call-recording-media-file.type.ts +1 -0
  210. package/src/logic-functions/types/call-recording-record.type.ts +15 -0
  211. package/src/logic-functions/types/call-recording-update-fields.type.ts +20 -0
  212. package/src/logic-functions/types/files-field-value.type.ts +1 -0
  213. package/src/logic-functions/types/meeting-recording.type.ts +7 -0
  214. package/src/logic-functions/types/recall-bot-operation-result.type.ts +19 -0
  215. package/src/logic-functions/types/recall-routing-metadata.type.ts +4 -0
  216. package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +6 -0
  217. package/src/logic-functions/types/transcript-marker.type.ts +6 -0
  218. package/src/logic-functions/utils/as-record.util.ts +6 -0
  219. package/src/logic-functions/utils/get-application-variable-value.util.ts +3 -0
  220. package/src/logic-functions/utils/get-record-at-path.util.ts +10 -0
  221. package/src/logic-functions/utils/get-string.util.ts +4 -0
  222. package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +8 -0
  223. package/src/logic-functions/utils/is-non-empty-string.util.ts +5 -0
  224. package/src/page-layouts/calendar-event-recording-tab.ts +33 -0
  225. package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +27 -0
  226. package/tsconfig.json +42 -0
  227. package/tsconfig.spec.json +9 -0
  228. package/vitest.config.ts +31 -0
  229. package/vitest.unit.config.ts +14 -0
package/.env.example ADDED
@@ -0,0 +1,5 @@
1
+ # Credentials for integration tests. Copy this file to .env.local and fill in the key.
2
+ # Get an API key from the Twenty UI: Settings -> APIs & Webhooks.
3
+ # .env.local is gitignored; never commit a real key.
4
+ TWENTY_API_URL=http://localhost:2020
5
+ TWENTY_API_KEY=
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24.5.0
package/.oxlintrc.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "extends": ["../../.oxlintrc.base.json"],
4
+ "plugins": ["typescript"],
5
+ "categories": {
6
+ "correctness": "off"
7
+ },
8
+ "ignorePatterns": ["node_modules", "dist"],
9
+ "rules": {
10
+ "no-unused-vars": "off",
11
+
12
+ "typescript/no-unused-vars": [
13
+ "warn",
14
+ {
15
+ "argsIgnorePattern": "^_"
16
+ }
17
+ ],
18
+ "typescript/no-explicit-any": "off"
19
+ }
20
+ }
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
package/AGENTS.md ADDED
@@ -0,0 +1,67 @@
1
+ ## Base documentation
2
+
3
+ - Getting started:
4
+ - https://docs.twenty.com/developers/extend/apps/getting-started/quick-start.md
5
+ - https://docs.twenty.com/developers/extend/apps/getting-started/concepts.md
6
+ - https://docs.twenty.com/developers/extend/apps/getting-started/project-structure.md
7
+ - https://docs.twenty.com/developers/extend/apps/getting-started/local-server.md
8
+ - https://docs.twenty.com/developers/extend/apps/getting-started/scaffolding.md
9
+ - https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting.md
10
+ - Config:
11
+ - https://docs.twenty.com/developers/extend/apps/config/overview.md
12
+ - https://docs.twenty.com/developers/extend/apps/config/application.md
13
+ - https://docs.twenty.com/developers/extend/apps/config/roles.md
14
+ - https://docs.twenty.com/developers/extend/apps/config/install-hooks.md
15
+ - https://docs.twenty.com/developers/extend/apps/config/public-assets.md
16
+ - Data:
17
+ - https://docs.twenty.com/developers/extend/apps/data/overview.md
18
+ - https://docs.twenty.com/developers/extend/apps/data/objects.md
19
+ - https://docs.twenty.com/developers/extend/apps/data/extending-objects.md
20
+ - https://docs.twenty.com/developers/extend/apps/data/relations.md
21
+ - Logic:
22
+ - https://docs.twenty.com/developers/extend/apps/logic/overview.md
23
+ - https://docs.twenty.com/developers/extend/apps/logic/logic-functions.md
24
+ - https://docs.twenty.com/developers/extend/apps/logic/skills-and-agents.md
25
+ - https://docs.twenty.com/developers/extend/apps/logic/connections.md
26
+ - Layout:
27
+ - https://docs.twenty.com/developers/extend/apps/layout/overview.md
28
+ - https://docs.twenty.com/developers/extend/apps/layout/views.md
29
+ - https://docs.twenty.com/developers/extend/apps/layout/navigation-menu-items.md
30
+ - https://docs.twenty.com/developers/extend/apps/layout/page-layouts.md
31
+ - https://docs.twenty.com/developers/extend/apps/layout/front-components.md
32
+ - https://docs.twenty.com/developers/extend/apps/layout/command-menu-items.md
33
+ - Operations:
34
+ - https://docs.twenty.com/developers/extend/apps/operations/overview.md
35
+ - https://docs.twenty.com/developers/extend/apps/operations/cli.md
36
+ - https://docs.twenty.com/developers/extend/apps/operations/testing.md
37
+ - https://docs.twenty.com/developers/extend/apps/operations/publishing.md
38
+ - Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
39
+
40
+ ## UUID requirement
41
+
42
+ - All generated UUIDs must be valid UUID v4.
43
+
44
+ ## Common Pitfalls
45
+
46
+ - Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
47
+ - Creating a view without a navigationMenuItem associated. This will make the view unavailable on the left sidebar.
48
+ - Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
49
+
50
+ ## Best practice
51
+
52
+ It's highly recommended to create new app entities using `yarn twenty dev:add`. These are the options:
53
+
54
+ | Entity type | Command | Generated file |
55
+ | -------------------- | ---------------------------------------- | ------------------------------------- |
56
+ | Object | `yarn twenty dev:add object` | `src/objects/<name>.ts` |
57
+ | Field | `yarn twenty dev:add field` | `src/fields/<name>.ts` |
58
+ | Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/<name>.ts` |
59
+ | Front component | `yarn twenty dev:add frontComponent` | `src/front-components/<name>.tsx` |
60
+ | Role | `yarn twenty dev:add role` | `src/roles/<name>.ts` |
61
+ | Skill | `yarn twenty dev:add skill` | `src/skills/<name>.ts` |
62
+ | Agent | `yarn twenty dev:add agent` | `src/agents/<name>.ts` |
63
+ | View | `yarn twenty dev:add view` | `src/views/<name>.ts` |
64
+ | Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/<name>.ts` |
65
+ | Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/<name>.ts` |
66
+
67
+ This helps automatically generate required IDs etc.
package/CLAUDE.md ADDED
@@ -0,0 +1,67 @@
1
+ ## Base documentation
2
+
3
+ - Getting started:
4
+ - https://docs.twenty.com/developers/extend/apps/getting-started/quick-start.md
5
+ - https://docs.twenty.com/developers/extend/apps/getting-started/concepts.md
6
+ - https://docs.twenty.com/developers/extend/apps/getting-started/project-structure.md
7
+ - https://docs.twenty.com/developers/extend/apps/getting-started/local-server.md
8
+ - https://docs.twenty.com/developers/extend/apps/getting-started/scaffolding.md
9
+ - https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting.md
10
+ - Config:
11
+ - https://docs.twenty.com/developers/extend/apps/config/overview.md
12
+ - https://docs.twenty.com/developers/extend/apps/config/application.md
13
+ - https://docs.twenty.com/developers/extend/apps/config/roles.md
14
+ - https://docs.twenty.com/developers/extend/apps/config/install-hooks.md
15
+ - https://docs.twenty.com/developers/extend/apps/config/public-assets.md
16
+ - Data:
17
+ - https://docs.twenty.com/developers/extend/apps/data/overview.md
18
+ - https://docs.twenty.com/developers/extend/apps/data/objects.md
19
+ - https://docs.twenty.com/developers/extend/apps/data/extending-objects.md
20
+ - https://docs.twenty.com/developers/extend/apps/data/relations.md
21
+ - Logic:
22
+ - https://docs.twenty.com/developers/extend/apps/logic/overview.md
23
+ - https://docs.twenty.com/developers/extend/apps/logic/logic-functions.md
24
+ - https://docs.twenty.com/developers/extend/apps/logic/skills-and-agents.md
25
+ - https://docs.twenty.com/developers/extend/apps/logic/connections.md
26
+ - Layout:
27
+ - https://docs.twenty.com/developers/extend/apps/layout/overview.md
28
+ - https://docs.twenty.com/developers/extend/apps/layout/views.md
29
+ - https://docs.twenty.com/developers/extend/apps/layout/navigation-menu-items.md
30
+ - https://docs.twenty.com/developers/extend/apps/layout/page-layouts.md
31
+ - https://docs.twenty.com/developers/extend/apps/layout/front-components.md
32
+ - https://docs.twenty.com/developers/extend/apps/layout/command-menu-items.md
33
+ - Operations:
34
+ - https://docs.twenty.com/developers/extend/apps/operations/overview.md
35
+ - https://docs.twenty.com/developers/extend/apps/operations/cli.md
36
+ - https://docs.twenty.com/developers/extend/apps/operations/testing.md
37
+ - https://docs.twenty.com/developers/extend/apps/operations/publishing.md
38
+ - Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard
39
+
40
+ ## UUID requirement
41
+
42
+ - All generated UUIDs must be valid UUID v4.
43
+
44
+ ## Common Pitfalls
45
+
46
+ - Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
47
+ - Creating a view without a navigationMenuItem associated. This will make the view unavailable on the left sidebar.
48
+ - Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
49
+
50
+ ## Best practice
51
+
52
+ It's highly recommended to create new app entities using `yarn twenty dev:add`. These are the options:
53
+
54
+ | Entity type | Command | Generated file |
55
+ | -------------------- | ---------------------------------------- | ------------------------------------- |
56
+ | Object | `yarn twenty dev:add object` | `src/objects/<name>.ts` |
57
+ | Field | `yarn twenty dev:add field` | `src/fields/<name>.ts` |
58
+ | Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/<name>.ts` |
59
+ | Front component | `yarn twenty dev:add frontComponent` | `src/front-components/<name>.tsx` |
60
+ | Role | `yarn twenty dev:add role` | `src/roles/<name>.ts` |
61
+ | Skill | `yarn twenty dev:add skill` | `src/skills/<name>.ts` |
62
+ | Agent | `yarn twenty dev:add agent` | `src/agents/<name>.ts` |
63
+ | View | `yarn twenty dev:add view` | `src/views/<name>.ts` |
64
+ | Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/<name>.ts` |
65
+ | Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/<name>.ts` |
66
+
67
+ This helps automatically generate required IDs etc.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Call Recorder
2
+
3
+ **Record, transcribe, and save every meeting — right inside your CRM.**
4
+
5
+ ## ✨ What you get
6
+
7
+ - **Recordings on every meeting**
8
+ - **A Call Recording tab**
9
+ - **A per-meeting on/off switch**
10
+ - **Built for AI & automation**
11
+
12
+ ## 💳 Billing
13
+
14
+ Metered in Twenty credits based on the bot's actual recording time, prorated by
15
+ duration — **$1.00 per recording-hour** (1 credit). No recording — opted out,
16
+ canceled, or no-show — means no charge.
17
+
18
+ ## 📌 Heads up
19
+
20
+ - **Needs a synced calendar + video link** — ad-hoc calls that were never on
21
+ your Google, Outlook, or CalDAV calendar aren't recorded.
22
+ - **Your copy is yours** — Twenty stores its own video, audio, and transcript,
23
+ so they stay available after the source media expires.
24
+
package/SETUP.md ADDED
@@ -0,0 +1,95 @@
1
+ # Call Recorder — Self-hosting setup
2
+
3
+ This guide is for Twenty **server admins**. It covers connecting Call Recorder
4
+ to the recording service (Recall.ai), the environment variables it reads, and
5
+ how to wire up the webhook.
6
+
7
+ If you're on **Twenty Cloud**, these credentials may already be configured —
8
+ check with your workspace before setting them.
9
+
10
+ ## What you need to wire up
11
+
12
+ Call Recorder talks to [Recall.ai](https://recall.ai) to send bots to meetings
13
+ and to receive recordings back. Two things must be configured:
14
+
15
+ 1. A **Recall.ai API key** (and optionally a region), so the app can schedule,
16
+ update, and cancel bots.
17
+ 2. A **webhook** from Recall.ai back to your deployment, so the app learns when
18
+ a recording is ready and can ingest it.
19
+
20
+ ## Server variables
21
+
22
+ Set these on the application registration after installing
23
+ (**Settings → Applications → Call Recorder**):
24
+
25
+ | Server variable | Required | Purpose |
26
+ |---|---|---|
27
+ | `RECALL_API_KEY` | Yes | Recall.ai API key for the configured region; used to schedule, update, and cancel bots. |
28
+ | `RECALL_REGION` | No | Recall.ai region for API requests. Defaults to `eu-central-1` (Europe / Frankfurt). |
29
+ | `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. |
30
+ | `RECALL_WEBHOOK_SECRET` | Yes | Svix signing secret (`whsec_…`) used to verify incoming Recall webhooks. |
31
+
32
+ > **Bot behavior settings** (display name, join timing, lobby and leave
33
+ > timeouts) are **application variables** that a workspace admin tunes inside the
34
+ > app — not server variables. See **Customize the bot** in the
35
+ > [README](./README.md).
36
+
37
+ ## Configuring the Recall webhook
38
+
39
+ The app exposes a server webhook route that verifies the Recall/Svix signature,
40
+ advances the matching recording's lifecycle status (`JOINING` → `RECORDING` →
41
+ `PROCESSING`, or `FAILED`), and — once the recording finishes — ingests the
42
+ audio, video, and transcript. It never moves a status backward, so out-of-order
43
+ or duplicate deliveries are safe, and it returns a non-2xx response on signature
44
+ failures so Recall retries.
45
+
46
+ Use this URL on your deployment, replacing only the host:
47
+
48
+ ```text
49
+ https://<your-twenty-host>/webhooks/server/9215afe6-1497-4149-a49d-e608e239bbaf
50
+ ```
51
+
52
+ The ID is the **Recall webhook logic function**.
53
+
54
+ 1. In the Recall.ai dashboard, create a webhook endpoint pointing at your
55
+ deployment's webhook URL, subscribed to the **bot status-change**,
56
+ **recording**, and **transcript** events (`bot.status_change`,
57
+ `recording.done`, `recording.failed`, `transcript.done`,
58
+ `transcript.failed`). Status-change drives the lifecycle; the recording and
59
+ transcript events trigger media and transcript ingestion. Subscribing to
60
+ status changes alone leaves ingestion to the reconciliation backstop.
61
+ 2. Copy the endpoint's signing secret — it starts with `whsec_`.
62
+ 3. Set it as the `RECALL_WEBHOOK_SECRET` server variable on the **Call Recorder**
63
+ application registration.
64
+ 4. Set `RECALL_API_KEY` (and optionally `RECALL_REGION`) the same way.
65
+
66
+ ## Recording lifecycle
67
+
68
+ Each recording is stored as a **CallRecording** record and moves through a
69
+ lifecycle status: `SCHEDULED` → `JOINING` → `RECORDING` → `PROCESSING` →
70
+ `COMPLETED`, or `FAILED` (with a failure reason when one is available).
71
+
72
+ A recording reaches `COMPLETED` only once **both** its audio and video have been
73
+ ingested. Recall produces only the artifacts requested at bot creation (mixed
74
+ MP3 + MP4); if processing fails, the recording is marked `FAILED`.
75
+
76
+ Recall.ai retains the source media for a limited window (about seven days by
77
+ default) to stay inside its free-storage window. Twenty ingests and stores the
78
+ video, audio, and transcript in its own storage, so they remain available after
79
+ Recall's media expires.
80
+
81
+ A periodic reconciliation job runs as a safety net, pulling the latest status
82
+ from Recall and keeping recordings and bots in sync even when a real-time
83
+ webhook update is missed.
84
+
85
+ ## Troubleshooting
86
+
87
+ | Symptom | Likely cause | Fix |
88
+ |---|---|---|
89
+ | No bot joined a meeting | **Recording** was off, the event had no conference link, it wasn't synced from a connected calendar, or `RECALL_API_KEY` isn't set | Confirm the event is on, upcoming, has a video link, and came from a synced calendar; confirm `RECALL_API_KEY` is set |
90
+ | Recording never reaches `COMPLETED` | A Recall webhook was missed, or only one of audio/video was produced | The reconciliation job pulls the latest status from Recall within a few minutes; if it is marked `FAILED`, inspect the bot in the Recall dashboard |
91
+ | 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 |
92
+ | Webhook rejected with `500` (`Invalid webhook signature`, 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 |
93
+ | Webhook rejected with `500` (`RECALL_WEBHOOK_SECRET … not set`) | `RECALL_WEBHOOK_SECRET` is not set | Set it on the application registration |
94
+ | Bot left almost immediately | No one was admitted before the lobby / empty-meeting timeout, or everyone left | Adjust the lobby / empty-meeting timeouts in the app settings (see **Customize the bot** in the README) if they're too aggressive |
95
+ | Bot joined a meeting you didn't want recorded | Recording is on by default | Set the event's **Recording** field to Off; the scheduled bot is canceled |
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@twentyhq/call-recorder",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "engines": {
6
+ "node": "^24.5.0",
7
+ "npm": "please-use-yarn",
8
+ "yarn": ">=4.0.2"
9
+ },
10
+ "keywords": [
11
+ "twenty-app"
12
+ ],
13
+ "packageManager": "yarn@4.9.2",
14
+ "scripts": {
15
+ "twenty": "twenty",
16
+ "lint": "oxlint -c .oxlintrc.json .",
17
+ "lint:fix": "oxlint --fix -c .oxlintrc.json .",
18
+ "typecheck": "tsgo --noEmit -p tsconfig.spec.json",
19
+ "test": "vitest run",
20
+ "test:unit": "vitest run --config vitest.unit.config.ts",
21
+ "test:unit:watch": "vitest --config vitest.unit.config.ts",
22
+ "test:watch": "vitest"
23
+ },
24
+ "dependencies": {
25
+ "@sniptt/guards": "^0.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "@emotion/react": "^11.14.0",
29
+ "@emotion/styled": "^11.14.0",
30
+ "@types/node": "^24.7.2",
31
+ "@types/react": "^19.0.0",
32
+ "@typescript/native-preview": "^7.0.0-dev.20260116.1",
33
+ "oxlint": "^0.16.0",
34
+ "react": "^19.0.0",
35
+ "react-dom": "^19.0.0",
36
+ "twenty-client-sdk": "2.16.0",
37
+ "twenty-sdk": "2.16.0",
38
+ "typescript": "^5.9.3",
39
+ "vite-tsconfig-paths": "^4.2.1",
40
+ "vitest": "^4.1.9"
41
+ }
42
+ }
@@ -0,0 +1,5 @@
1
+ <svg width="136" height="136" viewBox="0 0 136 136" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="136" height="136" fill="#D9F3EE"/>
3
+ <path d="M75.9072 46.0928C76.7667 46.9522 77.2499 48.1176 77.25 49.333V86.667C77.2499 87.8824 76.7667 89.0478 75.9072 89.9072C75.0478 90.7667 73.8824 91.2499 72.667 91.25H35.333C34.1176 91.2499 32.9522 90.7667 32.0928 89.9072C31.2333 89.0478 30.7501 87.8825 30.75 86.667V50.2044C30.75 49.6487 30.5188 49.1181 30.1119 48.7397L22.9309 42.0629C22.2912 41.4681 21.25 41.9218 21.25 42.7953V86.667C21.2501 90.402 22.7339 93.9839 25.375 96.625C28.0161 99.2661 31.598 100.75 35.333 100.75H72.667C76.402 100.75 79.9839 99.2661 82.625 96.625C85.2661 93.9839 86.7499 90.402 86.75 86.667V49.333C86.7499 45.598 85.2661 42.0161 82.625 39.375C79.9839 36.7339 76.402 35.2501 72.667 35.25H27.157C26.2342 35.25 25.8038 36.3934 26.4977 37.0018L34.7671 44.2537C35.1319 44.5736 35.6005 44.75 36.0857 44.75H72.667C73.8825 44.7501 75.0478 45.2333 75.9072 46.0928Z" fill="#12A594"/>
4
+ <path d="M104.757 42.8125C106.259 42.8802 107.723 43.3068 109.024 44.0547L109.283 44.208L109.536 44.3701C110.704 45.1443 111.685 46.1699 112.406 47.3711L112.557 47.6309L112.699 47.8955C113.389 49.2291 113.749 50.7108 113.75 52.2149V83.7852C113.749 85.3894 113.338 86.9672 112.557 88.3682C111.775 89.7692 110.648 90.9477 109.283 91.791C107.918 92.6344 106.36 93.1143 104.757 93.1865C103.154 93.2587 101.559 92.9203 100.124 92.2031L92.1058 88.1953C91.4281 87.8566 91 87.164 91 86.4063V78.6384C91 77.8951 91.7823 77.4116 92.4472 77.744L104.25 83.6445V52.3535L92.4471 58.2532C91.7822 58.5856 91 58.1021 91 57.3588V49.5926C91 48.835 91.4281 48.1425 92.1057 47.8037L100.123 43.7959C101.558 43.0788 103.154 42.7404 104.757 42.8125Z" fill="#12A594"/>
5
+ </svg>
@@ -0,0 +1,100 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+
5
+ import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
6
+
7
+ const APP_PATH = process.cwd();
8
+ const CONFIG_DIR = path.join(os.homedir(), '.twenty');
9
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.test.json');
10
+
11
+ function validateEnv(): { apiUrl: string; apiKey: string } {
12
+ const apiUrl = process.env.TWENTY_API_URL;
13
+ const apiKey = process.env.TWENTY_API_KEY;
14
+
15
+ if (!apiUrl || !apiKey) {
16
+ throw new Error(
17
+ 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
18
+ 'Start a local server: yarn twenty docker:start\n' +
19
+ 'Or set them in vitest env config.',
20
+ );
21
+ }
22
+
23
+ return { apiUrl, apiKey };
24
+ }
25
+
26
+ async function checkServer(apiUrl: string) {
27
+ let response: Response;
28
+
29
+ try {
30
+ response = await fetch(`${apiUrl}/healthz`);
31
+ } catch {
32
+ throw new Error(
33
+ `Twenty server is not reachable at ${apiUrl}. ` +
34
+ 'Make sure the server is running before executing integration tests.',
35
+ );
36
+ }
37
+
38
+ if (!response.ok) {
39
+ throw new Error(`Server at ${apiUrl} returned ${response.status}`);
40
+ }
41
+ }
42
+
43
+ function writeConfig(apiUrl: string, apiKey: string) {
44
+ const payload = JSON.stringify(
45
+ {
46
+ remotes: {
47
+ local: { apiUrl, apiKey, accessToken: apiKey },
48
+ },
49
+ defaultRemote: 'local',
50
+ },
51
+ null,
52
+ 2,
53
+ );
54
+
55
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
56
+ fs.writeFileSync(CONFIG_PATH, payload);
57
+ }
58
+
59
+ function removeConfig() {
60
+ fs.rmSync(CONFIG_PATH, { force: true });
61
+ }
62
+
63
+ export async function setup() {
64
+ const { apiUrl, apiKey } = validateEnv();
65
+
66
+ await checkServer(apiUrl);
67
+
68
+ writeConfig(apiUrl, apiKey);
69
+
70
+ await appUninstall({ appPath: APP_PATH }).catch(() => {});
71
+
72
+ const result = await appDevOnce({
73
+ appPath: APP_PATH,
74
+ onProgress: (message: string) => console.log(`[dev] ${message}`),
75
+ });
76
+
77
+ if (!result.success) {
78
+ throw new Error(
79
+ `Dev sync failed: ${result.error?.message ?? 'Unknown error'}`,
80
+ );
81
+ }
82
+ }
83
+
84
+ export async function teardown() {
85
+ try {
86
+ const uninstallResult = await appUninstall({ appPath: APP_PATH });
87
+
88
+ if (!uninstallResult.success) {
89
+ console.warn(
90
+ `App uninstall failed: ${uninstallResult.error?.message ?? 'Unknown error'}`,
91
+ );
92
+ }
93
+ } catch (error) {
94
+ console.warn(
95
+ `App uninstall failed: ${error instanceof Error ? error.message : String(error)}`,
96
+ );
97
+ } finally {
98
+ removeConfig();
99
+ }
100
+ }
@@ -0,0 +1,104 @@
1
+ import { CoreApiClient } from 'twenty-client-sdk/core';
2
+ import { MetadataApiClient } from 'twenty-client-sdk/metadata';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/application-universal-identifier';
6
+ import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status';
7
+ import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
8
+
9
+ describe('App installation', () => {
10
+ it('should find the installed app in the applications list', async () => {
11
+ const client = new MetadataApiClient();
12
+
13
+ const result = await client.query({
14
+ findManyApplications: {
15
+ id: true,
16
+ name: true,
17
+ universalIdentifier: true,
18
+ },
19
+ });
20
+
21
+ const app = result.findManyApplications.find(
22
+ (a: { universalIdentifier: string }) =>
23
+ a.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
24
+ );
25
+
26
+ expect(app).toBeDefined();
27
+ });
28
+ });
29
+
30
+ describe('CallRecording status contract', () => {
31
+ it('accepts every app status supported by the current server and every request status value the app mirrors', async () => {
32
+ const client = new CoreApiClient();
33
+ const serverCallRecordingStatuses = await getServerCallRecordingStatuses();
34
+
35
+ const created = await client.mutation({
36
+ createCallRecording: {
37
+ __args: {
38
+ data: {
39
+ title: 'Integration test recording',
40
+ status: CallRecordingStatus.SCHEDULED,
41
+ recordingRequestStatus: CallRecordingRequestStatus.REQUESTED,
42
+ },
43
+ },
44
+ id: true,
45
+ },
46
+ });
47
+
48
+ const callRecordingId = created.createCallRecording?.id;
49
+
50
+ expect(callRecordingId).toBeDefined();
51
+
52
+ if (callRecordingId === undefined) {
53
+ throw new Error('Expected call recording creation to return an id');
54
+ }
55
+
56
+ expect(serverCallRecordingStatuses).toEqual(
57
+ expect.arrayContaining(Object.values(CallRecordingStatus)),
58
+ );
59
+
60
+ for (const status of Object.values(CallRecordingStatus)) {
61
+ const updated = await client.mutation({
62
+ updateCallRecording: {
63
+ __args: { id: callRecordingId, data: { status } },
64
+ status: true,
65
+ },
66
+ });
67
+
68
+ expect(updated.updateCallRecording?.status).toBe(status);
69
+ }
70
+
71
+ for (const recordingRequestStatus of Object.values(
72
+ CallRecordingRequestStatus,
73
+ )) {
74
+ const updated = await client.mutation({
75
+ updateCallRecording: {
76
+ __args: { id: callRecordingId, data: { recordingRequestStatus } },
77
+ recordingRequestStatus: true,
78
+ },
79
+ });
80
+
81
+ expect(updated.updateCallRecording?.recordingRequestStatus).toBe(
82
+ recordingRequestStatus,
83
+ );
84
+ }
85
+
86
+ await client.mutation({
87
+ destroyCallRecording: {
88
+ __args: { id: callRecordingId },
89
+ id: true,
90
+ },
91
+ });
92
+ });
93
+ });
94
+
95
+ type GeneratedCoreSchemaRuntime = {
96
+ enumCallRecordingStatusEnum: Record<string, string>;
97
+ };
98
+
99
+ const getServerCallRecordingStatuses = async (): Promise<string[]> => {
100
+ const generatedCoreSchema =
101
+ (await import('twenty-client-sdk/core')) as unknown as GeneratedCoreSchemaRuntime;
102
+
103
+ return Object.values(generatedCoreSchema.enumCallRecordingStatusEnum);
104
+ };
@@ -0,0 +1,96 @@
1
+ import { defineApplication } from 'twenty-sdk/define';
2
+
3
+ import { APP_DESCRIPTION } from 'src/constants/app-description';
4
+ import { APP_DISPLAY_NAME } from 'src/constants/app-display-name';
5
+ import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/application-universal-identifier';
6
+ import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier';
7
+ import { CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier';
8
+ import { CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-name-app-variable-universal-identifier';
9
+ import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier';
10
+ import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier';
11
+ import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds';
12
+ import { CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name';
13
+ import { CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name';
14
+ import { CALL_RECORDER_NAME_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-name-env-var-name';
15
+ import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds';
16
+ import { CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name';
17
+ import { CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name';
18
+ import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds';
19
+ import { CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name';
20
+ import { DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES } from 'src/logic-functions/constants/default-call-recorder-join-early-minutes';
21
+ import { DEFAULT_CALL_RECORDER_NAME } from 'src/logic-functions/constants/default-call-recorder-name';
22
+ import { DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS } from 'src/logic-functions/constants/default-call-recorder-recording-retention-hours';
23
+ import { DEFAULT_RECALL_REGION } from 'src/logic-functions/constants/default-recall-region';
24
+ import { RECALL_API_KEY_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-api-key-env-var-name';
25
+ import { RECALL_REGION_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-region-env-var-name';
26
+ import { RECALL_WEBHOOK_SECRET_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-webhook-secret-env-var-name';
27
+
28
+ export default defineApplication({
29
+ universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
30
+ displayName: APP_DISPLAY_NAME,
31
+ description: APP_DESCRIPTION,
32
+ logoUrl: 'public/logo.svg',
33
+ screenshots: ['public/gallery/call-recorder-cover.png'],
34
+ applicationVariables: {
35
+ [CALL_RECORDER_NAME_ENV_VAR_NAME]: {
36
+ universalIdentifier: CALL_RECORDER_NAME_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
37
+ description: 'Display name the call recorder uses when it joins a call.',
38
+ isSecret: false,
39
+ value: DEFAULT_CALL_RECORDER_NAME,
40
+ },
41
+ [CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME]: {
42
+ universalIdentifier:
43
+ CALL_RECORDER_JOIN_EARLY_MINUTES_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
44
+ description:
45
+ 'How many minutes before the meeting start time the bot should join. Set to 0 to join at the scheduled start time.',
46
+ isSecret: false,
47
+ value: String(DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES),
48
+ },
49
+ [CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
50
+ universalIdentifier:
51
+ CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
52
+ description:
53
+ 'How many seconds the bot waits in a meeting lobby before giving up and leaving.',
54
+ isSecret: false,
55
+ value: String(CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS),
56
+ },
57
+ [CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
58
+ universalIdentifier:
59
+ CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
60
+ description:
61
+ 'How many seconds the bot stays in an empty meeting when no one else ever joins.',
62
+ isSecret: false,
63
+ value: String(CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS),
64
+ },
65
+ [CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME]: {
66
+ universalIdentifier:
67
+ CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_APP_VARIABLE_UNIVERSAL_IDENTIFIER,
68
+ description:
69
+ 'How many seconds the bot keeps recording after everyone else leaves the meeting.',
70
+ isSecret: false,
71
+ value: String(CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS),
72
+ },
73
+ },
74
+ serverVariables: {
75
+ [RECALL_API_KEY_ENV_VAR_NAME]: {
76
+ description:
77
+ '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.',
78
+ isSecret: true,
79
+ isRequired: true,
80
+ },
81
+ [RECALL_REGION_ENV_VAR_NAME]: {
82
+ description: `Recall.ai region used for API requests. Defaults to ${DEFAULT_RECALL_REGION} when unset. Europe Frankfurt is eu-central-1.`,
83
+ isSecret: false,
84
+ },
85
+ [CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME]: {
86
+ 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.`,
87
+ isSecret: false,
88
+ },
89
+ [RECALL_WEBHOOK_SECRET_ENV_VAR_NAME]: {
90
+ description:
91
+ '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.',
92
+ isSecret: true,
93
+ isRequired: true,
94
+ },
95
+ },
96
+ });