@twentyhq/call-recorder 1.0.1 → 1.0.2

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 (237) hide show
  1. package/manifest.json +297 -0
  2. package/package.json +2 -2
  3. package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
  4. package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
  5. package/src/logic-functions/process-recall-webhook.mjs +1744 -0
  6. package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
  7. package/src/logic-functions/recall-webhook.mjs +391 -0
  8. package/src/logic-functions/recall-webhook.mjs.map +7 -0
  9. package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
  10. package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
  11. package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
  12. package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
  13. package/.env.example +0 -5
  14. package/.nvmrc +0 -1
  15. package/.oxlintrc.json +0 -20
  16. package/AGENTS.md +0 -67
  17. package/CLAUDE.md +0 -67
  18. package/README.md +0 -24
  19. package/SETUP.md +0 -95
  20. package/src/__tests__/global-setup.ts +0 -100
  21. package/src/__tests__/schema.integration-test.ts +0 -104
  22. package/src/application-config.ts +0 -96
  23. package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
  24. package/src/constants/app-description.ts +0 -2
  25. package/src/constants/app-display-name.ts +0 -1
  26. package/src/constants/application-universal-identifier.ts +0 -2
  27. package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
  28. package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
  29. package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
  30. package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
  31. package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
  32. package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  33. package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
  34. package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
  35. package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
  36. package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  37. package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
  38. package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
  39. package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
  40. package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
  41. package/src/constants/call-recorder-preference.ts +0 -4
  42. package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  43. package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
  44. package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
  45. package/src/constants/default-role-universal-identifier.ts +0 -2
  46. package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
  47. package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
  48. package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
  49. package/src/default-role.ts +0 -69
  50. package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
  51. package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
  52. package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
  53. package/src/front-components/components/CalendarEventRecording.tsx +0 -39
  54. package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
  55. package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
  56. package/src/front-components/components/RecordingTranscript.tsx +0 -92
  57. package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
  58. package/src/front-components/components/TranscriptEntryList.tsx +0 -61
  59. package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
  60. package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
  61. package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
  62. package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
  63. package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
  64. package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
  65. package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
  66. package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
  67. package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
  68. package/src/front-components/types/transcript-entry.type.ts +0 -13
  69. package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
  70. package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
  71. package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
  72. package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
  73. package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
  74. package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
  75. package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
  76. package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
  77. package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
  78. package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
  79. package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
  80. package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
  81. package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
  82. package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
  83. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
  84. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
  85. package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
  86. package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
  87. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
  88. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
  89. package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
  90. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
  91. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
  92. package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
  93. package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
  94. package/src/logic-functions/constants/call-recording-status.ts +0 -9
  95. package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
  96. package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
  97. package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
  98. package/src/logic-functions/constants/default-recall-region.ts +0 -1
  99. package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
  100. package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
  101. package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
  102. package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
  103. package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
  104. package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
  105. package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
  106. package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
  107. package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
  108. package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
  109. package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
  110. package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
  111. package/src/logic-functions/constants/twenty-page-size.ts +0 -1
  112. package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
  113. package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
  114. package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
  115. package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
  116. package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
  117. package/src/logic-functions/data/create-call-recording.util.ts +0 -41
  118. package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
  119. package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
  120. package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
  121. package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
  122. package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
  123. package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
  124. package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
  125. package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
  126. package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
  127. package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
  128. package/src/logic-functions/data/update-call-recording.util.ts +0 -24
  129. package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
  130. package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
  131. package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
  132. package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
  133. package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
  134. package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
  135. package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
  136. package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
  137. package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
  138. package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
  139. package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
  140. package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
  141. package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
  142. package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
  143. package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
  144. package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
  145. package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
  146. package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
  147. package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
  148. package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
  149. package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
  150. package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
  151. package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
  152. package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
  153. package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
  154. package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
  155. package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
  156. package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
  157. package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
  158. package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
  159. package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
  160. package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
  161. package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
  162. package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
  163. package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
  164. package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
  165. package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
  166. package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
  167. package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
  168. package/src/logic-functions/flows/download-transcript.util.ts +0 -67
  169. package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
  170. package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
  171. package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
  172. package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
  173. package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
  174. package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
  175. package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
  176. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
  177. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
  178. package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
  179. package/src/logic-functions/process-recall-webhook.ts +0 -23
  180. package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
  181. package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
  182. package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
  183. package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
  184. package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
  185. package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
  186. package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
  187. package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
  188. package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
  189. package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
  190. package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
  191. package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
  192. package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
  193. package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
  194. package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
  195. package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
  196. package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
  197. package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
  198. package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
  199. package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
  200. package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
  201. package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
  202. package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
  203. package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
  204. package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
  205. package/src/logic-functions/recall-webhook.ts +0 -90
  206. package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
  207. package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
  208. package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
  209. package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
  210. package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
  211. package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
  212. package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
  213. package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
  214. package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
  215. package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
  216. package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
  217. package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
  218. package/src/logic-functions/types/call-recording-record.type.ts +0 -15
  219. package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
  220. package/src/logic-functions/types/files-field-value.type.ts +0 -1
  221. package/src/logic-functions/types/meeting-recording.type.ts +0 -7
  222. package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
  223. package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
  224. package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
  225. package/src/logic-functions/types/transcript-marker.type.ts +0 -6
  226. package/src/logic-functions/utils/as-record.util.ts +0 -6
  227. package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
  228. package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
  229. package/src/logic-functions/utils/get-string.util.ts +0 -4
  230. package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
  231. package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
  232. package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
  233. package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
  234. package/tsconfig.json +0 -42
  235. package/tsconfig.spec.json +0 -9
  236. package/vitest.config.ts +0 -31
  237. package/vitest.unit.config.ts +0 -14
