@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
@@ -0,0 +1,2268 @@
1
+ import { createRequire as __createRequire } from 'module';
2
+ const require = __createRequire(import.meta.url);
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e2) {
13
+ throw mod = 0, e2;
14
+ }
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // node_modules/@sniptt/guards/build/guards/primitives.js
34
+ var require_primitives = __commonJS({
35
+ "node_modules/@sniptt/guards/build/guards/primitives.js"(exports) {
36
+ "use strict";
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.isSymbol = exports.isBigInt = exports.isString = exports.isNumber = exports.isBoolean = exports.isUndefined = void 0;
39
+ var isUndefined26 = (term) => {
40
+ return typeof term === "undefined";
41
+ };
42
+ exports.isUndefined = isUndefined26;
43
+ var isBoolean = (term) => {
44
+ return typeof term === "boolean";
45
+ };
46
+ exports.isBoolean = isBoolean;
47
+ var isNumber = (term) => {
48
+ return typeof term === "number" && !Number.isNaN(term);
49
+ };
50
+ exports.isNumber = isNumber;
51
+ var isString8 = (term) => {
52
+ return typeof term === "string";
53
+ };
54
+ exports.isString = isString8;
55
+ var isBigInt = (term) => {
56
+ return typeof term === "bigint";
57
+ };
58
+ exports.isBigInt = isBigInt;
59
+ var isSymbol = (term) => {
60
+ return typeof term === "symbol";
61
+ };
62
+ exports.isSymbol = isSymbol;
63
+ }
64
+ });
65
+
66
+ // node_modules/@sniptt/guards/build/guards/structural.js
67
+ var require_structural = __commonJS({
68
+ "node_modules/@sniptt/guards/build/guards/structural.js"(exports) {
69
+ "use strict";
70
+ Object.defineProperty(exports, "__esModule", { value: true });
71
+ exports.isDate = exports.isWeakSet = exports.isWeakMap = exports.isSet = exports.isMap = exports.isArray = exports.isObject = exports.isFunction = exports.isNull = void 0;
72
+ var isNull5 = (term) => {
73
+ return term === null;
74
+ };
75
+ exports.isNull = isNull5;
76
+ var isFunction = (term) => {
77
+ return typeof term === "function";
78
+ };
79
+ exports.isFunction = isFunction;
80
+ var isObject2 = (term) => {
81
+ return !exports.isNull(term) && typeof term === "object";
82
+ };
83
+ exports.isObject = isObject2;
84
+ var isArray4 = (term) => {
85
+ return Array.isArray(term);
86
+ };
87
+ exports.isArray = isArray4;
88
+ var isMap = (term) => {
89
+ return term instanceof Map;
90
+ };
91
+ exports.isMap = isMap;
92
+ var isSet = (term) => {
93
+ return term instanceof Set;
94
+ };
95
+ exports.isSet = isSet;
96
+ var isWeakMap = (term) => {
97
+ return term instanceof WeakMap;
98
+ };
99
+ exports.isWeakMap = isWeakMap;
100
+ var isWeakSet = (term) => {
101
+ return term instanceof WeakSet;
102
+ };
103
+ exports.isWeakSet = isWeakSet;
104
+ var isDate = (term) => {
105
+ return term instanceof Date;
106
+ };
107
+ exports.isDate = isDate;
108
+ }
109
+ });
110
+
111
+ // node_modules/@sniptt/guards/build/guards/convenience.js
112
+ var require_convenience = __commonJS({
113
+ "node_modules/@sniptt/guards/build/guards/convenience.js"(exports) {
114
+ "use strict";
115
+ Object.defineProperty(exports, "__esModule", { value: true });
116
+ exports.isNegativeInteger = exports.isNonNegativeInteger = exports.isPositiveInteger = exports.isInteger = exports.isNumberOrNaN = exports.isNonEmptyString = exports.isNonEmptyArray = exports.isObjectOrNull = void 0;
117
+ var primitives_1 = require_primitives();
118
+ var structural_1 = require_structural();
119
+ var isObjectOrNull = (term) => {
120
+ return typeof term === "object";
121
+ };
122
+ exports.isObjectOrNull = isObjectOrNull;
123
+ var isNonEmptyArray3 = (term) => {
124
+ return structural_1.isArray(term) && term.length > 0;
125
+ };
126
+ exports.isNonEmptyArray = isNonEmptyArray3;
127
+ var isNonEmptyString2 = (term) => {
128
+ return primitives_1.isString(term) && term.length > 0;
129
+ };
130
+ exports.isNonEmptyString = isNonEmptyString2;
131
+ var isNumberOrNaN = (term) => {
132
+ return typeof term === "number";
133
+ };
134
+ exports.isNumberOrNaN = isNumberOrNaN;
135
+ var isInteger = (term) => {
136
+ return primitives_1.isNumber(term) && Number.isInteger(term);
137
+ };
138
+ exports.isInteger = isInteger;
139
+ var isPositiveInteger = (term) => {
140
+ return exports.isInteger(term) && term > 0;
141
+ };
142
+ exports.isPositiveInteger = isPositiveInteger;
143
+ var isNonNegativeInteger = (term) => {
144
+ return exports.isInteger(term) && term >= 0;
145
+ };
146
+ exports.isNonNegativeInteger = isNonNegativeInteger;
147
+ var isNegativeInteger = (term) => {
148
+ return exports.isInteger(term) && term < 0;
149
+ };
150
+ exports.isNegativeInteger = isNegativeInteger;
151
+ }
152
+ });
153
+
154
+ // node_modules/@sniptt/guards/build/index.js
155
+ var require_build = __commonJS({
156
+ "node_modules/@sniptt/guards/build/index.js"(exports) {
157
+ "use strict";
158
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
159
+ if (k2 === void 0) k2 = k;
160
+ Object.defineProperty(o, k2, { enumerable: true, get: function() {
161
+ return m[k];
162
+ } });
163
+ }) : (function(o, m, k, k2) {
164
+ if (k2 === void 0) k2 = k;
165
+ o[k2] = m[k];
166
+ }));
167
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
168
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
169
+ }) : function(o, v) {
170
+ o["default"] = v;
171
+ });
172
+ var __importStar = exports && exports.__importStar || function(mod) {
173
+ if (mod && mod.__esModule) return mod;
174
+ var result = {};
175
+ if (mod != null) {
176
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
177
+ }
178
+ __setModuleDefault(result, mod);
179
+ return result;
180
+ };
181
+ var __exportStar = exports && exports.__exportStar || function(m, exports2) {
182
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
183
+ };
184
+ Object.defineProperty(exports, "__esModule", { value: true });
185
+ exports.structural = exports.primitives = exports.convenience = void 0;
186
+ exports.convenience = __importStar(require_convenience());
187
+ __exportStar(require_convenience(), exports);
188
+ exports.primitives = __importStar(require_primitives());
189
+ __exportStar(require_primitives(), exports);
190
+ exports.structural = __importStar(require_structural());
191
+ __exportStar(require_structural(), exports);
192
+ }
193
+ });
194
+
195
+ // src/logic-functions/reconcile-stale-bot-state.ts
196
+ import { CoreApiClient } from "twenty-client-sdk/core";
197
+
198
+ // twenty-sdk-define-stub:__twenty-sdk-define-stub__
199
+ var __defineFactoryStub = (config) => ({
200
+ success: true,
201
+ config,
202
+ errors: []
203
+ });
204
+ var __anyHandler = {
205
+ get(_target, prop) {
206
+ if (prop === "__esModule") return true;
207
+ if (prop === Symbol.toPrimitive) return () => "";
208
+ if (typeof prop === "symbol") return void 0;
209
+ return new Proxy(() => void 0, __anyHandler);
210
+ },
211
+ apply() {
212
+ return new Proxy(() => void 0, __anyHandler);
213
+ }
214
+ };
215
+ var __anyStub = new Proxy(() => void 0, __anyHandler);
216
+ var defineLogicFunction = __defineFactoryStub;
217
+
218
+ // src/constants/stale-bot-state-logic-function-universal-identifier.ts
219
+ var STALE_BOT_STATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "e362aa9b-52c6-4b7e-bb20-927e0e8d7cbe";
220
+
221
+ // src/logic-functions/constants/stale-bot-state-cron-pattern.ts
222
+ var STALE_BOT_STATE_CRON_PATTERN = "*/5 * * * *";
223
+
224
+ // src/logic-functions/flows/converge-diverged-call-recordings.util.ts
225
+ var import_guards20 = __toESM(require_build());
226
+
227
+ // src/logic-functions/constants/non-terminal-call-recording-statuses.ts
228
+ var NON_TERMINAL_CALL_RECORDING_STATUSES = [
229
+ "SCHEDULED" /* SCHEDULED */,
230
+ "JOINING" /* JOINING */,
231
+ "RECORDING" /* RECORDING */,
232
+ "PROCESSING" /* PROCESSING */
233
+ ];
234
+
235
+ // src/logic-functions/constants/twenty-page-size.ts
236
+ var TWENTY_PAGE_SIZE = 100;
237
+
238
+ // src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts
239
+ var import_guards4 = __toESM(require_build());
240
+
241
+ // src/logic-functions/utils/as-record.util.ts
242
+ var import_guards = __toESM(require_build());
243
+ var asRecord = (value) => (0, import_guards.isObject)(value) && !(0, import_guards.isArray)(value) ? value : void 0;
244
+
245
+ // src/logic-functions/utils/is-non-empty-string.util.ts
246
+ var import_guards2 = __toESM(require_build());
247
+ var isNonEmptyString = (value) => (0, import_guards2.isString)(value) && value.trim() !== "";
248
+
249
+ // src/logic-functions/utils/get-string.util.ts
250
+ var getString = (value) => isNonEmptyString(value) ? value : void 0;
251
+
252
+ // src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts
253
+ var mapRecallStatusCodeToCallRecordingStatus = (statusCode) => {
254
+ switch (statusCode) {
255
+ case "joining_call":
256
+ case "in_waiting_room":
257
+ return "JOINING" /* JOINING */;
258
+ case "in_call_not_recording":
259
+ case "recording_permission_allowed":
260
+ case "in_call_recording":
261
+ return "RECORDING" /* RECORDING */;
262
+ // 'done' stays PROCESSING: COMPLETED is set only after all artifacts are ingested.
263
+ case "call_ended":
264
+ case "analysis_done":
265
+ case "done":
266
+ return "PROCESSING" /* PROCESSING */;
267
+ case "fatal":
268
+ case "analysis_failed":
269
+ case "recording_permission_denied":
270
+ return "FAILED" /* FAILED */;
271
+ default:
272
+ return void 0;
273
+ }
274
+ };
275
+
276
+ // src/logic-functions/recall-api/normalize-recall-timestamp.util.ts
277
+ var import_guards3 = __toESM(require_build());
278
+ var normalizeRecallTimestamp = (value) => {
279
+ if ((0, import_guards3.isUndefined)(value)) {
280
+ return void 0;
281
+ }
282
+ const parsed = new Date(value);
283
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed.toISOString();
284
+ };
285
+
286
+ // src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts
287
+ var extractRecallBotConvergence = (bot) => {
288
+ const statusChanges = extractStatusChanges(bot);
289
+ const latestStatusChange = getLatestStatusChange(statusChanges);
290
+ const status = mapRecallStatusCodeToCallRecordingStatus(
291
+ latestStatusChange?.code
292
+ );
293
+ const recording = extractFirstRecording(bot);
294
+ return {
295
+ status,
296
+ failureReason: status === "FAILED" /* FAILED */ ? latestStatusChange?.code : void 0,
297
+ startedAt: normalizeRecallTimestamp(
298
+ recording?.startedAt ?? findStatusChangeTimestamp(statusChanges, "in_call_recording")
299
+ ),
300
+ endedAt: normalizeRecallTimestamp(
301
+ recording?.completedAt ?? findStatusChangeTimestamp(statusChanges, "call_ended")
302
+ ),
303
+ externalRecordingId: recording?.id,
304
+ isRecallRecordingDone: !(0, import_guards4.isUndefined)(recording?.completedAt) || statusChanges.some((statusChange) => statusChange.code === "done")
305
+ };
306
+ };
307
+ var extractStatusChanges = (bot) => {
308
+ if (!(0, import_guards4.isArray)(bot.status_changes)) {
309
+ return [];
310
+ }
311
+ return bot.status_changes.flatMap((statusChange) => {
312
+ const code = getString(asRecord(statusChange)?.code);
313
+ if ((0, import_guards4.isUndefined)(code)) {
314
+ return [];
315
+ }
316
+ return [{ code, createdAt: getString(asRecord(statusChange)?.created_at) }];
317
+ });
318
+ };
319
+ var getLatestStatusChange = (statusChanges) => statusChanges.reduce(
320
+ (latestStatusChange, statusChange) => {
321
+ if ((0, import_guards4.isUndefined)(latestStatusChange)) {
322
+ return statusChange;
323
+ }
324
+ const statusChangeTime = getStatusChangeTime(statusChange);
325
+ const latestStatusChangeTime = getStatusChangeTime(latestStatusChange);
326
+ if ((0, import_guards4.isUndefined)(statusChangeTime) && (0, import_guards4.isUndefined)(latestStatusChangeTime)) {
327
+ return statusChange;
328
+ }
329
+ if ((0, import_guards4.isUndefined)(statusChangeTime)) {
330
+ return latestStatusChange;
331
+ }
332
+ if ((0, import_guards4.isUndefined)(latestStatusChangeTime)) {
333
+ return statusChange;
334
+ }
335
+ return statusChangeTime >= latestStatusChangeTime ? statusChange : latestStatusChange;
336
+ },
337
+ void 0
338
+ );
339
+ var getStatusChangeTime = (statusChange) => {
340
+ const normalizedTimestamp = normalizeRecallTimestamp(statusChange.createdAt);
341
+ if ((0, import_guards4.isUndefined)(normalizedTimestamp)) {
342
+ return void 0;
343
+ }
344
+ return new Date(normalizedTimestamp).getTime();
345
+ };
346
+ var extractFirstRecording = (bot) => {
347
+ if (!(0, import_guards4.isArray)(bot.recordings)) {
348
+ return void 0;
349
+ }
350
+ const recording = asRecord(bot.recordings[0]);
351
+ if ((0, import_guards4.isUndefined)(recording)) {
352
+ return void 0;
353
+ }
354
+ return {
355
+ id: getString(recording.id),
356
+ startedAt: getString(recording.started_at),
357
+ completedAt: getString(recording.completed_at)
358
+ };
359
+ };
360
+ var findStatusChangeTimestamp = (statusChanges, code) => statusChanges.find((statusChange) => statusChange.code === code)?.createdAt;
361
+
362
+ // src/logic-functions/data/fetch-all-nodes.util.ts
363
+ var import_guards5 = __toESM(require_build());
364
+ var fetchAllNodes = async (fetchPage) => {
365
+ const nodes = [];
366
+ let hasNextPage = true;
367
+ let afterCursor;
368
+ while (hasNextPage) {
369
+ const connection = await fetchPage(afterCursor);
370
+ if ((0, import_guards5.isUndefined)(connection)) {
371
+ throw new Error("Pagination query returned no connection");
372
+ }
373
+ for (const edge of connection.edges ?? []) {
374
+ nodes.push(edge.node);
375
+ }
376
+ hasNextPage = connection.pageInfo?.hasNextPage === true;
377
+ const endCursor = connection.pageInfo?.endCursor;
378
+ if (hasNextPage && !(0, import_guards5.isString)(endCursor)) {
379
+ throw new Error(
380
+ "Inconsistent pagination state: hasNextPage is true without an endCursor"
381
+ );
382
+ }
383
+ afterCursor = (0, import_guards5.isString)(endCursor) ? endCursor : void 0;
384
+ }
385
+ return nodes;
386
+ };
387
+
388
+ // src/logic-functions/recall-api/get-recall-api-config.util.ts
389
+ var import_guards6 = __toESM(require_build());
390
+
391
+ // src/logic-functions/constants/call-recorder-name-env-var-name.ts
392
+ var CALL_RECORDER_NAME_ENV_VAR_NAME = "CALL_RECORDER_NAME";
393
+
394
+ // src/logic-functions/constants/default-call-recorder-name.ts
395
+ var DEFAULT_CALL_RECORDER_NAME = "Twenty.com";
396
+
397
+ // src/logic-functions/constants/default-recall-region.ts
398
+ var DEFAULT_RECALL_REGION = "eu-central-1";
399
+
400
+ // src/logic-functions/constants/recall-api-key-env-var-name.ts
401
+ var RECALL_API_KEY_ENV_VAR_NAME = "RECALL_API_KEY";
402
+
403
+ // src/logic-functions/constants/recall-region-env-var-name.ts
404
+ var RECALL_REGION_ENV_VAR_NAME = "RECALL_REGION";
405
+
406
+ // src/logic-functions/utils/get-application-variable-value.util.ts
407
+ var getApplicationVariableValue = (key) => process.env[key];
408
+
409
+ // src/logic-functions/recall-api/get-recall-api-config.util.ts
410
+ var getRecallApiConfig = () => {
411
+ const apiKey = normalizeOptionalString(
412
+ getApplicationVariableValue(RECALL_API_KEY_ENV_VAR_NAME)
413
+ );
414
+ if ((0, import_guards6.isUndefined)(apiKey)) {
415
+ return {
416
+ success: false,
417
+ error: "RECALL_API_KEY server variable is not set. A server admin must set it on the Call Recorder application registration before scheduling bots."
418
+ };
419
+ }
420
+ const region = normalizeOptionalString(
421
+ getApplicationVariableValue(RECALL_REGION_ENV_VAR_NAME)
422
+ ) ?? DEFAULT_RECALL_REGION;
423
+ const botName = normalizeOptionalString(
424
+ getApplicationVariableValue(CALL_RECORDER_NAME_ENV_VAR_NAME)
425
+ ) ?? DEFAULT_CALL_RECORDER_NAME;
426
+ return {
427
+ success: true,
428
+ config: {
429
+ apiKey,
430
+ baseUrl: `https://${region}.recall.ai/api/v1`,
431
+ botName
432
+ }
433
+ };
434
+ };
435
+ var normalizeOptionalString = (value) => isNonEmptyString(value) ? value.trim() : void 0;
436
+
437
+ // src/logic-functions/recall-api/recall-bot-api-request.util.ts
438
+ var import_guards7 = __toESM(require_build());
439
+
440
+ // src/logic-functions/constants/recall-api-max-attempts.ts
441
+ var RECALL_API_MAX_ATTEMPTS = 3;
442
+
443
+ // src/logic-functions/constants/recall-api-retry-delay-ms.ts
444
+ var RECALL_API_RETRY_DELAY_MS = 500;
445
+
446
+ // src/logic-functions/recall-api/recall-bot-api-request.util.ts
447
+ var recallBotApiRequest = async (requestArgs) => {
448
+ const maxAttempts = requestArgs.maxAttempts ?? RECALL_API_MAX_ATTEMPTS;
449
+ for (let attemptNumber = 1; ; attemptNumber++) {
450
+ const { result, isRetryable } = await performRecallBotApiRequestAttempt(requestArgs);
451
+ if (!isRetryable || attemptNumber >= maxAttempts) {
452
+ return result;
453
+ }
454
+ await sleep(RECALL_API_RETRY_DELAY_MS * attemptNumber);
455
+ }
456
+ };
457
+ var performRecallBotApiRequestAttempt = async ({
458
+ config,
459
+ path,
460
+ method,
461
+ body,
462
+ allowNotFound = false
463
+ }) => {
464
+ let response;
465
+ try {
466
+ response = await fetch(`${config.baseUrl}${path}`, {
467
+ method,
468
+ headers: {
469
+ Authorization: buildRecallApiAuthorizationHeader(config.apiKey),
470
+ ...(0, import_guards7.isUndefined)(body) ? {} : { "Content-Type": "application/json" }
471
+ },
472
+ ...(0, import_guards7.isUndefined)(body) ? {} : { body: JSON.stringify(body) }
473
+ });
474
+ } catch (error) {
475
+ return {
476
+ isRetryable: true,
477
+ result: {
478
+ ok: false,
479
+ status: null,
480
+ errorMessage: `Recall API request failed: ${error instanceof Error ? error.message : String(error)}`
481
+ }
482
+ };
483
+ }
484
+ if (allowNotFound && response.status === 404) {
485
+ return {
486
+ isRetryable: false,
487
+ result: {
488
+ ok: true,
489
+ status: response.status,
490
+ data: void 0
491
+ }
492
+ };
493
+ }
494
+ if (response.status === 204) {
495
+ return {
496
+ isRetryable: false,
497
+ result: {
498
+ ok: true,
499
+ status: response.status,
500
+ data: void 0
501
+ }
502
+ };
503
+ }
504
+ if (!response.ok) {
505
+ return {
506
+ isRetryable: isRetryableRecallApiStatus(response.status),
507
+ result: {
508
+ ok: false,
509
+ status: response.status,
510
+ errorMessage: await extractRecallApiErrorMessage(response)
511
+ }
512
+ };
513
+ }
514
+ try {
515
+ return {
516
+ isRetryable: false,
517
+ result: {
518
+ ok: true,
519
+ status: response.status,
520
+ data: await response.json()
521
+ }
522
+ };
523
+ } catch (error) {
524
+ return {
525
+ isRetryable: false,
526
+ result: {
527
+ ok: false,
528
+ status: response.status,
529
+ errorMessage: `Recall API returned a non-JSON response: ${error instanceof Error ? error.message : String(error)}`
530
+ }
531
+ };
532
+ }
533
+ };
534
+ var isRetryableRecallApiStatus = (status) => status === 429 || status >= 500;
535
+ var sleep = (delayMs) => new Promise((resolve) => {
536
+ setTimeout(resolve, delayMs);
537
+ });
538
+ var buildRecallApiAuthorizationHeader = (apiKey) => {
539
+ const trimmedApiKey = apiKey.trim();
540
+ return trimmedApiKey.toLowerCase().startsWith("token ") ? trimmedApiKey : `Token ${trimmedApiKey}`;
541
+ };
542
+ var extractRecallApiErrorMessage = async (response) => {
543
+ const fallback = `Recall API responded with HTTP ${response.status}`;
544
+ try {
545
+ const body = await response.json();
546
+ return `${fallback}: ${JSON.stringify(body)}`;
547
+ } catch {
548
+ return fallback;
549
+ }
550
+ };
551
+
552
+ // src/logic-functions/recall-api/get-recall-bot.util.ts
553
+ var getRecallBot = async ({
554
+ externalBotId
555
+ }) => {
556
+ const configResult = getRecallApiConfig();
557
+ if (!configResult.success) {
558
+ return { ok: false, status: null, errorMessage: configResult.error };
559
+ }
560
+ const result = await recallBotApiRequest({
561
+ config: configResult.config,
562
+ path: `/bot/${externalBotId}/`,
563
+ method: "GET"
564
+ });
565
+ if (!result.ok) {
566
+ return result;
567
+ }
568
+ const bot = asRecord(result.data);
569
+ if (bot === void 0) {
570
+ return {
571
+ ok: false,
572
+ status: result.status,
573
+ errorMessage: "Recall API returned an empty bot response"
574
+ };
575
+ }
576
+ return { ok: true, bot };
577
+ };
578
+
579
+ // src/logic-functions/flows/ingest-call-recording-media.util.ts
580
+ var import_guards8 = __toESM(require_build());
581
+ import { MetadataApiClient } from "twenty-client-sdk/metadata";
582
+
583
+ // src/constants/call-recording-audio-field-universal-identifier.ts
584
+ var CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER = "2eafc2d0-8fec-430c-a939-65ca5fbc0f08";
585
+
586
+ // src/constants/call-recording-video-field-universal-identifier.ts
587
+ var CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER = "bb9523d3-457e-4f4b-8c79-27a77afb87da";
588
+
589
+ // src/logic-functions/utils/get-record-at-path.util.ts
590
+ var getRecordAtPath = (record, path) => path.reduce(
591
+ (currentValue, pathPart) => asRecord(currentValue)?.[pathPart],
592
+ record
593
+ );
594
+
595
+ // src/logic-functions/recall-api/extract-recall-media-urls.util.ts
596
+ var extractRecallMediaUrls = (recording) => {
597
+ const mediaShortcuts = asRecord(recording.media_shortcuts);
598
+ return {
599
+ videoUrl: extractArtifactDownloadUrl(mediaShortcuts, "video_mixed"),
600
+ audioUrl: extractArtifactDownloadUrl(mediaShortcuts, "audio_mixed")
601
+ };
602
+ };
603
+ var extractArtifactDownloadUrl = (mediaShortcuts, artifactKey) => getString(getRecordAtPath(mediaShortcuts, [artifactKey, "download_url"])) ?? getString(
604
+ getRecordAtPath(mediaShortcuts, [artifactKey, "data", "download_url"])
605
+ );
606
+
607
+ // src/logic-functions/recall-api/get-recall-recording.util.ts
608
+ var getRecallRecording = async ({
609
+ externalRecordingId
610
+ }) => {
611
+ const configResult = getRecallApiConfig();
612
+ if (!configResult.success) {
613
+ return { ok: false, status: null, errorMessage: configResult.error };
614
+ }
615
+ const result = await recallBotApiRequest({
616
+ config: configResult.config,
617
+ path: `/recording/${externalRecordingId}/`,
618
+ method: "GET"
619
+ });
620
+ if (!result.ok) {
621
+ return result;
622
+ }
623
+ return { ok: true, recording: result.data ?? {} };
624
+ };
625
+
626
+ // src/logic-functions/flows/ingest-call-recording-media.util.ts
627
+ var MEDIA_DOWNLOAD_TIMEOUT_MS = 12e4;
628
+ var ingestCallRecordingMedia = async ({
629
+ callRecordingId,
630
+ externalRecordingId,
631
+ hasAudio,
632
+ hasVideo
633
+ }) => {
634
+ if (hasAudio && hasVideo) {
635
+ return {};
636
+ }
637
+ const recordingResult = await getRecallRecording({ externalRecordingId });
638
+ if (!recordingResult.ok) {
639
+ console.warn(
640
+ `[call-recorder] failed to fetch Recall recording ${externalRecordingId} while ingesting media for call recording ${callRecordingId}: ${recordingResult.errorMessage}`
641
+ );
642
+ return {};
643
+ }
644
+ const mediaUrls = extractRecallMediaUrls(recordingResult.recording);
645
+ const metadataClient = new MetadataApiClient();
646
+ const updateFields = {};
647
+ if (!hasVideo && !(0, import_guards8.isUndefined)(mediaUrls.videoUrl)) {
648
+ const video = await ingestMediaArtifact({
649
+ callRecordingId,
650
+ metadataClient,
651
+ url: mediaUrls.videoUrl,
652
+ fileName: "video.mp4",
653
+ fieldMetadataUniversalIdentifier: CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER
654
+ });
655
+ if (!(0, import_guards8.isUndefined)(video)) {
656
+ updateFields.video = video;
657
+ }
658
+ }
659
+ if (!hasAudio && !(0, import_guards8.isUndefined)(mediaUrls.audioUrl)) {
660
+ const audio = await ingestMediaArtifact({
661
+ callRecordingId,
662
+ metadataClient,
663
+ url: mediaUrls.audioUrl,
664
+ fileName: "audio.mp3",
665
+ fieldMetadataUniversalIdentifier: CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER
666
+ });
667
+ if (!(0, import_guards8.isUndefined)(audio)) {
668
+ updateFields.audio = audio;
669
+ }
670
+ }
671
+ return updateFields;
672
+ };
673
+ var ingestMediaArtifact = async ({
674
+ callRecordingId,
675
+ metadataClient,
676
+ url,
677
+ fileName,
678
+ fieldMetadataUniversalIdentifier
679
+ }) => {
680
+ try {
681
+ const { buffer, contentType } = await downloadMediaFile(url);
682
+ const uploadedFile = await metadataClient.uploadFile(
683
+ buffer,
684
+ fileName,
685
+ contentType,
686
+ fieldMetadataUniversalIdentifier
687
+ );
688
+ return [{ fileId: uploadedFile.id, label: fileName }];
689
+ } catch (error) {
690
+ console.warn(
691
+ `[call-recorder] failed to ingest ${fileName} for call recording ${callRecordingId}: ${error instanceof Error ? error.message : String(error)}`
692
+ );
693
+ return void 0;
694
+ }
695
+ };
696
+ var downloadMediaFile = async (url) => {
697
+ const response = await fetch(url, {
698
+ signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS)
699
+ });
700
+ if (!response.ok) {
701
+ throw new Error(`download failed with status ${response.status}`);
702
+ }
703
+ return {
704
+ buffer: Buffer.from(await response.arrayBuffer()),
705
+ contentType: response.headers.get("content-type") ?? "application/octet-stream"
706
+ };
707
+ };
708
+
709
+ // src/logic-functions/domain/is-call-recording-status-downgrade.util.ts
710
+ var import_guards9 = __toESM(require_build());
711
+ var CALL_RECORDING_STATUS_PROGRESSION = {
712
+ ["SCHEDULED" /* SCHEDULED */]: 0,
713
+ ["JOINING" /* JOINING */]: 1,
714
+ ["RECORDING" /* RECORDING */]: 2,
715
+ ["PROCESSING" /* PROCESSING */]: 3,
716
+ ["FAILED" /* FAILED */]: 4,
717
+ ["COMPLETED" /* COMPLETED */]: 5
718
+ };
719
+ var getCallRecordingStatusRank = (status) => status in CALL_RECORDING_STATUS_PROGRESSION ? CALL_RECORDING_STATUS_PROGRESSION[status] : void 0;
720
+ var isCallRecordingStatusDowngrade = ({
721
+ fromStatus,
722
+ toStatus
723
+ }) => {
724
+ const fromRank = (0, import_guards9.isUndefined)(fromStatus) ? void 0 : getCallRecordingStatusRank(fromStatus);
725
+ const toRank = getCallRecordingStatusRank(toStatus);
726
+ if ((0, import_guards9.isUndefined)(fromRank) || (0, import_guards9.isUndefined)(toRank)) {
727
+ return false;
728
+ }
729
+ return toRank < fromRank;
730
+ };
731
+
732
+ // src/logic-functions/domain/parse-transcript-marker.util.ts
733
+ var import_guards10 = __toESM(require_build());
734
+ var parseTranscriptMarker = (transcript) => {
735
+ const candidate = asRecord(transcript);
736
+ if ((0, import_guards10.isUndefined)(candidate)) {
737
+ return void 0;
738
+ }
739
+ if (candidate.status !== "PENDING" && candidate.status !== "FAILED") {
740
+ return void 0;
741
+ }
742
+ return {
743
+ recallTranscriptId: (0, import_guards10.isString)(candidate.recallTranscriptId) ? candidate.recallTranscriptId : null,
744
+ status: candidate.status,
745
+ ...(0, import_guards10.isString)(candidate.requestedAt) ? { requestedAt: candidate.requestedAt } : {},
746
+ ...(0, import_guards10.isString)(candidate.subCode) ? { subCode: candidate.subCode } : {}
747
+ };
748
+ };
749
+
750
+ // src/logic-functions/data/complete-call-recording-ingestion.util.ts
751
+ var completeCallRecordingIngestion = async (client, { id }) => {
752
+ const result = await client.mutation({
753
+ updateCallRecordings: {
754
+ __args: {
755
+ filter: {
756
+ id: { eq: id },
757
+ status: { in: NON_TERMINAL_CALL_RECORDING_STATUSES }
758
+ },
759
+ data: { status: "COMPLETED" /* COMPLETED */ }
760
+ },
761
+ id: true
762
+ }
763
+ });
764
+ return (result.updateCallRecordings ?? []).length > 0;
765
+ };
766
+
767
+ // src/logic-functions/flows/charge-completed-call-recording.util.ts
768
+ var import_guards12 = __toESM(require_build());
769
+
770
+ // node_modules/twenty-sdk/dist/billing/index.mjs
771
+ var e = "TWENTY_API_URL";
772
+ var t = "TWENTY_APP_ACCESS_TOKEN";
773
+ var n = 5e3;
774
+ var r = async ({ creditsUsedMicro: r2, operationType: i, quantity: a = 1, resourceContext: o }) => {
775
+ let s = process.env[e], c = process.env[t];
776
+ if (!(!s || !c)) try {
777
+ let e2 = await fetch(`${s.replace(/\/$/, "")}/app/billing/charge`, {
778
+ method: "POST",
779
+ headers: {
780
+ Authorization: `Bearer ${c}`,
781
+ "Content-Type": "application/json"
782
+ },
783
+ body: JSON.stringify({
784
+ creditsUsedMicro: r2,
785
+ quantity: a,
786
+ operationType: i,
787
+ resourceContext: o
788
+ }),
789
+ signal: AbortSignal.timeout(n)
790
+ });
791
+ if (!e2.ok) {
792
+ let t2 = await e2.text().catch(() => "");
793
+ console.error(`chargeCredits: ${e2.status} ${e2.statusText}: ${t2}`);
794
+ }
795
+ } catch (e2) {
796
+ console.error(`chargeCredits: ${e2 instanceof Error ? e2.message : String(e2)}`);
797
+ }
798
+ };
799
+
800
+ // src/logic-functions/domain/compute-call-recording-charge.util.ts
801
+ var import_guards11 = __toESM(require_build());
802
+
803
+ // src/logic-functions/constants/call-recording-micro-credits-per-hour.ts
804
+ var CALL_RECORDING_MICRO_CREDITS_PER_HOUR = 1e6;
805
+
806
+ // src/logic-functions/constants/milliseconds-per-minute.ts
807
+ var MILLISECONDS_PER_MINUTE = 6e4;
808
+
809
+ // src/logic-functions/domain/compute-call-recording-charge.util.ts
810
+ var MILLISECONDS_PER_HOUR = 36e5;
811
+ var computeCallRecordingCharge = ({
812
+ startedAt,
813
+ endedAt
814
+ }) => {
815
+ if ((0, import_guards11.isUndefined)(startedAt) || (0, import_guards11.isUndefined)(endedAt)) {
816
+ return void 0;
817
+ }
818
+ const durationMilliseconds = new Date(endedAt).getTime() - new Date(startedAt).getTime();
819
+ if (!Number.isFinite(durationMilliseconds) || durationMilliseconds <= 0) {
820
+ return void 0;
821
+ }
822
+ return {
823
+ creditsUsedMicro: Math.round(
824
+ durationMilliseconds / MILLISECONDS_PER_HOUR * CALL_RECORDING_MICRO_CREDITS_PER_HOUR
825
+ ),
826
+ quantityMinutes: Math.max(
827
+ 1,
828
+ Math.round(durationMilliseconds / MILLISECONDS_PER_MINUTE)
829
+ )
830
+ };
831
+ };
832
+
833
+ // src/logic-functions/flows/charge-completed-call-recording.util.ts
834
+ var chargeCompletedCallRecording = async ({
835
+ callRecordingId,
836
+ startedAt,
837
+ endedAt
838
+ }) => {
839
+ const charge = computeCallRecordingCharge({ startedAt, endedAt });
840
+ if ((0, import_guards12.isUndefined)(charge)) {
841
+ console.warn(
842
+ `[call-recorder] call recording ${callRecordingId} completed without usable recording timestamps; it will not be billed`
843
+ );
844
+ return;
845
+ }
846
+ await r({
847
+ creditsUsedMicro: charge.creditsUsedMicro,
848
+ quantity: charge.quantityMinutes,
849
+ operationType: "CALL_RECORDING",
850
+ resourceContext: "recall"
851
+ });
852
+ };
853
+
854
+ // src/logic-functions/flows/complete-and-charge-call-recording.util.ts
855
+ var completeAndChargeCallRecording = async (client, {
856
+ id,
857
+ startedAt,
858
+ endedAt
859
+ }) => {
860
+ const claimed = await completeCallRecordingIngestion(client, { id });
861
+ if (claimed) {
862
+ await chargeCompletedCallRecording({
863
+ callRecordingId: id,
864
+ startedAt,
865
+ endedAt
866
+ });
867
+ }
868
+ return claimed;
869
+ };
870
+
871
+ // src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts
872
+ var import_guards13 = __toESM(require_build());
873
+ var isCallRecordingIngestionComplete = ({
874
+ transcript,
875
+ audio,
876
+ video
877
+ }) => !(0, import_guards13.isNull)(transcript) && !(0, import_guards13.isUndefined)(transcript) && (0, import_guards13.isUndefined)(parseTranscriptMarker(transcript)) && (0, import_guards13.isNonEmptyArray)(audio) && (0, import_guards13.isNonEmptyArray)(video);
878
+
879
+ // src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts
880
+ var shouldCompleteCallRecordingIngestion = ({
881
+ current,
882
+ updateData
883
+ }) => current.status !== "COMPLETED" /* COMPLETED */ && current.status !== "FAILED" /* FAILED */ && updateData.status !== "FAILED" /* FAILED */ && computeCallRecordingCharge({
884
+ startedAt: updateData.startedAt ?? current.startedAt,
885
+ endedAt: updateData.endedAt ?? current.endedAt
886
+ }) !== void 0 && isCallRecordingIngestionComplete({
887
+ transcript: updateData.transcript ?? current.transcript,
888
+ audio: updateData.audio ?? current.audio,
889
+ video: updateData.video ?? current.video
890
+ });
891
+
892
+ // src/logic-functions/data/update-call-recording.util.ts
893
+ var updateCallRecording = async (client, {
894
+ id,
895
+ data
896
+ }) => {
897
+ await client.mutation({
898
+ updateCallRecording: {
899
+ __args: {
900
+ id,
901
+ data
902
+ },
903
+ id: true
904
+ }
905
+ });
906
+ };
907
+
908
+ // src/logic-functions/flows/persist-call-recording-progress.util.ts
909
+ var persistCallRecordingProgress = async (client, {
910
+ id,
911
+ current,
912
+ updateData
913
+ }) => {
914
+ const completesIngestion = shouldCompleteCallRecordingIngestion({
915
+ current,
916
+ updateData
917
+ });
918
+ if (!completesIngestion) {
919
+ await updateCallRecording(client, { id, data: updateData });
920
+ return { completesIngestion: false };
921
+ }
922
+ const nonStatusUpdate = { ...updateData };
923
+ delete nonStatusUpdate.status;
924
+ delete nonStatusUpdate.callRecorderFailureReason;
925
+ if (Object.keys(nonStatusUpdate).length > 0) {
926
+ await updateCallRecording(client, { id, data: nonStatusUpdate });
927
+ }
928
+ await completeAndChargeCallRecording(client, {
929
+ id,
930
+ startedAt: updateData.startedAt ?? current.startedAt,
931
+ endedAt: updateData.endedAt ?? current.endedAt
932
+ });
933
+ return { completesIngestion: true };
934
+ };
935
+
936
+ // src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts
937
+ var import_guards19 = __toESM(require_build());
938
+
939
+ // src/logic-functions/domain/build-failed-transcript-marker.util.ts
940
+ var buildFailedTranscriptMarker = ({
941
+ recallTranscriptId,
942
+ subCode
943
+ }) => ({
944
+ recallTranscriptId,
945
+ status: "FAILED",
946
+ subCode
947
+ });
948
+
949
+ // src/logic-functions/domain/build-pending-transcript-marker.util.ts
950
+ var buildPendingTranscriptMarker = ({
951
+ recallTranscriptId,
952
+ requestedAt
953
+ }) => ({
954
+ recallTranscriptId,
955
+ status: "PENDING",
956
+ requestedAt
957
+ });
958
+
959
+ // src/logic-functions/domain/build-transcript-failure-reason.util.ts
960
+ var import_guards14 = __toESM(require_build());
961
+ var buildTranscriptFailureReason = (subCode) => {
962
+ return (0, import_guards14.isNull)(subCode) ? "transcript_failed" : `transcript_failed:${subCode}`;
963
+ };
964
+
965
+ // src/logic-functions/recall-api/create-async-recall-transcript.util.ts
966
+ var import_guards15 = __toESM(require_build());
967
+ var createAsyncRecallTranscript = async ({
968
+ externalRecordingId
969
+ }) => {
970
+ const configResult = getRecallApiConfig();
971
+ if (!configResult.success) {
972
+ return { ok: false, status: null, errorMessage: configResult.error };
973
+ }
974
+ const result = await recallBotApiRequest({
975
+ config: configResult.config,
976
+ path: `/recording/${externalRecordingId}/create_transcript/`,
977
+ method: "POST",
978
+ body: {
979
+ provider: { recallai_async: { language_code: "auto" } },
980
+ diarization: { use_separate_streams_when_available: true }
981
+ },
982
+ maxAttempts: 1
983
+ });
984
+ if (!result.ok) {
985
+ return result;
986
+ }
987
+ if (!(0, import_guards15.isString)(result.data?.id)) {
988
+ return {
989
+ ok: false,
990
+ status: null,
991
+ errorMessage: "Recall API created a transcript but the response did not include a transcript id"
992
+ };
993
+ }
994
+ return { ok: true, transcriptId: result.data.id };
995
+ };
996
+
997
+ // src/logic-functions/recall-api/list-recall-transcripts.util.ts
998
+ var import_guards16 = __toESM(require_build());
999
+ var RECALL_TRANSCRIPT_LIST_MAX_PAGES = 10;
1000
+ var listRecallTranscripts = async ({
1001
+ externalRecordingId
1002
+ }) => {
1003
+ const configResult = getRecallApiConfig();
1004
+ if (!configResult.success) {
1005
+ return { ok: false, status: null, errorMessage: configResult.error };
1006
+ }
1007
+ const transcripts = [];
1008
+ let path = buildListRecallTranscriptsPath({
1009
+ externalRecordingId
1010
+ });
1011
+ for (let pageIndex = 0; !(0, import_guards16.isUndefined)(path) && pageIndex < RECALL_TRANSCRIPT_LIST_MAX_PAGES; pageIndex++) {
1012
+ const result = await recallBotApiRequest({
1013
+ config: configResult.config,
1014
+ path,
1015
+ method: "GET"
1016
+ });
1017
+ if (!result.ok) {
1018
+ return result;
1019
+ }
1020
+ const pageTranscripts = extractRecallTranscriptSummaries(result.data);
1021
+ if ((0, import_guards16.isUndefined)(pageTranscripts)) {
1022
+ return {
1023
+ ok: false,
1024
+ status: result.status,
1025
+ errorMessage: "Recall API returned malformed transcript list"
1026
+ };
1027
+ }
1028
+ transcripts.push(...pageTranscripts);
1029
+ path = extractNextPath(result.data, configResult.config.baseUrl);
1030
+ }
1031
+ if (!(0, import_guards16.isUndefined)(path)) {
1032
+ return {
1033
+ ok: false,
1034
+ status: null,
1035
+ errorMessage: `Recall transcript list exceeded ${RECALL_TRANSCRIPT_LIST_MAX_PAGES} pages`
1036
+ };
1037
+ }
1038
+ return { ok: true, transcripts };
1039
+ };
1040
+ var buildListRecallTranscriptsPath = ({
1041
+ externalRecordingId
1042
+ }) => {
1043
+ const searchParams = new URLSearchParams({
1044
+ recording_id: externalRecordingId
1045
+ });
1046
+ return `/transcript/?${searchParams.toString()}`;
1047
+ };
1048
+ var extractRecallTranscriptSummaries = (response) => {
1049
+ if (!(0, import_guards16.isArray)(response?.results)) {
1050
+ return void 0;
1051
+ }
1052
+ const transcripts = [];
1053
+ for (const result of response.results) {
1054
+ const transcript = extractRecallTranscriptSummary(result);
1055
+ if ((0, import_guards16.isUndefined)(transcript)) {
1056
+ return void 0;
1057
+ }
1058
+ transcripts.push(transcript);
1059
+ }
1060
+ return transcripts;
1061
+ };
1062
+ var extractRecallTranscriptSummary = (transcript) => {
1063
+ const transcriptRecord = asRecord(transcript);
1064
+ const transcriptId = getString(transcriptRecord?.id);
1065
+ if ((0, import_guards16.isUndefined)(transcriptRecord) || (0, import_guards16.isUndefined)(transcriptId)) {
1066
+ return void 0;
1067
+ }
1068
+ const status = asRecord(transcriptRecord.status);
1069
+ return {
1070
+ id: transcriptId,
1071
+ statusCode: getString(status?.code),
1072
+ statusSubCode: getString(status?.sub_code)
1073
+ };
1074
+ };
1075
+ var extractNextPath = (response, baseUrl) => {
1076
+ const nextPage = getString(response?.next);
1077
+ if ((0, import_guards16.isUndefined)(nextPage) || !nextPage.startsWith(baseUrl)) {
1078
+ return void 0;
1079
+ }
1080
+ return nextPage.slice(baseUrl.length);
1081
+ };
1082
+
1083
+ // src/logic-functions/flows/download-transcript.util.ts
1084
+ var import_guards18 = __toESM(require_build());
1085
+
1086
+ // src/logic-functions/recall-api/retrieve-recall-transcript.util.ts
1087
+ var import_guards17 = __toESM(require_build());
1088
+ var retrieveRecallTranscript = async ({
1089
+ transcriptId
1090
+ }) => {
1091
+ const configResult = getRecallApiConfig();
1092
+ if (!configResult.success) {
1093
+ return { ok: false, status: null, errorMessage: configResult.error };
1094
+ }
1095
+ const result = await recallBotApiRequest({
1096
+ config: configResult.config,
1097
+ path: `/transcript/${transcriptId}/`,
1098
+ method: "GET"
1099
+ });
1100
+ if (!result.ok) {
1101
+ return result;
1102
+ }
1103
+ const transcript = extractRecallTranscriptDetails(result.data);
1104
+ if (isMalformedRecallTranscriptDetails(transcript)) {
1105
+ return {
1106
+ ok: false,
1107
+ status: result.status,
1108
+ errorMessage: "Recall API returned malformed transcript details"
1109
+ };
1110
+ }
1111
+ return { ok: true, transcript };
1112
+ };
1113
+ var extractRecallTranscriptDetails = (response) => {
1114
+ const data = asRecord(response?.data);
1115
+ const status = asRecord(response?.status);
1116
+ return {
1117
+ downloadUrl: getString(data?.download_url),
1118
+ statusCode: getString(status?.code),
1119
+ statusSubCode: getString(status?.sub_code)
1120
+ };
1121
+ };
1122
+ var isMalformedRecallTranscriptDetails = ({
1123
+ downloadUrl,
1124
+ statusCode
1125
+ }) => (0, import_guards17.isUndefined)(downloadUrl) && (0, import_guards17.isUndefined)(statusCode) || (0, import_guards17.isUndefined)(downloadUrl) && statusCode === "done";
1126
+
1127
+ // src/logic-functions/flows/download-transcript.util.ts
1128
+ var TRANSCRIPT_DOWNLOAD_TIMEOUT_MS = 2e4;
1129
+ var downloadTranscript = async ({
1130
+ transcriptId
1131
+ }) => {
1132
+ const retrieveResult = await retrieveRecallTranscript({ transcriptId });
1133
+ if (!retrieveResult.ok) {
1134
+ return { outcome: "error", errorMessage: retrieveResult.errorMessage };
1135
+ }
1136
+ const { downloadUrl, statusCode, statusSubCode } = retrieveResult.transcript;
1137
+ if (!(0, import_guards18.isUndefined)(downloadUrl)) {
1138
+ return downloadTranscriptContent(downloadUrl);
1139
+ }
1140
+ if (statusCode === "error" || statusCode === "failed") {
1141
+ return { outcome: "failed", subCode: statusSubCode ?? null };
1142
+ }
1143
+ return { outcome: "pending" };
1144
+ };
1145
+ var downloadTranscriptContent = async (downloadUrl) => {
1146
+ try {
1147
+ const response = await fetch(downloadUrl, {
1148
+ signal: AbortSignal.timeout(TRANSCRIPT_DOWNLOAD_TIMEOUT_MS)
1149
+ });
1150
+ if (!response.ok) {
1151
+ console.warn(
1152
+ `[call-recorder] transcript download responded with HTTP ${response.status}`
1153
+ );
1154
+ return {
1155
+ outcome: "error",
1156
+ errorMessage: "transcript download failed"
1157
+ };
1158
+ }
1159
+ return { outcome: "filled", content: await response.json() };
1160
+ } catch (error) {
1161
+ console.warn(
1162
+ `[call-recorder] transcript download failed: ${error instanceof Error ? error.message : String(error)}`
1163
+ );
1164
+ return {
1165
+ outcome: "error",
1166
+ errorMessage: "transcript download failed"
1167
+ };
1168
+ }
1169
+ };
1170
+
1171
+ // src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts
1172
+ var reconcileCallRecordingTranscriptArtifact = async ({
1173
+ callRecordingId,
1174
+ currentStatus,
1175
+ externalRecordingId,
1176
+ requestedAt,
1177
+ transcript
1178
+ }) => {
1179
+ const existingTranscriptMarker = parseTranscriptMarker(transcript);
1180
+ if (!(0, import_guards19.isNull)(transcript) && !(0, import_guards19.isUndefined)(transcript) && (0, import_guards19.isUndefined)(existingTranscriptMarker)) {
1181
+ return buildEmptyTranscriptArtifactResult();
1182
+ }
1183
+ if (existingTranscriptMarker?.status === "FAILED") {
1184
+ return buildEmptyTranscriptArtifactResult();
1185
+ }
1186
+ const listResult = await listRecallTranscripts({ externalRecordingId });
1187
+ if (!listResult.ok) {
1188
+ console.warn(
1189
+ `[call-recorder] failed to list Recall transcripts for recording ${externalRecordingId}: ${listResult.errorMessage}`
1190
+ );
1191
+ return buildEmptyTranscriptArtifactResult();
1192
+ }
1193
+ const transcriptArtifact = selectRecallTranscriptArtifact(
1194
+ listResult.transcripts
1195
+ );
1196
+ const pendingTranscriptMarkerRecallTranscriptId = existingTranscriptMarker?.status === "PENDING" ? existingTranscriptMarker.recallTranscriptId ?? void 0 : void 0;
1197
+ const transcriptIdToDownload = transcriptArtifact?.id ?? pendingTranscriptMarkerRecallTranscriptId;
1198
+ if ((0, import_guards19.isUndefined)(transcriptArtifact) && (0, import_guards19.isUndefined)(pendingTranscriptMarkerRecallTranscriptId)) {
1199
+ const createResult = await createAsyncRecallTranscript({
1200
+ externalRecordingId
1201
+ });
1202
+ if (!createResult.ok) {
1203
+ console.warn(
1204
+ `[call-recorder] failed to request transcript for Recall recording ${externalRecordingId}: ${createResult.errorMessage}`
1205
+ );
1206
+ return buildEmptyTranscriptArtifactResult();
1207
+ }
1208
+ return {
1209
+ updateData: {
1210
+ transcript: buildPendingTranscriptMarker({
1211
+ recallTranscriptId: createResult.transcriptId,
1212
+ requestedAt
1213
+ })
1214
+ },
1215
+ requestedTranscript: true
1216
+ };
1217
+ }
1218
+ if (!(0, import_guards19.isUndefined)(transcriptArtifact) && (transcriptArtifact.statusCode === "failed" || transcriptArtifact.statusCode === "error")) {
1219
+ return {
1220
+ updateData: buildTranscriptFailureUpdate({
1221
+ currentStatus,
1222
+ transcriptId: transcriptArtifact.id,
1223
+ subCode: transcriptArtifact.statusSubCode ?? null
1224
+ }),
1225
+ requestedTranscript: false
1226
+ };
1227
+ }
1228
+ if (!(0, import_guards19.isUndefined)(transcriptArtifact) && transcriptArtifact.statusCode !== "done") {
1229
+ return buildEmptyTranscriptArtifactResult();
1230
+ }
1231
+ if ((0, import_guards19.isUndefined)(transcriptIdToDownload)) {
1232
+ return buildEmptyTranscriptArtifactResult();
1233
+ }
1234
+ const downloadResult = await downloadTranscript({
1235
+ transcriptId: transcriptIdToDownload
1236
+ });
1237
+ if (downloadResult.outcome === "filled") {
1238
+ return {
1239
+ updateData: {
1240
+ transcript: downloadResult.content
1241
+ },
1242
+ requestedTranscript: false
1243
+ };
1244
+ }
1245
+ if (downloadResult.outcome === "failed") {
1246
+ return {
1247
+ updateData: buildTranscriptFailureUpdate({
1248
+ currentStatus,
1249
+ transcriptId: transcriptIdToDownload,
1250
+ subCode: downloadResult.subCode
1251
+ }),
1252
+ requestedTranscript: false
1253
+ };
1254
+ }
1255
+ if (downloadResult.outcome === "error") {
1256
+ console.warn(
1257
+ `[call-recorder] could not fill transcript for call recording ${callRecordingId}: ${downloadResult.errorMessage}`
1258
+ );
1259
+ }
1260
+ return buildEmptyTranscriptArtifactResult();
1261
+ };
1262
+ var buildEmptyTranscriptArtifactResult = () => ({
1263
+ updateData: {},
1264
+ requestedTranscript: false
1265
+ });
1266
+ var selectRecallTranscriptArtifact = (transcripts) => transcripts.find((transcript) => transcript.statusCode !== "deleted");
1267
+ var buildTranscriptFailureUpdate = ({
1268
+ currentStatus,
1269
+ transcriptId,
1270
+ subCode
1271
+ }) => ({
1272
+ transcript: buildFailedTranscriptMarker({
1273
+ recallTranscriptId: transcriptId,
1274
+ subCode
1275
+ }),
1276
+ callRecorderFailureReason: buildTranscriptFailureReason(subCode),
1277
+ ...isCallRecordingStatusDowngrade({
1278
+ fromStatus: currentStatus,
1279
+ toStatus: "FAILED" /* FAILED */
1280
+ }) ? {} : { status: "FAILED" /* FAILED */ }
1281
+ });
1282
+
1283
+ // src/logic-functions/flows/converge-diverged-call-recordings.util.ts
1284
+ var CONVERGENCE_LOOKBACK_DAYS = 7;
1285
+ var convergeDivergedCallRecordings = async ({
1286
+ client,
1287
+ now
1288
+ }) => {
1289
+ const candidates = await fetchDivergedCallRecordingCandidates(client);
1290
+ const convergenceLowerBound = new Date(
1291
+ now.getTime() - CONVERGENCE_LOOKBACK_DAYS * 24 * 60 * 60 * 1e3
1292
+ );
1293
+ const result = {
1294
+ candidateCount: candidates.length,
1295
+ updatedCallRecordingIds: [],
1296
+ markedFailedCallRecordingIds: [],
1297
+ requestedTranscriptCallRecordingIds: [],
1298
+ unconvergeableCallRecordingIds: [],
1299
+ skippedNotStartedCallRecordingIds: []
1300
+ };
1301
+ for (const candidate of candidates) {
1302
+ if (isOutsideConvergenceBound(candidate, convergenceLowerBound)) {
1303
+ console.warn(
1304
+ `[call-recorder] call recording ${candidate.id} diverged but its meeting ended more than ${CONVERGENCE_LOOKBACK_DAYS} days ago; it will not converge automatically`
1305
+ );
1306
+ result.unconvergeableCallRecordingIds.push(candidate.id);
1307
+ continue;
1308
+ }
1309
+ if ((0, import_guards20.isUndefined)(candidate.externalBotId)) {
1310
+ console.warn(
1311
+ `[call-recorder] call recording ${candidate.id} diverged but has no Recall bot id; it will not converge automatically`
1312
+ );
1313
+ result.unconvergeableCallRecordingIds.push(candidate.id);
1314
+ continue;
1315
+ }
1316
+ if (isBeforeMeetingStart(candidate, now)) {
1317
+ result.skippedNotStartedCallRecordingIds.push(candidate.id);
1318
+ continue;
1319
+ }
1320
+ await convergeCallRecording({
1321
+ client,
1322
+ candidate,
1323
+ externalBotId: candidate.externalBotId,
1324
+ now,
1325
+ result
1326
+ });
1327
+ }
1328
+ return result;
1329
+ };
1330
+ var fetchDivergedCallRecordingCandidates = async (client) => {
1331
+ const filter = {
1332
+ or: [
1333
+ {
1334
+ recordingRequestStatus: { eq: "REQUESTED" /* REQUESTED */ },
1335
+ status: { in: NON_TERMINAL_CALL_RECORDING_STATUSES },
1336
+ externalBotId: { is: "NOT_NULL" }
1337
+ },
1338
+ {
1339
+ status: { eq: "COMPLETED" /* COMPLETED */ },
1340
+ or: [{ startedAt: { is: "NULL" } }, { endedAt: { is: "NULL" } }]
1341
+ }
1342
+ ]
1343
+ };
1344
+ const candidateNodes = await fetchAllNodes(
1345
+ async (afterCursor) => {
1346
+ const queryResult = await client.query({
1347
+ callRecordings: {
1348
+ __args: {
1349
+ filter,
1350
+ first: TWENTY_PAGE_SIZE,
1351
+ ...(0, import_guards20.isUndefined)(afterCursor) ? {} : { after: afterCursor }
1352
+ },
1353
+ pageInfo: {
1354
+ hasNextPage: true,
1355
+ endCursor: true
1356
+ },
1357
+ edges: {
1358
+ node: {
1359
+ id: true,
1360
+ status: true,
1361
+ startedAt: true,
1362
+ endedAt: true,
1363
+ externalBotId: true,
1364
+ externalRecordingId: true,
1365
+ transcript: true,
1366
+ audio: { fileId: true },
1367
+ video: { fileId: true },
1368
+ createdAt: true,
1369
+ calendarEvent: {
1370
+ startsAt: true,
1371
+ endsAt: true
1372
+ }
1373
+ }
1374
+ }
1375
+ }
1376
+ });
1377
+ return queryResult.callRecordings ?? void 0;
1378
+ }
1379
+ );
1380
+ return candidateNodes.map((node) => ({
1381
+ id: node.id,
1382
+ status: node.status ?? void 0,
1383
+ startedAt: node.startedAt ?? void 0,
1384
+ endedAt: node.endedAt ?? void 0,
1385
+ externalBotId: isNonEmptyString(node.externalBotId) ? node.externalBotId : void 0,
1386
+ externalRecordingId: isNonEmptyString(node.externalRecordingId) ? node.externalRecordingId : void 0,
1387
+ transcript: node.transcript ?? void 0,
1388
+ audio: node.audio ?? void 0,
1389
+ video: node.video ?? void 0,
1390
+ createdAt: node.createdAt ?? void 0,
1391
+ calendarEventStartsAt: node.calendarEvent?.startsAt ?? void 0,
1392
+ calendarEventEndsAt: node.calendarEvent?.endsAt ?? void 0
1393
+ }));
1394
+ };
1395
+ var isOutsideConvergenceBound = (candidate, convergenceLowerBound) => {
1396
+ const meetingEndReference = candidate.calendarEventEndsAt ?? candidate.createdAt;
1397
+ return !(0, import_guards20.isUndefined)(meetingEndReference) && new Date(meetingEndReference).getTime() < convergenceLowerBound.getTime();
1398
+ };
1399
+ var isBeforeMeetingStart = (candidate, now) => !(0, import_guards20.isUndefined)(candidate.calendarEventStartsAt) && new Date(candidate.calendarEventStartsAt).getTime() > now.getTime();
1400
+ var convergeCallRecording = async ({
1401
+ client,
1402
+ candidate,
1403
+ externalBotId,
1404
+ now,
1405
+ result
1406
+ }) => {
1407
+ const botResult = await getRecallBot({ externalBotId });
1408
+ if (!botResult.ok) {
1409
+ if (botResult.status === 404) {
1410
+ await markCallRecordingFailedAfterBotLoss({
1411
+ client,
1412
+ candidate,
1413
+ externalBotId,
1414
+ result
1415
+ });
1416
+ return;
1417
+ }
1418
+ console.warn(
1419
+ `[call-recorder] failed to fetch Recall bot ${externalBotId} for call recording ${candidate.id}: ${botResult.errorMessage}`
1420
+ );
1421
+ return;
1422
+ }
1423
+ const convergence = extractRecallBotConvergence(botResult.bot);
1424
+ const updateData = buildConvergenceFieldUpdates({ candidate, convergence });
1425
+ const externalRecordingId = candidate.externalRecordingId ?? convergence.externalRecordingId;
1426
+ if (convergence.isRecallRecordingDone && !(0, import_guards20.isUndefined)(externalRecordingId)) {
1427
+ const transcriptArtifactResult = await reconcileCallRecordingTranscriptArtifact({
1428
+ callRecordingId: candidate.id,
1429
+ currentStatus: candidate.status,
1430
+ externalRecordingId,
1431
+ requestedAt: now.toISOString(),
1432
+ transcript: candidate.transcript
1433
+ });
1434
+ Object.assign(updateData, transcriptArtifactResult.updateData);
1435
+ if (transcriptArtifactResult.requestedTranscript) {
1436
+ result.requestedTranscriptCallRecordingIds.push(candidate.id);
1437
+ }
1438
+ Object.assign(
1439
+ updateData,
1440
+ await ingestCallRecordingMedia({
1441
+ callRecordingId: candidate.id,
1442
+ externalRecordingId,
1443
+ hasAudio: (0, import_guards20.isNonEmptyArray)(candidate.audio),
1444
+ hasVideo: (0, import_guards20.isNonEmptyArray)(candidate.video)
1445
+ })
1446
+ );
1447
+ }
1448
+ const terminalArtifactGateFailureUpdate = buildTerminalArtifactGateFailureUpdate({
1449
+ candidate,
1450
+ convergence,
1451
+ externalRecordingId,
1452
+ updateData
1453
+ });
1454
+ if (!(0, import_guards20.isUndefined)(terminalArtifactGateFailureUpdate)) {
1455
+ Object.assign(updateData, terminalArtifactGateFailureUpdate);
1456
+ }
1457
+ const completesIngestion = shouldCompleteCallRecordingIngestion({
1458
+ current: candidate,
1459
+ updateData
1460
+ });
1461
+ if (Object.keys(updateData).length === 0 && !completesIngestion) {
1462
+ return;
1463
+ }
1464
+ await persistCallRecordingProgress(client, {
1465
+ id: candidate.id,
1466
+ current: candidate,
1467
+ updateData
1468
+ });
1469
+ result.updatedCallRecordingIds.push(candidate.id);
1470
+ };
1471
+ var buildConvergenceFieldUpdates = ({
1472
+ candidate,
1473
+ convergence
1474
+ }) => {
1475
+ const updateData = {};
1476
+ if (!(0, import_guards20.isUndefined)(convergence.status) && convergence.status !== candidate.status && !isCallRecordingStatusDowngrade({
1477
+ fromStatus: candidate.status,
1478
+ toStatus: convergence.status
1479
+ })) {
1480
+ updateData.status = convergence.status;
1481
+ if (convergence.status === "FAILED" /* FAILED */) {
1482
+ updateData.callRecorderFailureReason = convergence.failureReason ?? "recall_bot_failed";
1483
+ }
1484
+ }
1485
+ if ((0, import_guards20.isUndefined)(candidate.startedAt) && !(0, import_guards20.isUndefined)(convergence.startedAt)) {
1486
+ updateData.startedAt = convergence.startedAt;
1487
+ }
1488
+ if ((0, import_guards20.isUndefined)(candidate.endedAt) && !(0, import_guards20.isUndefined)(convergence.endedAt)) {
1489
+ updateData.endedAt = convergence.endedAt;
1490
+ }
1491
+ if ((0, import_guards20.isUndefined)(candidate.externalRecordingId) && !(0, import_guards20.isUndefined)(convergence.externalRecordingId)) {
1492
+ updateData.externalRecordingId = convergence.externalRecordingId;
1493
+ }
1494
+ return updateData;
1495
+ };
1496
+ var buildTerminalArtifactGateFailureUpdate = ({
1497
+ candidate,
1498
+ convergence,
1499
+ externalRecordingId,
1500
+ updateData
1501
+ }) => {
1502
+ if (candidate.status === "COMPLETED" /* COMPLETED */ || updateData.status === "FAILED" /* FAILED */ || !convergence.isRecallRecordingDone || !(0, import_guards20.isUndefined)(externalRecordingId) || hasRecordingArtifactPath({ candidate, updateData })) {
1503
+ return void 0;
1504
+ }
1505
+ return {
1506
+ status: "FAILED" /* FAILED */,
1507
+ callRecorderFailureReason: convergence.failureReason ?? "recording_artifacts_unavailable"
1508
+ };
1509
+ };
1510
+ var hasRecordingArtifactPath = ({
1511
+ candidate,
1512
+ updateData
1513
+ }) => {
1514
+ return (0, import_guards20.isNonEmptyArray)(updateData.audio ?? candidate.audio) || (0, import_guards20.isNonEmptyArray)(updateData.video ?? candidate.video) || hasReachableTranscript(updateData.transcript ?? candidate.transcript);
1515
+ };
1516
+ var hasReachableTranscript = (transcript) => {
1517
+ if ((0, import_guards20.isUndefined)(transcript)) {
1518
+ return false;
1519
+ }
1520
+ const marker = parseTranscriptMarker(transcript);
1521
+ return (0, import_guards20.isUndefined)(marker) || marker.status === "PENDING";
1522
+ };
1523
+ var markCallRecordingFailedAfterBotLoss = async ({
1524
+ client,
1525
+ candidate,
1526
+ externalBotId,
1527
+ result
1528
+ }) => {
1529
+ console.warn(
1530
+ `[call-recorder] Recall bot ${externalBotId} for call recording ${candidate.id} no longer exists; it will not converge automatically`
1531
+ );
1532
+ if (isCallRecordingStatusDowngrade({
1533
+ fromStatus: candidate.status,
1534
+ toStatus: "FAILED" /* FAILED */
1535
+ })) {
1536
+ result.unconvergeableCallRecordingIds.push(candidate.id);
1537
+ return;
1538
+ }
1539
+ await updateCallRecording(client, {
1540
+ id: candidate.id,
1541
+ data: {
1542
+ status: "FAILED" /* FAILED */,
1543
+ callRecorderFailureReason: "recall_bot_not_found"
1544
+ }
1545
+ });
1546
+ result.markedFailedCallRecordingIds.push(candidate.id);
1547
+ };
1548
+
1549
+ // src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts
1550
+ var import_guards28 = __toESM(require_build());
1551
+
1552
+ // src/logic-functions/flows/ensure-call-recorder.util.ts
1553
+ var import_guards25 = __toESM(require_build());
1554
+
1555
+ // src/logic-functions/domain/build-recall-routing-metadata.util.ts
1556
+ var buildRecallRoutingMetadata = ({
1557
+ callRecordingId,
1558
+ workspaceId
1559
+ }) => ({
1560
+ twentyWorkspaceId: workspaceId,
1561
+ twentyCallRecordingId: callRecordingId
1562
+ });
1563
+
1564
+ // src/logic-functions/constants/default-call-recorder-join-early-minutes.ts
1565
+ var DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES = 1;
1566
+
1567
+ // src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts
1568
+ var CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME = "CALL_RECORDER_JOIN_EARLY_MINUTES";
1569
+
1570
+ // src/logic-functions/domain/compute-recall-bot-join-at.util.ts
1571
+ var computeRecallBotJoinAt = (meetingStartsAt) => {
1572
+ const meetingStartTimeInMilliseconds = new Date(meetingStartsAt).getTime();
1573
+ if (Number.isNaN(meetingStartTimeInMilliseconds)) {
1574
+ return meetingStartsAt;
1575
+ }
1576
+ return new Date(
1577
+ meetingStartTimeInMilliseconds - getRecallBotJoinEarlyMinutes() * MILLISECONDS_PER_MINUTE
1578
+ ).toISOString();
1579
+ };
1580
+ var getRecallBotJoinEarlyMinutes = () => {
1581
+ const rawValue = getApplicationVariableValue(
1582
+ CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME
1583
+ );
1584
+ if (!isNonEmptyString(rawValue)) {
1585
+ return DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
1586
+ }
1587
+ const joinEarlyMinutes = Number(rawValue.trim());
1588
+ return Number.isInteger(joinEarlyMinutes) && joinEarlyMinutes >= 0 ? joinEarlyMinutes : DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
1589
+ };
1590
+
1591
+ // src/logic-functions/data/find-call-recordings-by-filter.util.ts
1592
+ var import_guards21 = __toESM(require_build());
1593
+ var findCallRecordingsByFilter = async (client, filter) => {
1594
+ const callRecordingNodes = await fetchAllNodes(
1595
+ async (afterCursor) => {
1596
+ const queryResult = await client.query({
1597
+ callRecordings: {
1598
+ __args: {
1599
+ filter,
1600
+ first: TWENTY_PAGE_SIZE,
1601
+ ...(0, import_guards21.isUndefined)(afterCursor) ? {} : { after: afterCursor }
1602
+ },
1603
+ pageInfo: {
1604
+ hasNextPage: true,
1605
+ endCursor: true
1606
+ },
1607
+ edges: {
1608
+ node: {
1609
+ id: true,
1610
+ title: true,
1611
+ status: true,
1612
+ recordingRequestStatus: true,
1613
+ startedAt: true,
1614
+ endedAt: true,
1615
+ calendarEventId: true,
1616
+ externalBotId: true,
1617
+ externalRecordingId: true,
1618
+ callRecorderFailureReason: true
1619
+ }
1620
+ }
1621
+ }
1622
+ });
1623
+ return queryResult.callRecordings;
1624
+ }
1625
+ );
1626
+ return callRecordingNodes.map((callRecording) => ({
1627
+ id: callRecording.id,
1628
+ title: callRecording.title ?? void 0,
1629
+ status: callRecording.status ?? void 0,
1630
+ recordingRequestStatus: normalizeCallRecordingRequestStatus(
1631
+ callRecording.recordingRequestStatus
1632
+ ),
1633
+ startedAt: callRecording.startedAt ?? void 0,
1634
+ endedAt: callRecording.endedAt ?? void 0,
1635
+ calendarEventId: callRecording.calendarEventId ?? void 0,
1636
+ externalBotId: normalizeOptionalString2(callRecording.externalBotId),
1637
+ externalRecordingId: normalizeOptionalString2(
1638
+ callRecording.externalRecordingId
1639
+ ),
1640
+ callRecorderFailureReason: normalizeOptionalString2(
1641
+ callRecording.callRecorderFailureReason
1642
+ )
1643
+ }));
1644
+ };
1645
+ var normalizeOptionalString2 = (value) => isNonEmptyString(value) ? value : void 0;
1646
+ var normalizeCallRecordingRequestStatus = (recordingRequestStatus) => {
1647
+ if (recordingRequestStatus === "REQUESTED" /* REQUESTED */) {
1648
+ return recordingRequestStatus;
1649
+ }
1650
+ if (recordingRequestStatus === "CANCELED" /* CANCELED */) {
1651
+ return recordingRequestStatus;
1652
+ }
1653
+ return void 0;
1654
+ };
1655
+
1656
+ // src/logic-functions/data/find-call-recordings-by-ids.util.ts
1657
+ var findCallRecordingsByIds = async (client, callRecordingIds) => {
1658
+ if (callRecordingIds.length === 0) {
1659
+ return [];
1660
+ }
1661
+ return findCallRecordingsByFilter(client, {
1662
+ id: { in: callRecordingIds }
1663
+ });
1664
+ };
1665
+
1666
+ // src/logic-functions/data/get-current-workspace-id.util.ts
1667
+ var import_guards22 = __toESM(require_build());
1668
+ var APP_ACCESS_TOKEN_ENV_VAR_NAME = "TWENTY_APP_ACCESS_TOKEN";
1669
+ var getCurrentWorkspaceId = () => {
1670
+ const accessToken = getString(process.env[APP_ACCESS_TOKEN_ENV_VAR_NAME]);
1671
+ if ((0, import_guards22.isUndefined)(accessToken)) {
1672
+ return void 0;
1673
+ }
1674
+ return getWorkspaceIdFromAccessToken(accessToken);
1675
+ };
1676
+ var getWorkspaceIdFromAccessToken = (accessToken) => {
1677
+ const encodedPayload = accessToken.split(".")[1];
1678
+ if ((0, import_guards22.isUndefined)(encodedPayload)) {
1679
+ return void 0;
1680
+ }
1681
+ try {
1682
+ const payload = asRecord(
1683
+ JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"))
1684
+ );
1685
+ return getString(payload?.workspaceId);
1686
+ } catch {
1687
+ return void 0;
1688
+ }
1689
+ };
1690
+
1691
+ // src/logic-functions/recall-api/schedule-recall-bot.util.ts
1692
+ var import_guards24 = __toESM(require_build());
1693
+
1694
+ // src/logic-functions/constants/recall-bot-automatic-leave.ts
1695
+ var import_guards23 = __toESM(require_build());
1696
+
1697
+ // src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts
1698
+ var CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS";
1699
+
1700
+ // src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts
1701
+ var CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS";
1702
+
1703
+ // src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts
1704
+ var CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS";
1705
+
1706
+ // src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts
1707
+ var RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS = 1;
1708
+
1709
+ // src/logic-functions/constants/recall-bot-automatic-leave.ts
1710
+ var getRecallBotAutomaticLeave = () => {
1711
+ const waitingRoomTimeoutSeconds = getOptionalPositiveIntegerVariable(
1712
+ CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME
1713
+ );
1714
+ const nooneJoinedTimeoutSeconds = getOptionalPositiveIntegerVariable(
1715
+ CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME
1716
+ );
1717
+ const everyoneLeftTimeoutSeconds = getOptionalPositiveIntegerVariable(
1718
+ CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME
1719
+ );
1720
+ const automaticLeave = {};
1721
+ if (!(0, import_guards23.isUndefined)(waitingRoomTimeoutSeconds)) {
1722
+ automaticLeave.waiting_room_timeout = waitingRoomTimeoutSeconds;
1723
+ }
1724
+ if (!(0, import_guards23.isUndefined)(nooneJoinedTimeoutSeconds)) {
1725
+ automaticLeave.noone_joined_timeout = nooneJoinedTimeoutSeconds;
1726
+ }
1727
+ if (!(0, import_guards23.isUndefined)(everyoneLeftTimeoutSeconds)) {
1728
+ automaticLeave.everyone_left_timeout = {
1729
+ timeout: everyoneLeftTimeoutSeconds,
1730
+ activate_after: RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS
1731
+ };
1732
+ }
1733
+ return Object.keys(automaticLeave).length === 0 ? void 0 : automaticLeave;
1734
+ };
1735
+ var getOptionalPositiveIntegerVariable = (variableName) => {
1736
+ const rawValue = normalizeOptionalString3(
1737
+ getApplicationVariableValue(variableName)
1738
+ );
1739
+ if ((0, import_guards23.isUndefined)(rawValue)) {
1740
+ return void 0;
1741
+ }
1742
+ const timeoutSeconds = Number(rawValue);
1743
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds <= 0) {
1744
+ return void 0;
1745
+ }
1746
+ return timeoutSeconds;
1747
+ };
1748
+ var normalizeOptionalString3 = (value) => isNonEmptyString(value) ? value.trim() : void 0;
1749
+
1750
+ // src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts
1751
+ var CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME = "CALL_RECORDER_RECORDING_RETENTION_HOURS";
1752
+
1753
+ // src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts
1754
+ var DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS = 166;
1755
+
1756
+ // src/logic-functions/constants/recall-bot-recording-config.ts
1757
+ var getRecallBotRecordingConfig = () => {
1758
+ const configuredRecordingRetentionHours = getApplicationVariableValue(
1759
+ CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME
1760
+ );
1761
+ const recordingRetentionHours = isNonEmptyString(
1762
+ configuredRecordingRetentionHours
1763
+ ) ? Number(configuredRecordingRetentionHours.trim()) : NaN;
1764
+ const resolvedRecordingRetentionHours = Number.isInteger(recordingRetentionHours) && recordingRetentionHours > 0 ? recordingRetentionHours : DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS;
1765
+ return {
1766
+ video_mixed_mp4: {},
1767
+ audio_mixed_mp3: {},
1768
+ retention: { type: "timed", hours: resolvedRecordingRetentionHours }
1769
+ };
1770
+ };
1771
+
1772
+ // src/logic-functions/recall-api/extract-recall-bot-id.util.ts
1773
+ var extractRecallBotId = (response) => getString(response?.id) ?? getString(response?.bot_id);
1774
+
1775
+ // src/logic-functions/recall-api/schedule-recall-bot.util.ts
1776
+ var scheduleRecallBot = async ({
1777
+ meetingUrl,
1778
+ joinAt,
1779
+ metadata
1780
+ }) => {
1781
+ const configResult = getRecallApiConfig();
1782
+ if (!configResult.success) {
1783
+ return { ok: false, status: null, errorMessage: configResult.error };
1784
+ }
1785
+ const automaticLeave = getRecallBotAutomaticLeave();
1786
+ const result = await recallBotApiRequest({
1787
+ config: configResult.config,
1788
+ path: "/bot/",
1789
+ method: "POST",
1790
+ body: {
1791
+ meeting_url: meetingUrl,
1792
+ join_at: joinAt,
1793
+ bot_name: configResult.config.botName,
1794
+ ...(0, import_guards24.isUndefined)(automaticLeave) ? {} : { automatic_leave: automaticLeave },
1795
+ recording_config: getRecallBotRecordingConfig(),
1796
+ metadata
1797
+ }
1798
+ });
1799
+ if (!result.ok) {
1800
+ return result;
1801
+ }
1802
+ const externalBotId = extractRecallBotId(result.data);
1803
+ if ((0, import_guards24.isUndefined)(externalBotId)) {
1804
+ return {
1805
+ ok: false,
1806
+ status: null,
1807
+ errorMessage: "Recall API created a bot but the response did not include a bot id"
1808
+ };
1809
+ }
1810
+ return {
1811
+ ok: true,
1812
+ externalBotId
1813
+ };
1814
+ };
1815
+
1816
+ // src/logic-functions/flows/ensure-call-recorder.util.ts
1817
+ var ensureCallRecorder = async (client, { callRecording, calendarEvent }) => {
1818
+ const meetingUrl = calendarEvent.conferenceLinkUrl;
1819
+ const meetingStartsAt = calendarEvent.startsAt;
1820
+ if ((0, import_guards25.isUndefined)(meetingUrl) || (0, import_guards25.isUndefined)(meetingStartsAt)) {
1821
+ return false;
1822
+ }
1823
+ const joinAt = computeRecallBotJoinAt(meetingStartsAt);
1824
+ const freshCallRecording = (await findCallRecordingsByIds(client, [callRecording.id]))[0];
1825
+ if ((0, import_guards25.isUndefined)(freshCallRecording) || freshCallRecording.recordingRequestStatus !== "REQUESTED" /* REQUESTED */ || !(0, import_guards25.isUndefined)(freshCallRecording.externalBotId)) {
1826
+ return false;
1827
+ }
1828
+ const workspaceId = getCurrentWorkspaceId();
1829
+ if ((0, import_guards25.isUndefined)(workspaceId)) {
1830
+ console.error(
1831
+ `[call-recorder] cannot schedule Recall bot for callRecording ${callRecording.id}: workspace id unavailable, the shared webhook could not be routed back`
1832
+ );
1833
+ return false;
1834
+ }
1835
+ const scheduleResult = await scheduleRecallBot({
1836
+ meetingUrl,
1837
+ joinAt,
1838
+ metadata: buildRecallRoutingMetadata({
1839
+ callRecordingId: callRecording.id,
1840
+ workspaceId
1841
+ })
1842
+ });
1843
+ if (!scheduleResult.ok) {
1844
+ console.warn(
1845
+ `[call-recorder] failed to schedule Recall bot for callRecording ${callRecording.id}: ${scheduleResult.errorMessage}`
1846
+ );
1847
+ return false;
1848
+ }
1849
+ await updateCallRecording(client, {
1850
+ id: callRecording.id,
1851
+ data: { externalBotId: scheduleResult.externalBotId }
1852
+ });
1853
+ return true;
1854
+ };
1855
+
1856
+ // src/logic-functions/data/fetch-calendar-events-by-filter.util.ts
1857
+ var import_guards26 = __toESM(require_build());
1858
+
1859
+ // src/logic-functions/constants/restricted-field-placeholder.ts
1860
+ var RESTRICTED_FIELD_PLACEHOLDER = "FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED";
1861
+
1862
+ // src/logic-functions/data/strip-restricted-field-value.util.ts
1863
+ var stripRestrictedFieldValue = (value) => value === RESTRICTED_FIELD_PLACEHOLDER ? void 0 : value;
1864
+
1865
+ // src/logic-functions/data/fetch-calendar-events-by-filter.util.ts
1866
+ var fetchCalendarEventsByFilter = async (client, filter) => {
1867
+ const calendarEventNodes = await fetchAllNodes(
1868
+ async (afterCursor) => {
1869
+ const queryResult = await client.query({
1870
+ calendarEvents: {
1871
+ __args: {
1872
+ filter,
1873
+ first: TWENTY_PAGE_SIZE,
1874
+ ...(0, import_guards26.isUndefined)(afterCursor) ? {} : { after: afterCursor }
1875
+ },
1876
+ pageInfo: {
1877
+ hasNextPage: true,
1878
+ endCursor: true
1879
+ },
1880
+ edges: {
1881
+ node: {
1882
+ id: true,
1883
+ title: true,
1884
+ isCanceled: true,
1885
+ startsAt: true,
1886
+ endsAt: true,
1887
+ iCalUid: true,
1888
+ conferenceLink: {
1889
+ primaryLinkUrl: true
1890
+ },
1891
+ callRecorderPreference: true
1892
+ }
1893
+ }
1894
+ }
1895
+ });
1896
+ return queryResult.calendarEvents;
1897
+ }
1898
+ );
1899
+ return calendarEventNodes.map((calendarEvent) => ({
1900
+ id: calendarEvent.id,
1901
+ title: stripRestrictedFieldValue(calendarEvent.title ?? void 0),
1902
+ isCanceled: calendarEvent.isCanceled ?? false,
1903
+ startsAt: calendarEvent.startsAt ?? void 0,
1904
+ endsAt: calendarEvent.endsAt ?? void 0,
1905
+ iCalUid: calendarEvent.iCalUid ?? void 0,
1906
+ conferenceLinkUrl: isNonEmptyString(
1907
+ calendarEvent.conferenceLink?.primaryLinkUrl
1908
+ ) ? calendarEvent.conferenceLink.primaryLinkUrl : void 0,
1909
+ callRecorderPreference: (0, import_guards26.isString)(calendarEvent.callRecorderPreference) ? calendarEvent.callRecorderPreference : void 0
1910
+ }));
1911
+ };
1912
+
1913
+ // src/logic-functions/utils/get-unique-sorted-ids.util.ts
1914
+ var import_guards27 = __toESM(require_build());
1915
+ var getUniqueSortedIds = (ids) => [...new Set(ids.filter(import_guards27.isString))].sort(
1916
+ (firstId, secondId) => firstId.localeCompare(secondId)
1917
+ );
1918
+
1919
+ // src/logic-functions/data/fetch-calendar-events-by-ids.util.ts
1920
+ var fetchCalendarEventsByIds = async (client, calendarEventIds) => {
1921
+ const uniqueCalendarEventIds = getUniqueSortedIds(calendarEventIds);
1922
+ if (uniqueCalendarEventIds.length === 0) {
1923
+ return [];
1924
+ }
1925
+ return fetchCalendarEventsByFilter(client, {
1926
+ id: { in: uniqueCalendarEventIds }
1927
+ });
1928
+ };
1929
+
1930
+ // src/logic-functions/data/find-open-scheduled-call-recordings.util.ts
1931
+ var findOpenScheduledCallRecordings = async (client) => findCallRecordingsByFilter(client, {
1932
+ recordingRequestStatus: { eq: "REQUESTED" /* REQUESTED */ },
1933
+ status: { eq: "SCHEDULED" /* SCHEDULED */ }
1934
+ });
1935
+
1936
+ // src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts
1937
+ var healCallRecordingsMissingBot = async ({
1938
+ client,
1939
+ now
1940
+ }) => {
1941
+ const botlessCallRecordings = (await findOpenScheduledCallRecordings(client)).filter((callRecording) => (0, import_guards28.isUndefined)(callRecording.externalBotId));
1942
+ if (botlessCallRecordings.length === 0) {
1943
+ return { scheduledCallRecordingIds: [] };
1944
+ }
1945
+ const calendarEventsById = new Map(
1946
+ (await fetchCalendarEventsByIds(
1947
+ client,
1948
+ getUniqueSortedIds(
1949
+ botlessCallRecordings.map(
1950
+ (callRecording) => callRecording.calendarEventId
1951
+ )
1952
+ )
1953
+ )).map((calendarEvent) => [calendarEvent.id, calendarEvent])
1954
+ );
1955
+ const scheduledCallRecordingIds = [];
1956
+ for (const callRecording of botlessCallRecordings) {
1957
+ const calendarEvent = (0, import_guards28.isUndefined)(callRecording.calendarEventId) ? void 0 : calendarEventsById.get(callRecording.calendarEventId);
1958
+ if ((0, import_guards28.isUndefined)(calendarEvent) || hasMeetingEnded({ calendarEvent, now })) {
1959
+ continue;
1960
+ }
1961
+ const didScheduleCallRecorder = await ensureCallRecorder(client, {
1962
+ callRecording,
1963
+ calendarEvent
1964
+ });
1965
+ if (didScheduleCallRecorder) {
1966
+ scheduledCallRecordingIds.push(callRecording.id);
1967
+ }
1968
+ }
1969
+ return { scheduledCallRecordingIds };
1970
+ };
1971
+ var hasMeetingEnded = ({
1972
+ calendarEvent,
1973
+ now
1974
+ }) => {
1975
+ const reference = calendarEvent.endsAt ?? calendarEvent.startsAt;
1976
+ if ((0, import_guards28.isUndefined)(reference)) {
1977
+ return false;
1978
+ }
1979
+ const referenceTime = new Date(reference).getTime();
1980
+ return !Number.isNaN(referenceTime) && referenceTime <= now.getTime();
1981
+ };
1982
+
1983
+ // src/logic-functions/flows/reap-orphaned-call-recorders.util.ts
1984
+ var import_guards30 = __toESM(require_build());
1985
+
1986
+ // src/logic-functions/recall-api/cancel-recall-bot.util.ts
1987
+ var cancelRecallBot = async ({
1988
+ externalBotId
1989
+ }) => {
1990
+ const configResult = getRecallApiConfig();
1991
+ if (!configResult.success) {
1992
+ return { ok: false, status: null, errorMessage: configResult.error };
1993
+ }
1994
+ const result = await recallBotApiRequest({
1995
+ config: configResult.config,
1996
+ path: `/bot/${externalBotId}/`,
1997
+ method: "DELETE",
1998
+ allowNotFound: true
1999
+ });
2000
+ if (!result.ok) {
2001
+ return result;
2002
+ }
2003
+ return { ok: true };
2004
+ };
2005
+
2006
+ // src/logic-functions/recall-api/eject-recall-bot.util.ts
2007
+ var ejectRecallBot = async ({
2008
+ externalBotId
2009
+ }) => {
2010
+ const configResult = getRecallApiConfig();
2011
+ if (!configResult.success) {
2012
+ return { ok: false, status: null, errorMessage: configResult.error };
2013
+ }
2014
+ const result = await recallBotApiRequest({
2015
+ config: configResult.config,
2016
+ path: `/bot/${externalBotId}/leave_call/`,
2017
+ method: "POST",
2018
+ allowNotFound: true
2019
+ });
2020
+ if (!result.ok) {
2021
+ return result;
2022
+ }
2023
+ return { ok: true };
2024
+ };
2025
+
2026
+ // src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts
2027
+ var import_guards29 = __toESM(require_build());
2028
+ var RECALL_BOT_LIST_MAX_PAGES = 10;
2029
+ var listScheduledRecallBots = async ({
2030
+ joinAtAfter,
2031
+ joinAtBefore
2032
+ }) => {
2033
+ const configResult = getRecallApiConfig();
2034
+ if (!configResult.success) {
2035
+ return { ok: false, status: null, errorMessage: configResult.error };
2036
+ }
2037
+ const bots = [];
2038
+ let path = `/bot/?join_at_after=${encodeURIComponent(
2039
+ joinAtAfter
2040
+ )}&join_at_before=${encodeURIComponent(joinAtBefore)}`;
2041
+ for (let pageIndex = 0; !(0, import_guards29.isUndefined)(path) && pageIndex < RECALL_BOT_LIST_MAX_PAGES; pageIndex++) {
2042
+ const result = await recallBotApiRequest({
2043
+ config: configResult.config,
2044
+ path,
2045
+ method: "GET"
2046
+ });
2047
+ if (!result.ok) {
2048
+ return result;
2049
+ }
2050
+ bots.push(...extractRecallBots(result.data));
2051
+ path = extractNextPath2(result.data, configResult.config.baseUrl);
2052
+ }
2053
+ if (!(0, import_guards29.isUndefined)(path)) {
2054
+ return {
2055
+ ok: false,
2056
+ status: null,
2057
+ errorMessage: `Recall bot list exceeded ${RECALL_BOT_LIST_MAX_PAGES} pages`
2058
+ };
2059
+ }
2060
+ return { ok: true, bots };
2061
+ };
2062
+ var extractRecallBots = (response) => {
2063
+ if (!Array.isArray(response?.results)) {
2064
+ return [];
2065
+ }
2066
+ return response.results.flatMap((candidate) => {
2067
+ const bot = asRecord(candidate);
2068
+ if ((0, import_guards29.isUndefined)(bot) || !(0, import_guards29.isString)(bot.id)) {
2069
+ return [];
2070
+ }
2071
+ return [
2072
+ {
2073
+ id: bot.id,
2074
+ metadata: asRecord(bot.metadata) ?? {}
2075
+ }
2076
+ ];
2077
+ });
2078
+ };
2079
+ var extractNextPath2 = (response, baseUrl) => {
2080
+ const next = response?.next;
2081
+ if (!(0, import_guards29.isString)(next) || !next.startsWith(baseUrl)) {
2082
+ return void 0;
2083
+ }
2084
+ return next.slice(baseUrl.length);
2085
+ };
2086
+
2087
+ // src/logic-functions/flows/reap-orphaned-call-recorders.util.ts
2088
+ var reapOrphanedCallRecorders = async ({
2089
+ client,
2090
+ joinAtAfter,
2091
+ joinAtBefore
2092
+ }) => {
2093
+ const listResult = await listScheduledRecallBots({
2094
+ joinAtAfter,
2095
+ joinAtBefore
2096
+ });
2097
+ if (!listResult.ok) {
2098
+ console.warn(
2099
+ `[call-recorder] failed to list Recall bots for orphan reaping: ${listResult.errorMessage}`
2100
+ );
2101
+ return { scannedBotCount: 0, canceledExternalBotIds: [] };
2102
+ }
2103
+ const currentWorkspaceId = getCurrentWorkspaceId();
2104
+ if ((0, import_guards30.isUndefined)(currentWorkspaceId)) {
2105
+ console.warn(
2106
+ "[call-recorder] cannot reap orphaned Recall bots: workspace id unavailable"
2107
+ );
2108
+ return {
2109
+ scannedBotCount: listResult.bots.length,
2110
+ canceledExternalBotIds: []
2111
+ };
2112
+ }
2113
+ const workspaceManagedBots = listResult.bots.filter(
2114
+ (bot) => isCurrentWorkspaceManagedBot({ bot, currentWorkspaceId })
2115
+ );
2116
+ if (workspaceManagedBots.length === 0) {
2117
+ return {
2118
+ scannedBotCount: listResult.bots.length,
2119
+ canceledExternalBotIds: []
2120
+ };
2121
+ }
2122
+ const callRecordings = await findCallRecordingsByIds(
2123
+ client,
2124
+ getUniqueSortedIds(
2125
+ workspaceManagedBots.map((bot) => getClaimedCallRecordingId(bot))
2126
+ )
2127
+ );
2128
+ const callRecordingsById = new Map(
2129
+ callRecordings.map((callRecording) => [callRecording.id, callRecording])
2130
+ );
2131
+ const canceledExternalBotIds = [];
2132
+ for (const bot of workspaceManagedBots) {
2133
+ const claimedCallRecordingId = getClaimedCallRecordingId(bot);
2134
+ const callRecording = (0, import_guards30.isUndefined)(claimedCallRecordingId) ? void 0 : callRecordingsById.get(claimedCallRecordingId);
2135
+ if (isBotClaimed({ bot, callRecording })) {
2136
+ continue;
2137
+ }
2138
+ console.warn(
2139
+ `[call-recorder] canceling orphaned Recall bot ${bot.id} (claimed callRecording: ${claimedCallRecordingId})`
2140
+ );
2141
+ if (await cancelOrEjectRecallBot(bot.id)) {
2142
+ canceledExternalBotIds.push(bot.id);
2143
+ }
2144
+ }
2145
+ return {
2146
+ scannedBotCount: listResult.bots.length,
2147
+ canceledExternalBotIds
2148
+ };
2149
+ };
2150
+ var getClaimedCallRecordingId = (bot) => {
2151
+ const claimedCallRecordingId = bot.metadata.twentyCallRecordingId;
2152
+ return normalizeOptionalString4(claimedCallRecordingId);
2153
+ };
2154
+ var getClaimedWorkspaceId = (bot) => {
2155
+ const claimedWorkspaceId = bot.metadata.twentyWorkspaceId;
2156
+ return normalizeOptionalString4(claimedWorkspaceId);
2157
+ };
2158
+ var isCurrentWorkspaceManagedBot = ({
2159
+ bot,
2160
+ currentWorkspaceId
2161
+ }) => {
2162
+ if ((0, import_guards30.isUndefined)(getClaimedCallRecordingId(bot))) {
2163
+ return false;
2164
+ }
2165
+ const claimedWorkspaceId = getClaimedWorkspaceId(bot);
2166
+ return claimedWorkspaceId === currentWorkspaceId;
2167
+ };
2168
+ var isBotClaimed = ({
2169
+ bot,
2170
+ callRecording
2171
+ }) => {
2172
+ if (callRecording?.recordingRequestStatus !== "REQUESTED" /* REQUESTED */) {
2173
+ return false;
2174
+ }
2175
+ if (callRecording.externalBotId === bot.id) {
2176
+ return true;
2177
+ }
2178
+ return (0, import_guards30.isUndefined)(callRecording.externalBotId);
2179
+ };
2180
+ var cancelOrEjectRecallBot = async (externalBotId) => {
2181
+ const cancelResult = await cancelRecallBot({ externalBotId });
2182
+ if (cancelResult.ok) {
2183
+ return true;
2184
+ }
2185
+ if (!(0, import_guards30.isNull)(cancelResult.status)) {
2186
+ const ejectResult = await ejectRecallBot({ externalBotId });
2187
+ if (ejectResult.ok) {
2188
+ return true;
2189
+ }
2190
+ }
2191
+ console.warn(
2192
+ `[call-recorder] failed to cancel orphaned Recall bot ${externalBotId}: ${cancelResult.errorMessage}`
2193
+ );
2194
+ return false;
2195
+ };
2196
+ var normalizeOptionalString4 = (value) => isNonEmptyString(value) ? value.trim() : void 0;
2197
+
2198
+ // src/logic-functions/reconcile-stale-bot-state.ts
2199
+ var REAPER_JOIN_AT_LOOKBACK_HOURS = 4;
2200
+ var REAPER_JOIN_AT_LOOKAHEAD_HOURS = 24;
2201
+ var reconcileStaleBotStateHandler = async () => {
2202
+ const now = /* @__PURE__ */ new Date();
2203
+ const client = new CoreApiClient();
2204
+ const botlessHealResult = await healCallRecordingsMissingBotSafely(
2205
+ client,
2206
+ now
2207
+ );
2208
+ const orphanedBotReapingResult = await reapOrphanedCallRecordersInJoinAtWindow(client, now);
2209
+ const statusConvergenceResult = await convergeDivergedCallRecordingsSafely(
2210
+ client,
2211
+ now
2212
+ );
2213
+ return {
2214
+ botlessHealResult,
2215
+ orphanedBotReapingResult,
2216
+ statusConvergenceResult
2217
+ };
2218
+ };
2219
+ var healCallRecordingsMissingBotSafely = async (client, now) => {
2220
+ try {
2221
+ return await healCallRecordingsMissingBot({ client, now });
2222
+ } catch (error) {
2223
+ return buildStepFailure("botless call recording healing", error);
2224
+ }
2225
+ };
2226
+ var reapOrphanedCallRecordersInJoinAtWindow = async (client, now) => {
2227
+ try {
2228
+ return await reapOrphanedCallRecorders({
2229
+ client,
2230
+ joinAtAfter: new Date(
2231
+ now.getTime() - REAPER_JOIN_AT_LOOKBACK_HOURS * 60 * 60 * 1e3
2232
+ ).toISOString(),
2233
+ joinAtBefore: new Date(
2234
+ now.getTime() + REAPER_JOIN_AT_LOOKAHEAD_HOURS * 60 * 60 * 1e3
2235
+ ).toISOString()
2236
+ });
2237
+ } catch (error) {
2238
+ return buildStepFailure("orphaned bot reaping", error);
2239
+ }
2240
+ };
2241
+ var convergeDivergedCallRecordingsSafely = async (client, now) => {
2242
+ try {
2243
+ return await convergeDivergedCallRecordings({ client, now });
2244
+ } catch (error) {
2245
+ return buildStepFailure("call recording status convergence", error);
2246
+ }
2247
+ };
2248
+ var buildStepFailure = (stepLabel, error) => {
2249
+ const errorMessage = error instanceof Error ? error.message : String(error);
2250
+ if (process.env.NODE_ENV !== "test") {
2251
+ console.error(`[call-recorder] ${stepLabel} failed: ${errorMessage}`);
2252
+ }
2253
+ return { error: `${stepLabel} failed` };
2254
+ };
2255
+ var reconcile_stale_bot_state_default = defineLogicFunction({
2256
+ universalIdentifier: STALE_BOT_STATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
2257
+ name: "reconcile-stale-bot-state",
2258
+ description: "Converges call recordings with Recall on a schedule: pulls stale bot statuses and overdue transcripts, finishes failed cancellations, schedules bots for recordings still missing one, and reaps unclaimed bots. Reads calendar events only to heal already-decided recordings, never to discover meetings.",
2259
+ timeoutSeconds: 250,
2260
+ handler: reconcileStaleBotStateHandler,
2261
+ cronTriggerSettings: {
2262
+ pattern: STALE_BOT_STATE_CRON_PATTERN
2263
+ }
2264
+ });
2265
+ export {
2266
+ reconcile_stale_bot_state_default as default
2267
+ };
2268
+ //# sourceMappingURL=reconcile-stale-bot-state.mjs.map