@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.
- package/manifest.json +297 -0
- package/package.json +2 -2
- package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
- package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
- package/src/logic-functions/process-recall-webhook.mjs +1744 -0
- package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
- package/src/logic-functions/recall-webhook.mjs +391 -0
- package/src/logic-functions/recall-webhook.mjs.map +7 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
- package/.env.example +0 -5
- package/.nvmrc +0 -1
- package/.oxlintrc.json +0 -20
- package/AGENTS.md +0 -67
- package/CLAUDE.md +0 -67
- package/README.md +0 -24
- package/SETUP.md +0 -95
- package/src/__tests__/global-setup.ts +0 -100
- package/src/__tests__/schema.integration-test.ts +0 -104
- package/src/application-config.ts +0 -96
- package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
- package/src/constants/app-description.ts +0 -2
- package/src/constants/app-display-name.ts +0 -1
- package/src/constants/application-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
- package/src/constants/call-recorder-preference.ts +0 -4
- package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
- package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
- package/src/constants/default-role-universal-identifier.ts +0 -2
- package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
- package/src/default-role.ts +0 -69
- package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
- package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
- package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
- package/src/front-components/components/CalendarEventRecording.tsx +0 -39
- package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
- package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
- package/src/front-components/components/RecordingTranscript.tsx +0 -92
- package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
- package/src/front-components/components/TranscriptEntryList.tsx +0 -61
- package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
- package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
- package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
- package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
- package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
- package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
- package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
- package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
- package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
- package/src/front-components/types/transcript-entry.type.ts +0 -13
- package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
- package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
- package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
- package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
- package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
- package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
- package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
- package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
- package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
- package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
- package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
- package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
- package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
- package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
- package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
- package/src/logic-functions/constants/call-recording-status.ts +0 -9
- package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
- package/src/logic-functions/constants/default-recall-region.ts +0 -1
- package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
- package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
- package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
- package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
- package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
- package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
- package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
- package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
- package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
- package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
- package/src/logic-functions/constants/twenty-page-size.ts +0 -1
- package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
- package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
- package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
- package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
- package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
- package/src/logic-functions/data/create-call-recording.util.ts +0 -41
- package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
- package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
- package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
- package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
- package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
- package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
- package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
- package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
- package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
- package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
- package/src/logic-functions/data/update-call-recording.util.ts +0 -24
- package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
- package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
- package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
- package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
- package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
- package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
- package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
- package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
- package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
- package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
- package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
- package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
- package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
- package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
- package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
- package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
- package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
- package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
- package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
- package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
- package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
- package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
- package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
- package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
- package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
- package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
- package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
- package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
- package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
- package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
- package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
- package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
- package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
- package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
- package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
- package/src/logic-functions/flows/download-transcript.util.ts +0 -67
- package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
- package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
- package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
- package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
- package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
- package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
- package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
- package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
- package/src/logic-functions/process-recall-webhook.ts +0 -23
- package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
- package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
- package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
- package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
- package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
- package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
- package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
- package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
- package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
- package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
- package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
- package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
- package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
- package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
- package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
- package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
- package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
- package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
- package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
- package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
- package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
- package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
- package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
- package/src/logic-functions/recall-webhook.ts +0 -90
- package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
- package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
- package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
- package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
- package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
- package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
- package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
- package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
- package/src/logic-functions/types/call-recording-record.type.ts +0 -15
- package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
- package/src/logic-functions/types/files-field-value.type.ts +0 -1
- package/src/logic-functions/types/meeting-recording.type.ts +0 -7
- package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
- package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
- package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
- package/src/logic-functions/types/transcript-marker.type.ts +0 -6
- package/src/logic-functions/utils/as-record.util.ts +0 -6
- package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
- package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
- package/src/logic-functions/utils/get-string.util.ts +0 -4
- package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
- package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
- package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
- package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
- package/tsconfig.json +0 -42
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -31
- 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
|