@@ -1,59 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { isCallRecordingIngestionComplete } from 'src/logic-functions/domain/is-call-recording-ingestion-complete.util';
4
-
5
- const AUDIO_VALUE = [{ fileId: 'file-audio-1', label: 'audio.mp3' }];
6
- const VIDEO_VALUE = [{ fileId: 'file-video-1', label: 'video.mp4' }];
7
- const TRANSCRIPT_CONTENT = [{ participant: { id: 1 }, words: [] }];
8
-
9
- describe('isCallRecordingIngestionComplete', () => {
10
- it('is complete when transcript content and both media files are present', () => {
11
- expect(
12
- isCallRecordingIngestionComplete({
13
- transcript: TRANSCRIPT_CONTENT,
14
- audio: AUDIO_VALUE,
15
- video: VIDEO_VALUE,
16
- }),
17
- ).toBe(true);
18
- });
19
-
20
- it('is incomplete while the transcript holds a marker', () => {
21
- expect(
22
- isCallRecordingIngestionComplete({
23
- transcript: {
24
- recallTranscriptId: 'recall-transcript-1',
25
- status: 'PENDING',
26
- },
27
- audio: AUDIO_VALUE,
28
- video: VIDEO_VALUE,
29
- }),
30
- ).toBe(false);
31
- });
32
-
33
- it('is incomplete when the transcript is unset', () => {
34
- expect(
35
- isCallRecordingIngestionComplete({
36
- transcript: null,
37
- audio: AUDIO_VALUE,
38
- video: VIDEO_VALUE,
39
- }),
40
- ).toBe(false);
41
- });
42
-
43
- it('is incomplete while any media field is empty', () => {
44
- expect(
45
- isCallRecordingIngestionComplete({
46
- transcript: TRANSCRIPT_CONTENT,
47
- audio: undefined,
48
- video: VIDEO_VALUE,
49
- }),
50
- ).toBe(false);
51
- expect(
52
- isCallRecordingIngestionComplete({
53
- transcript: TRANSCRIPT_CONTENT,
54
- audio: AUDIO_VALUE,
55
- video: [],
56
- }),
57
- ).toBe(false);
58
- });
59
- });
@@ -1,37 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { isCallRecordingStatusDowngrade } from 'src/logic-functions/domain/is-call-recording-status-downgrade.util';
4
-
5
- describe('isCallRecordingStatusDowngrade', () => {
6
- it.each([
7
- ['SCHEDULED', 'JOINING', false],
8
- ['JOINING', 'RECORDING', false],
9
- ['RECORDING', 'PROCESSING', false],
10
- ['PROCESSING', 'FAILED', false],
11
- ['PROCESSING', 'COMPLETED', false],
12
- ['RECORDING', 'RECORDING', false],
13
- ['COMPLETED', 'RECORDING', true],
14
- ['PROCESSING', 'JOINING', true],
15
- ['FAILED', 'RECORDING', true],
16
- ['JOINING', 'SCHEDULED', true],
17
- ])('from %s to %s -> %s', (fromStatus, toStatus, expected) => {
18
- expect(isCallRecordingStatusDowngrade({ fromStatus, toStatus })).toBe(
19
- expected,
20
- );
21
- });
22
-
23
- it('never treats transitions from unknown statuses as downgrades', () => {
24
- expect(
25
- isCallRecordingStatusDowngrade({
26
- fromStatus: undefined,
27
- toStatus: 'COMPLETED',
28
- }),
29
- ).toBe(false);
30
- expect(
31
- isCallRecordingStatusDowngrade({
32
- fromStatus: 'NOT_A_STATUS',
33
- toStatus: 'SCHEDULED',
34
- }),
35
- ).toBe(false);
36
- });
37
- });
@@ -1,120 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { CallRecorderPreference } from 'src/constants/call-recorder-preference';
4
- import { resolveCallRecorderPolicyResult } from 'src/logic-functions/domain/resolve-call-recorder-policy-result.util';
5
-
6
- const NOW = new Date('2026-01-01T12:00:00.000Z');
7
- const FUTURE_STARTS_AT = '2026-01-01T13:00:00.000Z';
8
- const FUTURE_ENDS_AT = '2026-01-01T14:00:00.000Z';
9
- const PAST_STARTS_AT = '2026-01-01T09:00:00.000Z';
10
- const PAST_ENDS_AT = '2026-01-01T10:00:00.000Z';
11
-
12
- describe('resolveCallRecorderPolicyResult', () => {
13
- it('requires a bot when preference is ON and the event is upcoming', () => {
14
- expect(
15
- resolveCallRecorderPolicyResult({
16
- input: {
17
- callRecorderPreference: CallRecorderPreference.ON,
18
- isCanceled: false,
19
- startsAt: FUTURE_STARTS_AT,
20
- endsAt: FUTURE_ENDS_AT,
21
- conferenceLinkUrl: 'https://meet.example.com/team-sync',
22
- },
23
- now: NOW,
24
- }),
25
- ).toEqual({
26
- shouldRequestBot: true,
27
- reason: 'RECORDING_ENABLED',
28
- });
29
- });
30
-
31
- it('does not request a bot for ON when the meeting has no conference link', () => {
32
- expect(
33
- resolveCallRecorderPolicyResult({
34
- input: {
35
- callRecorderPreference: CallRecorderPreference.ON,
36
- isCanceled: false,
37
- startsAt: FUTURE_STARTS_AT,
38
- endsAt: FUTURE_ENDS_AT,
39
- conferenceLinkUrl: undefined,
40
- },
41
- now: NOW,
42
- }),
43
- ).toEqual({
44
- shouldRequestBot: false,
45
- reason: 'MISSING_CONFERENCE_LINK',
46
- });
47
- });
48
-
49
- it('requires a bot without an event preference override', () => {
50
- expect(
51
- resolveCallRecorderPolicyResult({
52
- input: {
53
- callRecorderPreference: undefined,
54
- isCanceled: false,
55
- startsAt: FUTURE_STARTS_AT,
56
- endsAt: FUTURE_ENDS_AT,
57
- conferenceLinkUrl: 'https://meet.example.com/team-sync',
58
- },
59
- now: NOW,
60
- }),
61
- ).toEqual({
62
- shouldRequestBot: true,
63
- reason: 'RECORDING_ENABLED',
64
- });
65
- });
66
-
67
- it('lets an OFF event preference opt out of workspace auto-recording', () => {
68
- expect(
69
- resolveCallRecorderPolicyResult({
70
- input: {
71
- callRecorderPreference: CallRecorderPreference.OFF,
72
- isCanceled: false,
73
- startsAt: FUTURE_STARTS_AT,
74
- endsAt: FUTURE_ENDS_AT,
75
- conferenceLinkUrl: 'https://meet.example.com/team-sync',
76
- },
77
- now: NOW,
78
- }),
79
- ).toEqual({
80
- shouldRequestBot: false,
81
- reason: 'PREFERENCE_OFF',
82
- });
83
- });
84
-
85
- it('does not request a bot for a meeting that already ended', () => {
86
- expect(
87
- resolveCallRecorderPolicyResult({
88
- input: {
89
- callRecorderPreference: undefined,
90
- isCanceled: false,
91
- startsAt: PAST_STARTS_AT,
92
- endsAt: PAST_ENDS_AT,
93
- conferenceLinkUrl: 'https://meet.example.com/team-sync',
94
- },
95
- now: NOW,
96
- }),
97
- ).toEqual({
98
- shouldRequestBot: false,
99
- reason: 'EVENT_NOT_UPCOMING',
100
- });
101
- });
102
-
103
- it('does not request a bot for a canceled meeting', () => {
104
- expect(
105
- resolveCallRecorderPolicyResult({
106
- input: {
107
- callRecorderPreference: undefined,
108
- isCanceled: true,
109
- startsAt: FUTURE_STARTS_AT,
110
- endsAt: FUTURE_ENDS_AT,
111
- conferenceLinkUrl: 'https://meet.example.com/team-sync',
112
- },
113
- now: NOW,
114
- }),
115
- ).toEqual({
116
- shouldRequestBot: false,
117
- reason: 'EVENT_CANCELED',
118
- });
119
- });
120
- });
@@ -1,102 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
4
- import { shouldCompleteCallRecordingIngestion } from 'src/logic-functions/domain/should-complete-call-recording-ingestion.util';
5
-
6
- const filledTranscript = [{ participant: { id: 1 }, words: [] }];
7
- const filledAudio = [{ fileId: 'file-audio-1', label: 'audio.mp3' }];
8
- const filledVideo = [{ fileId: 'file-video-1', label: 'video.mp4' }];
9
-
10
- describe('shouldCompleteCallRecordingIngestion', () => {
11
- it('requires complete artifacts and billable timestamps before completion', () => {
12
- expect(
13
- shouldCompleteCallRecordingIngestion({
14
- current: {
15
- status: CallRecordingStatus.PROCESSING,
16
- startedAt: '2026-06-10T09:00:00.000Z',
17
- endedAt: '2026-06-10T10:00:00.000Z',
18
- transcript: filledTranscript,
19
- audio: filledAudio,
20
- video: filledVideo,
21
- },
22
- updateData: {},
23
- }),
24
- ).toBe(true);
25
-
26
- expect(
27
- shouldCompleteCallRecordingIngestion({
28
- current: {
29
- status: CallRecordingStatus.PROCESSING,
30
- endedAt: '2026-06-10T10:00:00.000Z',
31
- transcript: filledTranscript,
32
- audio: filledAudio,
33
- video: filledVideo,
34
- },
35
- updateData: {},
36
- }),
37
- ).toBe(false);
38
-
39
- expect(
40
- shouldCompleteCallRecordingIngestion({
41
- current: {
42
- status: CallRecordingStatus.PROCESSING,
43
- transcript: filledTranscript,
44
- audio: filledAudio,
45
- video: filledVideo,
46
- },
47
- updateData: {
48
- startedAt: '2026-06-10T09:00:00.000Z',
49
- endedAt: '2026-06-10T10:00:00.000Z',
50
- },
51
- }),
52
- ).toBe(true);
53
-
54
- expect(
55
- shouldCompleteCallRecordingIngestion({
56
- current: {
57
- status: CallRecordingStatus.PROCESSING,
58
- startedAt: '2026-06-10T10:00:00.000Z',
59
- endedAt: '2026-06-10T09:00:00.000Z',
60
- transcript: filledTranscript,
61
- audio: filledAudio,
62
- video: filledVideo,
63
- },
64
- updateData: {},
65
- }),
66
- ).toBe(false);
67
- });
68
-
69
- it('does not complete a persisted failed recording', () => {
70
- expect(
71
- shouldCompleteCallRecordingIngestion({
72
- current: {
73
- status: CallRecordingStatus.FAILED,
74
- startedAt: '2026-06-10T09:00:00.000Z',
75
- endedAt: '2026-06-10T10:00:00.000Z',
76
- transcript: filledTranscript,
77
- audio: filledAudio,
78
- video: filledVideo,
79
- },
80
- updateData: {},
81
- }),
82
- ).toBe(false);
83
- });
84
-
85
- it('does not complete when the incoming update marks the recording as failed', () => {
86
- expect(
87
- shouldCompleteCallRecordingIngestion({
88
- current: {
89
- status: CallRecordingStatus.PROCESSING,
90
- startedAt: '2026-06-10T09:00:00.000Z',
91
- endedAt: '2026-06-10T10:00:00.000Z',
92
- transcript: filledTranscript,
93
- audio: filledAudio,
94
- video: filledVideo,
95
- },
96
- updateData: {
97
- status: CallRecordingStatus.FAILED,
98
- },
99
- }),
100
- ).toBe(false);
101
- });
102
- });
@@ -1,42 +0,0 @@
1
- import { type CallRecorderPolicyResultForCalendarEvent } from 'src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type';
2
- import { type CallRecorderPolicyResultForMeeting } from 'src/logic-functions/types/call-recorder-policy-result-for-meeting.type';
3
-
4
- type CallRecorderPolicyResultForMeetingInput = Pick<
5
- CallRecorderPolicyResultForCalendarEvent,
6
- 'calendarEventId' | 'realMeetingKey' | 'shouldRequestBot'
7
- >;
8
-
9
- export const aggregateCallRecorderPolicyResultsByMeeting = (
10
- perCalendarEventPolicyResults: CallRecorderPolicyResultForMeetingInput[],
11
- ): CallRecorderPolicyResultForMeeting[] => {
12
- const meetingPolicyResultsByMeetingKey = new Map<
13
- string,
14
- CallRecorderPolicyResultForMeeting
15
- >();
16
-
17
- for (const {
18
- calendarEventId,
19
- realMeetingKey,
20
- shouldRequestBot,
21
- } of perCalendarEventPolicyResults) {
22
- const meetingPolicyResult = meetingPolicyResultsByMeetingKey.get(
23
- realMeetingKey,
24
- ) ?? {
25
- realMeetingKey,
26
- shouldRequestBot: false,
27
- calendarEventIds: [],
28
- requestingCalendarEventIds: [],
29
- };
30
-
31
- meetingPolicyResult.calendarEventIds.push(calendarEventId);
32
-
33
- if (shouldRequestBot) {
34
- meetingPolicyResult.shouldRequestBot = true;
35
- meetingPolicyResult.requestingCalendarEventIds.push(calendarEventId);
36
- }
37
-
38
- meetingPolicyResultsByMeetingKey.set(realMeetingKey, meetingPolicyResult);
39
- }
40
-
41
- return [...meetingPolicyResultsByMeetingKey.values()];
42
- };
@@ -1,53 +0,0 @@
1
- import { CallRecorderPreference } from 'src/constants/call-recorder-preference';
2
- import { type CallRecorderPolicyCalendarEventInput } from 'src/logic-functions/types/call-recorder-policy-calendar-event-input.type';
3
- import { type CallRecorderPolicyResultForCalendarEvent } from 'src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type';
4
- import { computeRealMeetingKey } from 'src/logic-functions/domain/compute-real-meeting-key.util';
5
- import { resolveCallRecorderPolicyResult } from 'src/logic-functions/domain/resolve-call-recorder-policy-result.util';
6
-
7
- export const buildCallRecorderPolicyResult = (
8
- calendarEvent: CallRecorderPolicyCalendarEventInput,
9
- now: Date,
10
- ): CallRecorderPolicyResultForCalendarEvent => {
11
- const realMeetingKey = computeRealMeetingKey({
12
- calendarEventId: calendarEvent.id,
13
- conferenceLinkUrl: calendarEvent.conferenceLinkUrl,
14
- iCalUid: calendarEvent.iCalUid,
15
- startsAt: calendarEvent.startsAt,
16
- });
17
-
18
- const callRecorderPreference = normalizeCallRecorderPreference(
19
- calendarEvent.callRecorderPreference,
20
- );
21
-
22
- const policyResult = resolveCallRecorderPolicyResult({
23
- input: {
24
- callRecorderPreference,
25
- isCanceled: calendarEvent.isCanceled,
26
- startsAt: calendarEvent.startsAt,
27
- endsAt: calendarEvent.endsAt,
28
- conferenceLinkUrl: calendarEvent.conferenceLinkUrl,
29
- },
30
- now,
31
- });
32
-
33
- return {
34
- calendarEventId: calendarEvent.id,
35
- callRecorderPreference,
36
- realMeetingKey,
37
- ...policyResult,
38
- };
39
- };
40
-
41
- const normalizeCallRecorderPreference = (
42
- callRecorderPreference: string | undefined,
43
- ): CallRecorderPreference | undefined =>
44
- isCallRecorderPreference(callRecorderPreference)
45
- ? callRecorderPreference
46
- : undefined;
47
-
48
- const isCallRecorderPreference = (
49
- callRecorderPreference: string | undefined,
50
- ): callRecorderPreference is CallRecorderPreference =>
51
- Object.values(CallRecorderPreference).some(
52
- (preference) => preference === callRecorderPreference,
53
- );
@@ -1,13 +0,0 @@
1
- import { type TranscriptMarker } from 'src/logic-functions/types/transcript-marker.type';
2
-
3
- export const buildFailedTranscriptMarker = ({
4
- recallTranscriptId,
5
- subCode,
6
- }: {
7
- recallTranscriptId: string | null;
8
- subCode: string | null;
9
- }): TranscriptMarker => ({
10
- recallTranscriptId,
11
- status: 'FAILED',
12
- subCode,
13
- });
@@ -1,13 +0,0 @@
1
- import { type TranscriptMarker } from 'src/logic-functions/types/transcript-marker.type';
2
-
3
- export const buildPendingTranscriptMarker = ({
4
- recallTranscriptId,
5
- requestedAt,
6
- }: {
7
- recallTranscriptId: string;
8
- requestedAt: string;
9
- }): TranscriptMarker => ({
10
- recallTranscriptId,
11
- status: 'PENDING',
12
- requestedAt,
13
- });
@@ -1,12 +0,0 @@
1
- import { type RecallRoutingMetadata } from 'src/logic-functions/types/recall-routing-metadata.type';
2
-
3
- export const buildRecallRoutingMetadata = ({
4
- callRecordingId,
5
- workspaceId,
6
- }: {
7
- callRecordingId: string;
8
- workspaceId: string;
9
- }): RecallRoutingMetadata => ({
10
- twentyWorkspaceId: workspaceId,
11
- twentyCallRecordingId: callRecordingId,
12
- });
@@ -1,7 +0,0 @@
1
- import { isNull } from '@sniptt/guards';
2
-
3
- export const buildTranscriptFailureReason = (
4
- subCode: string | null,
5
- ): string => {
6
- return isNull(subCode) ? 'transcript_failed' : `transcript_failed:${subCode}`;
7
- };
@@ -1,41 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { CALL_RECORDING_MICRO_CREDITS_PER_HOUR } from 'src/logic-functions/constants/call-recording-micro-credits-per-hour';
4
- import { MILLISECONDS_PER_MINUTE } from 'src/logic-functions/constants/milliseconds-per-minute';
5
-
6
- const MILLISECONDS_PER_HOUR = 3_600_000;
7
-
8
- export type CallRecordingCharge = {
9
- creditsUsedMicro: number;
10
- quantityMinutes: number;
11
- };
12
-
13
- export const computeCallRecordingCharge = ({
14
- startedAt,
15
- endedAt,
16
- }: {
17
- startedAt: string | undefined;
18
- endedAt: string | undefined;
19
- }): CallRecordingCharge | undefined => {
20
- if (isUndefined(startedAt) || isUndefined(endedAt)) {
21
- return undefined;
22
- }
23
-
24
- const durationMilliseconds =
25
- new Date(endedAt).getTime() - new Date(startedAt).getTime();
26
-
27
- if (!Number.isFinite(durationMilliseconds) || durationMilliseconds <= 0) {
28
- return undefined;
29
- }
30
-
31
- return {
32
- creditsUsedMicro: Math.round(
33
- (durationMilliseconds / MILLISECONDS_PER_HOUR) *
34
- CALL_RECORDING_MICRO_CREDITS_PER_HOUR,
35
- ),
36
- quantityMinutes: Math.max(
37
- 1,
38
- Math.round(durationMilliseconds / MILLISECONDS_PER_MINUTE),
39
- ),
40
- };
41
- };
@@ -1,16 +0,0 @@
1
- import { createHash } from 'crypto';
2
-
3
- // Same meeting key → same id: the primary key serializes concurrent creates.
4
- export const computeCallRecordingIdForMeeting = (
5
- realMeetingKey: string,
6
- ): string => {
7
- const bytes = createHash('sha256').update(realMeetingKey).digest();
8
-
9
- // v4 version/variant bits so server-side UUID validation accepts the hash.
10
- bytes[6] = (bytes[6] & 0x0f) | 0x40;
11
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
12
-
13
- const hex = bytes.subarray(0, 16).toString('hex');
14
-
15
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
16
- };
@@ -1,48 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
4
-
5
- type ComputeRealMeetingKeyInput = {
6
- calendarEventId: string;
7
- conferenceLinkUrl: unknown;
8
- iCalUid: string | undefined;
9
- startsAt: string | undefined;
10
- };
11
-
12
- export const computeRealMeetingKey = ({
13
- calendarEventId,
14
- conferenceLinkUrl,
15
- iCalUid,
16
- startsAt,
17
- }: ComputeRealMeetingKeyInput): string => {
18
- const normalizedConferenceLink = normalizeConferenceLink(conferenceLinkUrl);
19
-
20
- if (!isUndefined(normalizedConferenceLink)) {
21
- return `link:${normalizedConferenceLink}:${startsAt ?? ''}`;
22
- }
23
-
24
- if (isNonEmptyString(iCalUid)) {
25
- return `ical:${iCalUid}:${startsAt ?? ''}`;
26
- }
27
-
28
- return `event:${calendarEventId}`;
29
- };
30
-
31
- const normalizeConferenceLink = (
32
- conferenceLinkUrl: unknown,
33
- ): string | undefined => {
34
- if (!isNonEmptyString(conferenceLinkUrl)) {
35
- return undefined;
36
- }
37
-
38
- const withoutProtocol = conferenceLinkUrl
39
- .trim()
40
- .toLowerCase()
41
- .replace(/^https?:\/\//, '')
42
- .replace(/^www\./, '');
43
-
44
- const withoutQueryAndFragment = withoutProtocol.split(/[?#]/)[0];
45
- const withoutTrailingSlash = withoutQueryAndFragment.replace(/\/+$/, '');
46
-
47
- return withoutTrailingSlash === '' ? undefined : withoutTrailingSlash;
48
- };
@@ -1,34 +0,0 @@
1
- import { DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES } from 'src/logic-functions/constants/default-call-recorder-join-early-minutes';
2
- import { MILLISECONDS_PER_MINUTE } from 'src/logic-functions/constants/milliseconds-per-minute';
3
- import { CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name';
4
- import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
5
- import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
6
-
7
- export const computeRecallBotJoinAt = (meetingStartsAt: string): string => {
8
- const meetingStartTimeInMilliseconds = new Date(meetingStartsAt).getTime();
9
-
10
- if (Number.isNaN(meetingStartTimeInMilliseconds)) {
11
- return meetingStartsAt;
12
- }
13
-
14
- return new Date(
15
- meetingStartTimeInMilliseconds -
16
- getRecallBotJoinEarlyMinutes() * MILLISECONDS_PER_MINUTE,
17
- ).toISOString();
18
- };
19
-
20
- const getRecallBotJoinEarlyMinutes = (): number => {
21
- const rawValue = getApplicationVariableValue(
22
- CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME,
23
- );
24
-
25
- if (!isNonEmptyString(rawValue)) {
26
- return DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
27
- }
28
-
29
- const joinEarlyMinutes = Number(rawValue.trim());
30
-
31
- return Number.isInteger(joinEarlyMinutes) && joinEarlyMinutes >= 0
32
- ? joinEarlyMinutes
33
- : DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
34
- };
@@ -1,19 +0,0 @@
1
- import { isNonEmptyArray, isNull, isUndefined } from '@sniptt/guards';
2
-
3
- import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type';
4
- import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util';
5
-
6
- export const isCallRecordingIngestionComplete = ({
7
- transcript,
8
- audio,
9
- video,
10
- }: {
11
- transcript: unknown;
12
- audio: FilesFieldValue | undefined;
13
- video: FilesFieldValue | undefined;
14
- }): boolean =>
15
- !isNull(transcript) &&
16
- !isUndefined(transcript) &&
17
- isUndefined(parseTranscriptMarker(transcript)) &&
18
- isNonEmptyArray(audio) &&
19
- isNonEmptyArray(video);
@@ -1,37 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
4
-
5
- // Deliveries are unordered; a late event must never move status backwards.
6
- const CALL_RECORDING_STATUS_PROGRESSION: Record<CallRecordingStatus, number> = {
7
- [CallRecordingStatus.SCHEDULED]: 0,
8
- [CallRecordingStatus.JOINING]: 1,
9
- [CallRecordingStatus.RECORDING]: 2,
10
- [CallRecordingStatus.PROCESSING]: 3,
11
- [CallRecordingStatus.FAILED]: 4,
12
- [CallRecordingStatus.COMPLETED]: 5,
13
- };
14
-
15
- const getCallRecordingStatusRank = (status: string): number | undefined =>
16
- status in CALL_RECORDING_STATUS_PROGRESSION
17
- ? CALL_RECORDING_STATUS_PROGRESSION[status as CallRecordingStatus]
18
- : undefined;
19
-
20
- export const isCallRecordingStatusDowngrade = ({
21
- fromStatus,
22
- toStatus,
23
- }: {
24
- fromStatus: string | undefined;
25
- toStatus: string;
26
- }): boolean => {
27
- const fromRank = isUndefined(fromStatus)
28
- ? undefined
29
- : getCallRecordingStatusRank(fromStatus);
30
- const toRank = getCallRecordingStatusRank(toStatus);
31
-
32
- if (isUndefined(fromRank) || isUndefined(toRank)) {
33
- return false;
34
- }
35
-
36
- return toRank < fromRank;
37
- };
@@ -1,13 +0,0 @@
1
- export const isRecallRecordingDoneSignal = ({
2
- event,
3
- statusCode,
4
- }: {
5
- event: string;
6
- statusCode: string | undefined;
7
- }): boolean => {
8
- return (
9
- event === 'recording.done' ||
10
- event === 'recording.failed' ||
11
- statusCode === 'done'
12
- );
13
- };