From db687c7407bcd2b4863865fa9d4a24f5b7fa5e97 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:36:58 +0530 Subject: [PATCH] chore(call-recorder): drop FAILED status schema bridge (#22134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup of the call-recorder data layer + the SDK 2.16 bump and its fallout, now that the `FAILED_UNKNOWN → FAILED` rename shipped in `twenty/v2.16.0` (#22062). - **Drop the schema bridge.** `executeCurrentSchemaMutation` and the integration-test compatibility filter existed only to work around servers exposing `FAILED_UNKNOWN`. Deleted the bridge; `updateCallRecording` / `completeCallRecordingIngestion` call `client.mutation(...)` directly. Integration test iterates all `CallRecordingStatus` values. - **Bump SDK** `twenty-sdk` / `twenty-client-sdk` to `2.16.0`. - **One export per file.** Move `CallRecordingUpdateFields` to its own type file; extract the duplicated media-file shape into `CallRecordingMediaFile`. - **Migrate the Recall webhook to `serverRouteTriggerSettings`** (2.16 dropped `serverWebhookTriggerSettings` + its declarative `workspaceIdResolver`). The webhook is now a **resolver** (`recall-webhook`) that verifies the Svix signature, reads `twentyWorkspaceId` from the Recall bot metadata, and returns `{ workspaceId, targetLogicFunctionUniversalIdentifier, payload }`; the platform dispatches to a new **target** function (`process-recall-webhook`) in the resolved workspace, where `CoreApiClient` is workspace-scoped. Resolver UID/route unchanged, so the registered Recall endpoint URL stays valid. Failures now throw → HTTP 500 (Svix retries) instead of returning 401/400. Verified: typecheck, 213 unit tests, oxlint, oxfmt all green. **Not yet verified end-to-end against a live server** — call-recorder is the first app on `serverRouteTriggerSettings`, so a real Recall webhook should be tested through the resolver→target path before relying on it. --- .../public/call-recorder/README.md | 4 +- .../public/call-recorder/package.json | 4 +- .../src/__tests__/schema.integration-test.ts | 26 +- ...ook-logic-function-universal-identifier.ts | 2 + .../__tests__/process-recall-webhook.test.ts | 62 +++ .../__tests__/recall-webhook.test.ts | 156 ++++---- .../complete-call-recording-ingestion.util.ts | 10 +- .../execute-current-schema-mutation.util.ts | 54 --- .../data/update-call-recording.util.ts | 30 +- .../domain/build-recall-bot-metadata.util.ts | 21 -- .../build-recall-routing-metadata.util.ts | 12 + ...-complete-call-recording-ingestion.util.ts | 2 +- .../converge-diverged-call-recordings.test.ts | 2 - .../__tests__/handle-recall-webhook.test.ts | 21 +- .../__tests__/reconcile-call-recorder.test.ts | 6 - .../converge-diverged-call-recordings.util.ts | 6 +- .../flows/ensure-call-recorder.util.ts | 7 +- .../flows/handle-recall-webhook.util.ts | 6 +- .../flows/ingest-call-recording-media.util.ts | 5 +- .../persist-call-recording-progress.util.ts | 6 +- .../flows/reconcile-call-recorder.util.ts | 6 +- ...cording-transcript-artifact-result.type.ts | 2 +- ...call-recording-transcript-artifact.util.ts | 1 - .../reschedule-call-recording-bot.util.ts | 7 +- .../logic-functions/process-recall-webhook.ts | 23 ++ .../__tests__/recall-bot-api.test.ts | 73 +--- .../create-async-recall-transcript.util.ts | 5 - ...y-workspace-id-from-recall-webhook.util.ts | 8 + .../get-recall-webhook-bot-metadata.util.ts | 18 + .../parse-recall-webhook-event.util.ts | 24 +- .../recall-api/schedule-recall-bot.util.ts | 4 +- .../src/logic-functions/recall-webhook.ts | 64 ++-- .../types/call-recording-media-file.type.ts | 1 + .../call-recording-update-fields.type.ts | 20 + .../types/recall-bot-metadata.type.ts | 6 - .../types/recall-routing-metadata.type.ts | 4 + .../public/call-recorder/yarn.lock | 354 +++++++++++++++++- 37 files changed, 663 insertions(+), 399 deletions(-) create mode 100644 packages/twenty-apps/public/call-recorder/src/constants/process-recall-webhook-logic-function-universal-identifier.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/process-recall-webhook.test.ts delete mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/data/execute-current-schema-mutation.util.ts delete mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-bot-metadata.util.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-routing-metadata.util.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/process-recall-webhook.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-media-file.type.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-update-fields.type.ts delete mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-bot-metadata.type.ts create mode 100644 packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-routing-metadata.type.ts diff --git a/packages/twenty-apps/public/call-recorder/README.md b/packages/twenty-apps/public/call-recorder/README.md index e75729bcb7..5fc7fa388f 100644 --- a/packages/twenty-apps/public/call-recorder/README.md +++ b/packages/twenty-apps/public/call-recorder/README.md @@ -106,8 +106,8 @@ What this app intentionally does **not** do in v1: | No bot joined a meeting | **Recording Bot** was Off, the event had no conference link, it wasn't synced from a connected calendar, or `RECALL_API_KEY` isn't set | Confirm the event is On, upcoming, has a video link, and came from a synced calendar; admin: confirm `RECALL_API_KEY` is set | | Recording never reaches `COMPLETED` | A Recall webhook was missed, or only one of audio/video was produced | The reconciliation job pulls the latest status from Recall within a few minutes; if it is marked `FAILED`, inspect the bot in the Recall dashboard | | Transcript empty, or marked pending/failed | Recall hasn't finished async transcription yet, or transcription failed for that call | Wait for the reconciliation job to ingest the transcript; a persistent failure leaves a marker in the transcript | -| Webhook rejected with `401` (Recall keeps retrying) | `RECALL_WEBHOOK_SECRET` doesn't match the Recall endpoint's signing secret | Re-copy the `whsec_…` secret from the Recall webhook endpoint into the `RECALL_WEBHOOK_SECRET` server variable | -| Webhook rejected with `500` about the secret | `RECALL_WEBHOOK_SECRET` is not set | Admin: set it on the application registration | +| Webhook rejected with `500` (`Invalid webhook signature`, Recall keeps retrying) | `RECALL_WEBHOOK_SECRET` doesn't match the Recall endpoint's signing secret | Re-copy the `whsec_…` secret from the Recall webhook endpoint into the `RECALL_WEBHOOK_SECRET` server variable | +| Webhook rejected with `500` (`RECALL_WEBHOOK_SECRET … not set`) | `RECALL_WEBHOOK_SECRET` is not set | Admin: set it on the application registration | | Bot left almost immediately | No one was admitted before the lobby / no-one-joined timeout, or everyone left | Adjust `CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS` / `CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS` if too aggressive | | Bot joined a meeting you didn't want recorded | Recording is on by default | Set the event's **Recording Bot** field to Off; the scheduled bot is canceled | diff --git a/packages/twenty-apps/public/call-recorder/package.json b/packages/twenty-apps/public/call-recorder/package.json index b6f29ffa1a..c5ff432b3a 100644 --- a/packages/twenty-apps/public/call-recorder/package.json +++ b/packages/twenty-apps/public/call-recorder/package.json @@ -31,8 +31,8 @@ "oxlint": "^0.16.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "twenty-client-sdk": "2.15.0", - "twenty-sdk": "2.15.0", + "twenty-client-sdk": "2.16.0", + "twenty-sdk": "2.16.0", "typescript": "^5.9.3", "vite-tsconfig-paths": "^4.2.1", "vitest": "^4.1.9" diff --git a/packages/twenty-apps/public/call-recorder/src/__tests__/schema.integration-test.ts b/packages/twenty-apps/public/call-recorder/src/__tests__/schema.integration-test.ts index 0ed83a6b8d..e3ce75584e 100644 --- a/packages/twenty-apps/public/call-recorder/src/__tests__/schema.integration-test.ts +++ b/packages/twenty-apps/public/call-recorder/src/__tests__/schema.integration-test.ts @@ -5,10 +5,6 @@ import { describe, expect, it } from 'vitest'; import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/application-universal-identifier'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; -import { - executeCurrentSchemaMutation, - type CurrentSchemaUpdateCallRecordingMutation, -} from 'src/logic-functions/data/execute-current-schema-mutation.util'; describe('App installation', () => { it('should find the installed app in the applications list', async () => { @@ -58,30 +54,16 @@ describe('CallRecording status contract', () => { } expect(serverCallRecordingStatuses).toEqual( - expect.arrayContaining([ - CallRecordingStatus.SCHEDULED, - CallRecordingStatus.JOINING, - CallRecordingStatus.RECORDING, - CallRecordingStatus.PROCESSING, - CallRecordingStatus.COMPLETED, - ]), + expect.arrayContaining(Object.values(CallRecordingStatus)), ); - // TODO: Remove this compatibility filter once the released server/SDK - // exposes FAILED instead of FAILED_UNKNOWN. - const statusesAcceptedByCurrentServer = Object.values( - CallRecordingStatus, - ).filter((status) => serverCallRecordingStatuses.includes(status)); - - for (const status of statusesAcceptedByCurrentServer) { - const mutation = { + for (const status of Object.values(CallRecordingStatus)) { + const updated = await client.mutation({ updateCallRecording: { __args: { id: callRecordingId, data: { status } }, status: true, }, - } satisfies CurrentSchemaUpdateCallRecordingMutation; - - const updated = await executeCurrentSchemaMutation(client, mutation); + }); expect(updated.updateCallRecording?.status).toBe(status); } diff --git a/packages/twenty-apps/public/call-recorder/src/constants/process-recall-webhook-logic-function-universal-identifier.ts b/packages/twenty-apps/public/call-recorder/src/constants/process-recall-webhook-logic-function-universal-identifier.ts new file mode 100644 index 0000000000..fa502af230 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/constants/process-recall-webhook-logic-function-universal-identifier.ts @@ -0,0 +1,2 @@ +export const PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = + '13d9c427-447e-494a-8d3c-1af5d0bacb82'; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/process-recall-webhook.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/process-recall-webhook.test.ts new file mode 100644 index 0000000000..b1777294b9 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/process-recall-webhook.test.ts @@ -0,0 +1,62 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import processRecallWebhookLogicFunction, { + processRecallWebhookHandler, +} from 'src/logic-functions/process-recall-webhook'; + +const handleRecallWebhookMock = vi.hoisted(() => vi.fn()); +const coreApiClientMock = vi.hoisted(() => vi.fn()); + +vi.mock('src/logic-functions/flows/handle-recall-webhook.util', () => ({ + handleRecallWebhook: handleRecallWebhookMock, +})); + +vi.mock('twenty-client-sdk/core', () => ({ + CoreApiClient: coreApiClientMock, +})); + +const buildRecordingDoneWebhookBody = () => ({ + event: 'recording.done', + data: { + bot: { + id: 'recall-bot-1', + metadata: { + twentyWorkspaceId: '123e4567-e89b-12d3-a456-426614174000', + twentyCallRecordingId: 'call-recording-1', + }, + }, + recording: { id: 'recall-recording-1' }, + }, +}); + +describe('process-recall-webhook', () => { + beforeEach(() => { + handleRecallWebhookMock.mockReset(); + handleRecallWebhookMock.mockResolvedValue({ status: 'updated' }); + coreApiClientMock.mockReset(); + }); + + it('declares no external trigger so it only runs when dispatched by the resolver', () => { + expect(processRecallWebhookLogicFunction.success).toBe(true); + expect( + 'serverRouteTriggerSettings' in processRecallWebhookLogicFunction.config, + ).toBe(false); + expect( + processRecallWebhookLogicFunction.config.httpRouteTriggerSettings, + ).toBeUndefined(); + }); + + it('forwards the resolved payload to handleRecallWebhook with a workspace-scoped client', async () => { + const body = buildRecordingDoneWebhookBody(); + + const result = await processRecallWebhookHandler(body); + + expect(coreApiClientMock).toHaveBeenCalledTimes(1); + expect(handleRecallWebhookMock).toHaveBeenCalledTimes(1); + expect(handleRecallWebhookMock).toHaveBeenCalledWith({ + client: coreApiClientMock.mock.instances[0], + body, + }); + expect(result).toEqual({ status: 'updated' }); + }); +}); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.ts index 3a20df91e5..03667dde07 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.ts @@ -2,12 +2,12 @@ import { createHmac } from 'crypto'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/process-recall-webhook-logic-function-universal-identifier'; import recallWebhookLogicFunction, { recallWebhookRouteHandler, } from 'src/logic-functions/recall-webhook'; const getApplicationVariableValueMock = vi.hoisted(() => vi.fn()); -const handleRecallWebhookMock = vi.hoisted(() => vi.fn()); vi.mock( 'src/logic-functions/utils/get-application-variable-value.util', @@ -16,17 +16,10 @@ vi.mock( }), ); -vi.mock('src/logic-functions/flows/handle-recall-webhook.util', () => ({ - handleRecallWebhook: handleRecallWebhookMock, -})); - -vi.mock('twenty-client-sdk/core', () => ({ - CoreApiClient: vi.fn(), -})); - const SECRET_BYTES = Buffer.from('entry-test-secret'); const SECRET = `whsec_${SECRET_BYTES.toString('base64')}`; const WORKSPACE_ID = '123e4567-e89b-12d3-a456-426614174000'; +const CALL_RECORDING_ID = 'call-recording-1'; type RecallWebhookRoutePayload = Parameters< typeof recallWebhookRouteHandler @@ -61,6 +54,7 @@ const buildRecordingDoneWebhookBody = () => ({ id: 'recall-bot-1', metadata: { twentyWorkspaceId: WORKSPACE_ID, + twentyCallRecordingId: CALL_RECORDING_ID, }, }, recording: { @@ -71,25 +65,26 @@ const buildRecordingDoneWebhookBody = () => ({ describe('recallWebhookRouteHandler', () => { beforeEach(() => { - vi.spyOn(console, 'error').mockImplementation(() => {}); getApplicationVariableValueMock.mockReset(); getApplicationVariableValueMock.mockReturnValue(SECRET); - handleRecallWebhookMock.mockReset(); - handleRecallWebhookMock.mockResolvedValue({ status: 'updated' }); }); - it('declares a server webhook resolver for Recall bot workspace metadata', () => { + it('declares a server route trigger that forwards the webhook signature headers', () => { expect(recallWebhookLogicFunction.success).toBe(true); expect( recallWebhookLogicFunction.config.httpRouteTriggerSettings, ).toBeUndefined(); expect( - recallWebhookLogicFunction.config.serverWebhookTriggerSettings, + 'serverRouteTriggerSettings' in recallWebhookLogicFunction.config, + ).toBe(true); + + if (!('serverRouteTriggerSettings' in recallWebhookLogicFunction.config)) { + throw new Error('Expected a server route trigger'); + } + + expect( + recallWebhookLogicFunction.config.serverRouteTriggerSettings, ).toEqual({ - workspaceIdResolver: { - source: 'body', - path: 'data.bot.metadata.twentyWorkspaceId', - }, forwardedRequestHeaders: [ 'webhook-id', 'webhook-timestamp', @@ -101,83 +96,73 @@ describe('recallWebhookRouteHandler', () => { }); }); - it('responds 500 when the webhook secret is not configured', async () => { + it('throws when the webhook secret is not configured', () => { getApplicationVariableValueMock.mockReturnValue(undefined); - const result = await recallWebhookRouteHandler( - buildRoutePayload({ rawBody: '{}', body: {} }), - ); - - expect(result).toMatchObject({ - __twentyHttpResponse: true, - status: 500, - body: { - error: expect.stringContaining('RECALL_WEBHOOK_SECRET'), - }, - }); + expect(() => + recallWebhookRouteHandler(buildRoutePayload({ rawBody: '{}', body: {} })), + ).toThrow('RECALL_WEBHOOK_SECRET'); }); - it('responds 500 when the raw body is not forwarded', async () => { - const result = await recallWebhookRouteHandler( - buildRoutePayload({ body: {} }), - ); - - expect(result).toMatchObject({ - __twentyHttpResponse: true, - status: 500, - body: { - error: expect.stringContaining('Raw request body'), - }, - }); + it('throws when the raw body is not forwarded', () => { + expect(() => + recallWebhookRouteHandler(buildRoutePayload({ body: {} })), + ).toThrow('Raw request body'); }); - it('responds 401 when the signature is invalid', async () => { - const result = await recallWebhookRouteHandler( - buildRoutePayload({ - rawBody: '{}', - body: {}, - headers: { - 'webhook-id': 'msg_entry_test', - 'webhook-timestamp': Math.floor(Date.now() / 1000).toString(), - 'webhook-signature': 'v1,not-a-real-signature', - }, - }), - ); - - expect(result).toMatchObject({ - __twentyHttpResponse: true, - status: 401, - body: { - error: expect.stringContaining('Invalid webhook signature'), - }, - }); + it('throws when the signature is invalid', () => { + expect(() => + recallWebhookRouteHandler( + buildRoutePayload({ + rawBody: '{}', + body: {}, + headers: { + 'webhook-id': 'msg_entry_test', + 'webhook-timestamp': Math.floor(Date.now() / 1000).toString(), + 'webhook-signature': 'v1,not-a-real-signature', + }, + }), + ), + ).toThrow('Invalid webhook signature'); }); - it('responds 400 when a correctly signed payload is empty', async () => { + it('throws when a correctly signed payload is empty', () => { const rawBody = 'null'; - const result = await recallWebhookRouteHandler( - buildRoutePayload({ - rawBody, - body: null, - headers: buildSignedHeaders(rawBody), - }), - ); - - expect(result).toMatchObject({ - __twentyHttpResponse: true, - status: 400, - body: { - error: 'Webhook payload was empty', - }, - }); + expect(() => + recallWebhookRouteHandler( + buildRoutePayload({ + rawBody, + body: null, + headers: buildSignedHeaders(rawBody), + }), + ), + ).toThrow('Webhook payload was empty'); }); - it('dispatches a correctly signed payload to the handler', async () => { + it('throws when the workspace id is missing from the bot metadata', () => { + const body = { + event: 'recording.done', + data: { bot: { id: 'recall-bot-1' } }, + }; + const rawBody = JSON.stringify(body); + + expect(() => + recallWebhookRouteHandler( + buildRoutePayload({ + rawBody, + body, + headers: buildSignedHeaders(rawBody), + }), + ), + ).toThrow('workspace id'); + }); + + it('resolves the target workspace for a correctly signed payload', () => { const body = buildRecordingDoneWebhookBody(); const rawBody = JSON.stringify(body); - const result = await recallWebhookRouteHandler( + const result = recallWebhookRouteHandler( buildRoutePayload({ rawBody, body, @@ -185,10 +170,11 @@ describe('recallWebhookRouteHandler', () => { }), ); - expect(handleRecallWebhookMock).toHaveBeenCalledTimes(1); - expect(handleRecallWebhookMock).toHaveBeenCalledWith( - expect.objectContaining({ body }), - ); - expect(result).toEqual({ status: 'updated' }); + expect(result).toEqual({ + workspaceId: WORKSPACE_ID, + targetLogicFunctionUniversalIdentifier: + PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + payload: body, + }); }); }); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/complete-call-recording-ingestion.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/data/complete-call-recording-ingestion.util.ts index b1f2dbc566..659c601af9 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/complete-call-recording-ingestion.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/data/complete-call-recording-ingestion.util.ts @@ -2,16 +2,12 @@ import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; import { NON_TERMINAL_CALL_RECORDING_STATUSES } from 'src/logic-functions/constants/non-terminal-call-recording-statuses'; -import { - executeCurrentSchemaMutation, - type CurrentSchemaUpdateCallRecordingsMutation, -} from 'src/logic-functions/data/execute-current-schema-mutation.util'; export const completeCallRecordingIngestion = async ( client: CoreApiClient, { id }: { id: string }, ): Promise => { - const mutation = { + const result = await client.mutation({ updateCallRecordings: { __args: { filter: { @@ -22,9 +18,7 @@ export const completeCallRecordingIngestion = async ( }, id: true, }, - } satisfies CurrentSchemaUpdateCallRecordingsMutation; - - const result = await executeCurrentSchemaMutation(client, mutation); + }); return (result.updateCallRecordings ?? []).length > 0; }; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/execute-current-schema-mutation.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/data/execute-current-schema-mutation.util.ts deleted file mode 100644 index a9a108f8e9..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/execute-current-schema-mutation.util.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { type CoreApiClient } from 'twenty-client-sdk/core'; - -import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; -import { type CallRecordingUpdateFields } from 'src/logic-functions/data/update-call-recording.util'; - -type CurrentSchemaMutationFunction = ( - mutation: CurrentSchemaMutation, -) => Promise; - -export type CurrentSchemaUpdateCallRecordingMutation = { - updateCallRecording: { - __args: { - id: string; - data: CallRecordingUpdateFields; - }; - id?: true; - status?: true; - }; -}; - -export type CurrentSchemaUpdateCallRecordingsMutation = { - updateCallRecordings: { - __args: { - filter: { - id: { eq: string }; - status?: { in: CallRecordingStatus[] }; - }; - data: Pick; - }; - id?: true; - }; -}; - -type CurrentSchemaMutation = - | CurrentSchemaUpdateCallRecordingMutation - | CurrentSchemaUpdateCallRecordingsMutation; - -type CurrentSchemaMutationResult = { - updateCallRecording?: { id?: string; status?: string | null } | null; - updateCallRecordings?: { id?: string }[] | null; -}; - -// TODO: Remove this bridge once the released SDK includes the current -// CallRecording schema with FAILED and callRecorderFailureReason. -export const executeCurrentSchemaMutation = ( - client: CoreApiClient, - mutation: CurrentSchemaMutation, -): Promise => { - const currentSchemaClient = client as { - mutation: CurrentSchemaMutationFunction; - }; - - return currentSchemaClient.mutation(mutation); -}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.ts index 174775a5b7..3121b57c56 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.ts @@ -1,28 +1,6 @@ import { type CoreApiClient } from 'twenty-client-sdk/core'; -import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; -import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; -import { - executeCurrentSchemaMutation, - type CurrentSchemaUpdateCallRecordingMutation, -} from 'src/logic-functions/data/execute-current-schema-mutation.util'; - -export type CallRecordingUpdateFields = Partial<{ - // null clears a previously synced title when the calendar title disappears. - title: string | null; - status: CallRecordingStatus; - recordingRequestStatus: CallRecordingRequestStatus; - startedAt: string; - endedAt: string; - calendarEventId: string; - // null clears stale app-owned state on cancel/eject or reschedule. - externalBotId: string | null; - externalRecordingId: string; - callRecorderFailureReason: string | null; - transcript: Record; - audio: { fileId: string; label: string }[]; - video: { fileId: string; label: string }[]; -}>; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; export const updateCallRecording = async ( client: CoreApiClient, @@ -34,7 +12,7 @@ export const updateCallRecording = async ( data: CallRecordingUpdateFields; }, ): Promise => { - const mutation = { + await client.mutation({ updateCallRecording: { __args: { id, @@ -42,7 +20,5 @@ export const updateCallRecording = async ( }, id: true, }, - } satisfies CurrentSchemaUpdateCallRecordingMutation; - - await executeCurrentSchemaMutation(client, mutation); + }); }; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-bot-metadata.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-bot-metadata.util.ts deleted file mode 100644 index ce8f6b240a..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-bot-metadata.util.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { type MeetingRecording } from 'src/logic-functions/types/meeting-recording.type'; -import { type RecallBotMetadata } from 'src/logic-functions/types/recall-bot-metadata.type'; -import { computeRealMeetingKey } from 'src/logic-functions/domain/compute-real-meeting-key.util'; - -export const buildRecallBotMetadata = ({ - callRecording, - calendarEvent, - workspaceId, -}: MeetingRecording & { workspaceId: string }): RecallBotMetadata => { - return { - twentyWorkspaceId: workspaceId, - twentyCallRecordingId: callRecording.id, - twentyCalendarEventId: calendarEvent.id, - twentyRealMeetingKey: computeRealMeetingKey({ - calendarEventId: calendarEvent.id, - conferenceLinkUrl: calendarEvent.conferenceLinkUrl, - iCalUid: calendarEvent.iCalUid, - startsAt: calendarEvent.startsAt, - }), - }; -}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-routing-metadata.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-routing-metadata.util.ts new file mode 100644 index 0000000000..ba608a7b63 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/build-recall-routing-metadata.util.ts @@ -0,0 +1,12 @@ +import { type RecallRoutingMetadata } from 'src/logic-functions/types/recall-routing-metadata.type'; + +export const buildRecallRoutingMetadata = ({ + callRecordingId, + workspaceId, +}: { + callRecordingId: string; + workspaceId: string; +}): RecallRoutingMetadata => ({ + twentyWorkspaceId: workspaceId, + twentyCallRecordingId: callRecordingId, +}); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts index 5e2f827e45..b6c054e96a 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts @@ -2,7 +2,7 @@ import { CallRecordingStatus } from 'src/logic-functions/constants/call-recordin import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type'; import { computeCallRecordingCharge } from 'src/logic-functions/domain/compute-call-recording-charge.util'; import { isCallRecordingIngestionComplete } from 'src/logic-functions/domain/is-call-recording-ingestion-complete.util'; -import { type CallRecordingUpdateFields } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; export const shouldCompleteCallRecordingIngestion = ({ current, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts index 308581ebe0..76a3d97ea5 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts @@ -160,7 +160,6 @@ describe('convergeDivergedCallRecordings', () => { }); expect(createAsyncRecallTranscriptMock).toHaveBeenCalledWith({ externalRecordingId: 'recall-recording-1', - callRecordingId: 'call-recording-1', }); expect(client.mutations).toEqual([ expect.objectContaining({ @@ -497,7 +496,6 @@ describe('convergeDivergedCallRecordings', () => { expect(createAsyncRecallTranscriptMock).toHaveBeenCalledTimes(1); expect(createAsyncRecallTranscriptMock).toHaveBeenCalledWith({ externalRecordingId: 'recall-recording-1', - callRecordingId: 'call-recording-1', }); expect(client.mutations).toEqual([ { diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts index 451b22b49d..81f5c1930d 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts @@ -12,6 +12,7 @@ const buildRecordingDoneWebhookBody = () => ({ id: 'recall-bot-1', metadata: { twentyWorkspaceId: WORKSPACE_ID, + twentyCallRecordingId: 'call-recording-1', }, }, recording: { @@ -190,6 +191,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -237,6 +239,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -272,6 +275,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -321,6 +325,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-current', }, }, @@ -410,6 +415,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -457,6 +463,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -504,6 +511,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -545,6 +553,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -591,6 +600,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -632,6 +642,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -668,6 +679,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -696,6 +708,7 @@ describe('handleRecallWebhook', () => { data: { bot: { metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-deleted', }, }, @@ -755,7 +768,6 @@ describe('handleRecallWebhook', () => { expect(createAsyncRecallTranscriptMock).toHaveBeenCalledTimes(1); expect(createAsyncRecallTranscriptMock).toHaveBeenCalledWith({ externalRecordingId: 'recall-recording-1', - callRecordingId: 'call-recording-1', }); expect(client.mutations).toEqual([ { @@ -841,6 +853,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -856,7 +869,6 @@ describe('handleRecallWebhook', () => { }); expect(createAsyncRecallTranscriptMock).toHaveBeenCalledWith({ externalRecordingId: 'recall-recording-9', - callRecordingId: 'call-recording-1', }); expect(client.mutations).toEqual([ expect.objectContaining({ @@ -954,7 +966,6 @@ describe('handleRecallWebhook', () => { expect(createAsyncRecallTranscriptMock).toHaveBeenCalledWith({ externalRecordingId: 'recall-recording-1', - callRecordingId: 'call-recording-1', }); expect(client.mutations).toEqual([ expect.objectContaining({ @@ -1070,6 +1081,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -1150,6 +1162,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -1209,6 +1222,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, @@ -1263,6 +1277,7 @@ describe('handleRecallWebhook', () => { bot: { id: 'recall-bot-1', metadata: { + twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: 'call-recording-1', }, }, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts index bb8ba52c7b..60b4e5f99a 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts @@ -265,9 +265,6 @@ describe('reconcileCallRecorderForCalendarEventIds', () => { metadata: { twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: buildCustomerSyncCallRecordingId(), - twentyCalendarEventId: 'calendar-event-1', - twentyRealMeetingKey: - 'link:meet.example.com/customer-sync:2026-01-01T13:00:00.000Z', }, }); }); @@ -416,9 +413,6 @@ describe('reconcileCallRecorderForCalendarEventIds', () => { metadata: { twentyWorkspaceId: WORKSPACE_ID, twentyCallRecordingId: buildCustomerSyncCallRecordingId(), - twentyCalendarEventId: 'calendar-event-1', - twentyRealMeetingKey: - 'link:meet.example.com/customer-sync:2026-01-01T13:00:00.000Z', }, }); }); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts index ed712296b3..05eb727847 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts @@ -23,10 +23,8 @@ import { persistCallRecordingProgress } from 'src/logic-functions/flows/persist- import { reconcileCallRecordingTranscriptArtifact } from 'src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util'; import { type ConvergeDivergedCallRecordingsResult } from 'src/logic-functions/flows/converge-diverged-call-recordings-result.type'; import { shouldCompleteCallRecordingIngestion } from 'src/logic-functions/domain/should-complete-call-recording-ingestion.util'; -import { - updateCallRecording, - type CallRecordingUpdateFields, -} from 'src/logic-functions/data/update-call-recording.util'; +import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; const CONVERGENCE_LOOKBACK_DAYS = 7; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ensure-call-recorder.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ensure-call-recorder.util.ts index 2fe38ab658..522e63acb3 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ensure-call-recorder.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ensure-call-recorder.util.ts @@ -3,7 +3,7 @@ import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { type MeetingRecording } from 'src/logic-functions/types/meeting-recording.type'; -import { buildRecallBotMetadata } from 'src/logic-functions/domain/build-recall-bot-metadata.util'; +import { buildRecallRoutingMetadata } from 'src/logic-functions/domain/build-recall-routing-metadata.util'; import { computeRecallBotJoinAt } from 'src/logic-functions/domain/compute-recall-bot-join-at.util'; import { findCallRecordingsByIds } from 'src/logic-functions/data/find-call-recordings-by-ids.util'; import { getCurrentWorkspaceId } from 'src/logic-functions/data/get-current-workspace-id.util'; @@ -50,9 +50,8 @@ export const ensureCallRecorder = async ( const scheduleResult = await scheduleRecallBot({ meetingUrl, joinAt, - metadata: buildRecallBotMetadata({ - callRecording, - calendarEvent, + metadata: buildRecallRoutingMetadata({ + callRecordingId: callRecording.id, workspaceId, }), }); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-recall-webhook.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-recall-webhook.util.ts index d4f764e3f2..f1a41a9125 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-recall-webhook.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-recall-webhook.util.ts @@ -21,10 +21,8 @@ import { import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util'; import { persistCallRecordingProgress } from 'src/logic-functions/flows/persist-call-recording-progress.util'; import { reconcileCallRecordingTranscriptArtifact } from 'src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util'; -import { - updateCallRecording, - type CallRecordingUpdateFields, -} from 'src/logic-functions/data/update-call-recording.util'; +import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; type MatchedCallRecording = { id: string; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ingest-call-recording-media.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ingest-call-recording-media.util.ts index f7e28399a7..517cb00e29 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ingest-call-recording-media.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/ingest-call-recording-media.util.ts @@ -5,7 +5,8 @@ import { CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/c import { CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recording-video-field-universal-identifier'; import { extractRecallMediaUrls } from 'src/logic-functions/recall-api/extract-recall-media-urls.util'; import { getRecallRecording } from 'src/logic-functions/recall-api/get-recall-recording.util'; -import { type CallRecordingUpdateFields } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingMediaFile } from 'src/logic-functions/types/call-recording-media-file.type'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; type CallRecordingMediaUpdateFields = Pick< CallRecordingUpdateFields, @@ -88,7 +89,7 @@ const ingestMediaArtifact = async ({ url: string; fileName: string; fieldMetadataUniversalIdentifier: string; -}): Promise<{ fileId: string; label: string }[] | undefined> => { +}): Promise => { try { const { buffer, contentType } = await downloadMediaFile(url); const uploadedFile = await metadataClient.uploadFile( diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/persist-call-recording-progress.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/persist-call-recording-progress.util.ts index 5024ec3a22..22aa7a2916 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/persist-call-recording-progress.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/persist-call-recording-progress.util.ts @@ -3,10 +3,8 @@ import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type'; import { completeAndChargeCallRecording } from 'src/logic-functions/flows/complete-and-charge-call-recording.util'; import { shouldCompleteCallRecordingIngestion } from 'src/logic-functions/domain/should-complete-call-recording-ingestion.util'; -import { - updateCallRecording, - type CallRecordingUpdateFields, -} from 'src/logic-functions/data/update-call-recording.util'; +import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; type PersistCallRecordingProgressCurrent = { status?: string; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recorder.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recorder.util.ts index 33cdf49225..89c91e9817 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recorder.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recorder.util.ts @@ -23,10 +23,8 @@ import { findCallRecordingsByCalendarEventIds } from 'src/logic-functions/data/f import { findCallRecordingsByIds } from 'src/logic-functions/data/find-call-recordings-by-ids.util'; import { getUniqueSortedIds } from 'src/logic-functions/utils/get-unique-sorted-ids.util'; import { rescheduleCallRecordingBot } from 'src/logic-functions/flows/reschedule-call-recording-bot.util'; -import { - updateCallRecording, - type CallRecordingUpdateFields, -} from 'src/logic-functions/data/update-call-recording.util'; +import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; export const reconcileCallRecorderForCalendarEventIds = async ({ client, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts index 18581dc5b2..1c8ab638e4 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts @@ -1,4 +1,4 @@ -import { type CallRecordingUpdateFields } from 'src/logic-functions/data/update-call-recording.util'; +import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type'; type CallRecordingTranscriptArtifactUpdateFields = Pick< CallRecordingUpdateFields, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts index 0d6489c59a..c50aa5e8f9 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts @@ -68,7 +68,6 @@ export const reconcileCallRecordingTranscriptArtifact = async ({ ) { const createResult = await createAsyncRecallTranscript({ externalRecordingId, - callRecordingId, }); if (!createResult.ok) { diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts index caa1e8801b..3ec904cf49 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.ts @@ -2,7 +2,7 @@ import { isUndefined } from '@sniptt/guards'; import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type MeetingRecording } from 'src/logic-functions/types/meeting-recording.type'; -import { buildRecallBotMetadata } from 'src/logic-functions/domain/build-recall-bot-metadata.util'; +import { buildRecallRoutingMetadata } from 'src/logic-functions/domain/build-recall-routing-metadata.util'; import { computeRecallBotJoinAt } from 'src/logic-functions/domain/compute-recall-bot-join-at.util'; import { getCurrentWorkspaceId } from 'src/logic-functions/data/get-current-workspace-id.util'; import { rescheduleRecallBot } from 'src/logic-functions/recall-api/reschedule-recall-bot.util'; @@ -43,9 +43,8 @@ export const rescheduleCallRecordingBot = async ( externalBotId, meetingUrl, joinAt, - metadata: buildRecallBotMetadata({ - callRecording, - calendarEvent, + metadata: buildRecallRoutingMetadata({ + callRecordingId: callRecording.id, workspaceId, }), }); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/process-recall-webhook.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/process-recall-webhook.ts new file mode 100644 index 0000000000..e5e6edb82e --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/process-recall-webhook.ts @@ -0,0 +1,23 @@ +import { CoreApiClient } from 'twenty-client-sdk/core'; +import { defineLogicFunction } from 'twenty-sdk/define'; + +import { PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/process-recall-webhook-logic-function-universal-identifier'; +import { handleRecallWebhook } from 'src/logic-functions/flows/handle-recall-webhook.util'; +import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util'; + +// Dispatched by the recall-webhook resolver; runs in the resolved workspace so the client is workspace-scoped. +export const processRecallWebhookHandler = (body: RecallWebhookBody) => + handleRecallWebhook({ + client: new CoreApiClient(), + body, + }); + +export default defineLogicFunction({ + universalIdentifier: + PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + name: 'process-recall-webhook', + description: + 'Updates the matching CallRecording lifecycle status from a verified Recall.ai webhook event.', + timeoutSeconds: 30, + handler: processRecallWebhookHandler, +}); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts index aa165b12a5..93a142c544 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts @@ -13,6 +13,10 @@ import { CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME } from 'src/logic- const getRecallApiConfigMock = vi.hoisted(() => vi.fn()); const WORKSPACE_ID = '123e4567-e89b-12d3-a456-426614174000'; +const RECALL_ROUTING_METADATA = { + twentyWorkspaceId: WORKSPACE_ID, + twentyCallRecordingId: 'call-recording-id', +}; vi.mock('src/logic-functions/recall-api/get-recall-api-config.util', () => ({ getRecallApiConfig: getRecallApiConfigMock, @@ -45,12 +49,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(result).toEqual({ ok: true, externalBotId: 'recall-bot-id' }); @@ -73,12 +72,7 @@ describe('recall bot api', () => { audio_mixed_mp3: {}, retention: { type: 'timed', hours: 166 }, }, - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); }); @@ -88,12 +82,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(result).toEqual({ ok: true, externalBotId: 'recall-bot-id' }); @@ -113,12 +102,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(result).toEqual({ ok: true, externalBotId: 'recall-bot-id' }); @@ -141,12 +125,7 @@ describe('recall bot api', () => { const result = await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(result).toEqual({ @@ -168,12 +147,7 @@ describe('recall bot api', () => { externalBotId: 'recall-bot-gone', meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(result).toEqual({ @@ -204,12 +178,7 @@ describe('recall bot api', () => { await scheduleRecallBot({ meetingUrl: 'https://meet.google.com/abc-defg-hij', joinAt: '2026-01-01T13:00:00.000Z', - metadata: { - twentyWorkspaceId: WORKSPACE_ID, - twentyCallRecordingId: 'call-recording-id', - twentyCalendarEventId: 'calendar-event-id', - twentyRealMeetingKey: 'meeting-key', - }, + metadata: RECALL_ROUTING_METADATA, }); expect(fetchMock).toHaveBeenCalledWith( @@ -522,26 +491,6 @@ describe('recall bot api', () => { }); }); - it('adds call recording metadata when convergence creates an async transcript', async () => { - fetchMock.mockResolvedValue({ - ok: true, - status: 201, - json: async () => ({ id: 'recall-transcript-id' }), - }); - - const result = await createAsyncRecallTranscript({ - externalRecordingId: 'recall-recording-id', - callRecordingId: 'call-recording-id', - }); - - expect(result).toEqual({ ok: true, transcriptId: 'recall-transcript-id' }); - expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ - provider: { recallai_async: { language_code: 'auto' } }, - diarization: { use_separate_streams_when_available: true }, - metadata: { twentyCallRecordingId: 'call-recording-id' }, - }); - }); - it('does not retry async transcript creation failures', async () => { fetchMock.mockResolvedValue({ ok: false, diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/create-async-recall-transcript.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/create-async-recall-transcript.util.ts index c52ed514ee..388887f0d2 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/create-async-recall-transcript.util.ts @@ -10,10 +10,8 @@ type CreateAsyncRecallTranscriptResult = export const createAsyncRecallTranscript = async ({ externalRecordingId, - callRecordingId, }: { externalRecordingId: string; - callRecordingId?: string; }): Promise => { const configResult = getRecallApiConfig(); @@ -28,9 +26,6 @@ export const createAsyncRecallTranscript = async ({ body: { provider: { recallai_async: { language_code: 'auto' } }, diarization: { use_separate_streams_when_available: true }, - ...(callRecordingId === undefined - ? {} - : { metadata: { twentyCallRecordingId: callRecordingId } }), }, maxAttempts: 1, }); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts new file mode 100644 index 0000000000..de17f979ea --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts @@ -0,0 +1,8 @@ +import { getRecallWebhookBotMetadata } from 'src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util'; +import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util'; +import { getString } from 'src/logic-functions/utils/get-string.util'; + +export const extractTwentyWorkspaceIdFromRecallWebhook = ( + body: RecallWebhookBody, +): string | undefined => + getString(getRecallWebhookBotMetadata(body)?.twentyWorkspaceId); diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts new file mode 100644 index 0000000000..b566fe97db --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts @@ -0,0 +1,18 @@ +import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util'; +import { asRecord } from 'src/logic-functions/utils/as-record.util'; +import { getRecordAtPath } from 'src/logic-functions/utils/get-record-at-path.util'; + +// Recall delivers bot metadata under several body shapes per event family; this is the single reader of all of them. +export const getRecallWebhookBotMetadata = ( + body: RecallWebhookBody, +): Record | undefined => { + const data = asRecord(body.data); + const bot = asRecord(body.bot); + + return ( + asRecord(bot?.metadata) ?? + asRecord(getRecordAtPath(data, ['bot', 'metadata'])) ?? + asRecord(getRecordAtPath(data, ['recording', 'metadata'])) ?? + asRecord(data?.metadata) + ); +}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts index e1752ac8c8..9c9fe9a1b1 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts @@ -1,5 +1,6 @@ import { isUndefined } from '@sniptt/guards'; +import { getRecallWebhookBotMetadata } from 'src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util'; import { asRecord } from 'src/logic-functions/utils/as-record.util'; import { getRecordAtPath } from 'src/logic-functions/utils/get-record-at-path.util'; import { getString } from 'src/logic-functions/utils/get-string.util'; @@ -60,10 +61,9 @@ export const parseRecallWebhookEvent = ( getString(getRecordAtPath(data, ['status', 'recording_id'])) ?? getString(getRecordAtPath(data, ['recording', 'id'])) ?? getString(data?.recording_id), - callRecordingIdFromMetadata: extractCallRecordingIdFromMetadata({ - data, - bot, - }), + callRecordingIdFromMetadata: getString( + getRecallWebhookBotMetadata(body)?.twentyCallRecordingId, + ), recordingStartedAt: normalizeRecallTimestamp( getString(getRecordAtPath(data, ['recording', 'started_at'])), ), @@ -86,19 +86,3 @@ const getStatusCodeFromEventName = (event: string): string | undefined => { return statusCode === 'status_change' ? undefined : statusCode; }; - -const extractCallRecordingIdFromMetadata = ({ - data, - bot, -}: { - data: Record | undefined; - bot: Record | undefined; -}): string | undefined => { - const metadata = - asRecord(bot?.metadata) ?? - asRecord(getRecordAtPath(data, ['bot', 'metadata'])) ?? - asRecord(getRecordAtPath(data, ['recording', 'metadata'])) ?? - asRecord(data?.metadata); - - return getString(metadata?.twentyCallRecordingId); -}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts index 7cec18c3cb..8883e54359 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/schedule-recall-bot.util.ts @@ -2,7 +2,7 @@ import { isUndefined } from '@sniptt/guards'; import { getRecallBotAutomaticLeave } from 'src/logic-functions/constants/recall-bot-automatic-leave'; import { getRecallBotRecordingConfig } from 'src/logic-functions/constants/recall-bot-recording-config'; -import { type RecallBotMetadata } from 'src/logic-functions/types/recall-bot-metadata.type'; +import { type RecallRoutingMetadata } from 'src/logic-functions/types/recall-routing-metadata.type'; import { type RecallBotScheduleResult } from 'src/logic-functions/types/recall-bot-operation-result.type'; import { extractRecallBotId, @@ -14,7 +14,7 @@ import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-a export type ScheduleRecallBotArgs = { meetingUrl: string; joinAt: string; - metadata: RecallBotMetadata; + metadata: RecallRoutingMetadata; }; export const scheduleRecallBot = async ({ diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-webhook.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-webhook.ts index c596b17418..9a320f9098 100644 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-webhook.ts +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/recall-webhook.ts @@ -1,33 +1,31 @@ import { isNull, isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; -import { Response } from 'twenty-sdk/logic-function'; +import { PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/process-recall-webhook-logic-function-universal-identifier'; import { RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/recall-webhook-logic-function-universal-identifier'; import { RECALL_WEBHOOK_SECRET_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-webhook-secret-env-var-name'; -import { handleRecallWebhook } from 'src/logic-functions/flows/handle-recall-webhook.util'; +import { extractTwentyWorkspaceIdFromRecallWebhook } from 'src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util'; import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util'; import { verifyRecallWebhookSignature } from 'src/logic-functions/recall-api/verify-recall-webhook-signature.util'; import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util'; import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util'; -// Non-2xx makes Svix retry; a returned plain object would 200-ack permanently. -const rejectWebhook = (status: number, error: string): Response => { - console.error(`[call-recorder] webhook rejected: ${error}`); - - return new Response({ error }, { status }); +type RecallWebhookResolverResult = { + workspaceId: string; + targetLogicFunctionUniversalIdentifier: string; + payload: RecallWebhookBody; }; -export const recallWebhookRouteHandler = async ( +// A thrown error becomes a non-2xx, which makes Svix retry; a returned result dispatches to the target. +export const recallWebhookRouteHandler = ( routePayload: RoutePayload, -): Promise => { +): RecallWebhookResolverResult => { const webhookSecret = getApplicationVariableValue( RECALL_WEBHOOK_SECRET_ENV_VAR_NAME, ); if (!isNonEmptyString(webhookSecret)) { - return rejectWebhook( - 500, + throw new Error( 'RECALL_WEBHOOK_SECRET server variable is not set. A server admin must copy it from the Recall webhook endpoint settings and set it on the Call Recorder application registration.', ); } @@ -35,8 +33,7 @@ export const recallWebhookRouteHandler = async ( const { rawBody } = routePayload; if (isUndefined(rawBody)) { - return rejectWebhook( - 500, + throw new Error( 'Raw request body was not forwarded by the server; cannot verify the webhook signature', ); } @@ -48,34 +45,39 @@ export const recallWebhookRouteHandler = async ( }); if (!signatureCheck.valid) { - return rejectWebhook( - 401, - `Invalid webhook signature: ${signatureCheck.error}`, + throw new Error(`Invalid webhook signature: ${signatureCheck.error}`); + } + + const body = routePayload.body; + + if (isUndefined(body) || isNull(body)) { + throw new Error('Webhook payload was empty'); + } + + const workspaceId = extractTwentyWorkspaceIdFromRecallWebhook(body); + + if (!isNonEmptyString(workspaceId)) { + throw new Error( + 'Webhook payload is missing the Twenty workspace id in the Recall bot metadata', ); } - if (isUndefined(routePayload.body) || isNull(routePayload.body)) { - return rejectWebhook(400, 'Webhook payload was empty'); - } - - return handleRecallWebhook({ - client: new CoreApiClient(), - body: routePayload.body, - }); + return { + workspaceId, + targetLogicFunctionUniversalIdentifier: + PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + payload: body, + }; }; export default defineLogicFunction({ universalIdentifier: RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'recall-webhook', description: - 'Receives Recall.ai webhook events and updates the matching CallRecording lifecycle status.', + 'Verifies Recall.ai webhook signatures and resolves the target workspace for the matching CallRecording update.', timeoutSeconds: 30, handler: recallWebhookRouteHandler, - serverWebhookTriggerSettings: { - workspaceIdResolver: { - source: 'body', - path: 'data.bot.metadata.twentyWorkspaceId', - }, + serverRouteTriggerSettings: { forwardedRequestHeaders: [ 'webhook-id', 'webhook-timestamp', diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-media-file.type.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-media-file.type.ts new file mode 100644 index 0000000000..56de4133ed --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-media-file.type.ts @@ -0,0 +1 @@ +export type CallRecordingMediaFile = { fileId: string; label: string }; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-update-fields.type.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-update-fields.type.ts new file mode 100644 index 0000000000..6455bb2e0a --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-update-fields.type.ts @@ -0,0 +1,20 @@ +import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; +import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; +import { type CallRecordingMediaFile } from 'src/logic-functions/types/call-recording-media-file.type'; + +export type CallRecordingUpdateFields = Partial<{ + // null clears a previously synced title when the calendar title disappears. + title: string | null; + status: CallRecordingStatus; + recordingRequestStatus: CallRecordingRequestStatus; + startedAt: string; + endedAt: string; + calendarEventId: string; + // null clears stale app-owned state on cancel/eject or reschedule. + externalBotId: string | null; + externalRecordingId: string; + callRecorderFailureReason: string | null; + transcript: Record; + audio: CallRecordingMediaFile[]; + video: CallRecordingMediaFile[]; +}>; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-bot-metadata.type.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-bot-metadata.type.ts deleted file mode 100644 index 196f08cf02..0000000000 --- a/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-bot-metadata.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type RecallBotMetadata = { - twentyWorkspaceId: string; - twentyCallRecordingId: string; - twentyCalendarEventId: string; - twentyRealMeetingKey: string; -}; diff --git a/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-routing-metadata.type.ts b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-routing-metadata.type.ts new file mode 100644 index 0000000000..bc6be6aec3 --- /dev/null +++ b/packages/twenty-apps/public/call-recorder/src/logic-functions/types/recall-routing-metadata.type.ts @@ -0,0 +1,4 @@ +export type RecallRoutingMetadata = { + twentyWorkspaceId: string; + twentyCallRecordingId: string; +}; diff --git a/packages/twenty-apps/public/call-recorder/yarn.lock b/packages/twenty-apps/public/call-recorder/yarn.lock index 32ea508482..a93c98ac4d 100644 --- a/packages/twenty-apps/public/call-recorder/yarn.lock +++ b/packages/twenty-apps/public/call-recorder/yarn.lock @@ -143,6 +143,15 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:^1.7.0": + version: 1.11.1 + resolution: "@emnapi/runtime@npm:1.11.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/04332fb62076afc440aa23316c04bec42f584ca8b074e5507d08e2b33a47cbe0493b1aadb8f3c1057b64ae1e17f5bde1a7bc37f7facc9d0bc25c18197cbd366f + languageName: node + linkType: hard + "@emnapi/wasi-threads@npm:1.2.1": version: 1.2.1 resolution: "@emnapi/wasi-threads@npm:1.2.1" @@ -500,6 +509,233 @@ __metadata: languageName: node linkType: hard +"@img/colour@npm:^1.0.0": + version: 1.1.0 + resolution: "@img/colour@npm:1.1.0" + checksum: 10c0/2ebea2c0bbaee73b99badcefa04e1e71d83f36e5369337d3121dca841f4569533c4e2faddda6d62dd247f0d5cca143711f9446c59bcce81e427ba433a7a94a17 + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@inquirer/ansi@npm:^2.0.7": version: 2.0.7 resolution: "@inquirer/ansi@npm:2.0.7" @@ -1002,8 +1238,8 @@ __metadata: oxlint: "npm:^0.16.0" react: "npm:^19.0.0" react-dom: "npm:^19.0.0" - twenty-client-sdk: "npm:2.15.0" - twenty-sdk: "npm:2.15.0" + twenty-client-sdk: "npm:2.16.0" + twenty-sdk: "npm:2.16.0" typescript: "npm:^5.9.3" vite-tsconfig-paths: "npm:^4.2.1" vitest: "npm:^4.1.9" @@ -1539,7 +1775,7 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.3": +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.1.2": version: 2.1.2 resolution: "detect-libc@npm:2.1.2" checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 @@ -2775,6 +3011,99 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.3": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"sharp@npm:^0.34.5": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f + languageName: node + linkType: hard + "siginfo@npm:^2.0.0": version: 2.0.0 resolution: "siginfo@npm:2.0.0" @@ -3002,22 +3331,22 @@ __metadata: languageName: node linkType: hard -"twenty-client-sdk@npm:2.15.0": - version: 2.15.0 - resolution: "twenty-client-sdk@npm:2.15.0" +"twenty-client-sdk@npm:2.16.0": + version: 2.16.0 + resolution: "twenty-client-sdk@npm:2.16.0" dependencies: "@genql/runtime": "npm:^2.10.0" esbuild: "npm:^0.28.1" graphql: "npm:^16.8.1" lodash: "npm:^4.17.21" prettier: "npm:^3.8.3" - checksum: 10c0/ea5143511ec3d42a0c2eaabda8833bac5c73c25fe04ba967d76b47b22c67468dd59d9e748a00e99a8085ffdb02d6cc5f41556f9e85a9785b2909b63adcb4205f + checksum: 10c0/f990220bec103a04ca50dc8bb38c295e94a4e51b27a3188f4a0ae515c204b76bf87f5591739eb705b97e7aa5a9aa6a215d0d4da05297e50a0392801ea64a2234 languageName: node linkType: hard -"twenty-sdk@npm:2.15.0": - version: 2.15.0 - resolution: "twenty-sdk@npm:2.15.0" +"twenty-sdk@npm:2.16.0": + version: 2.16.0 + resolution: "twenty-sdk@npm:2.16.0" dependencies: "@sniptt/guards": "npm:^0.2.0" axios: "npm:^1.16.0" @@ -3034,13 +3363,14 @@ __metadata: react: "npm:^19.2.0" react-dom: "npm:^19.2.0" semver: "npm:7.6.3" + sharp: "npm:^0.34.5" tinyglobby: "npm:^0.2.15" - twenty-client-sdk: "npm:2.15.0" + twenty-client-sdk: "npm:2.16.0" typescript: "npm:^5.9.3" uuid: "npm:^13.0.2" bin: twenty: dist/cli.cjs - checksum: 10c0/0fe9a3653f3adaa54eb8398e520328ed2fd51b4c4284b4ed4105dfd92e3c866f2e9b5c6933f952e974684cd38169cdb26d64cfe06f058f4f4cb5bb1f6be7d2fc + checksum: 10c0/528f275f8bda022061a52c3563af514d2257f291fc5a232be90ca6011c50ff18a250984b6c68fc8e4c690644c92e1ccb0b89c511b8a19d832455c8860f2751a8 languageName: node linkType: hard