feat(call-recorder): support Meeting BaaS provider - #4
Conversation
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary by CodeRabbit
WalkthroughAdds Meeting BaaS as a call-recorder provider, including new server variables, API helpers, webhook handling, provider-routing wrappers, and updated scheduling, cancellation, and convergence flows. ChangesMeeting BaaS call recorder integration
Sequence Diagram(s)sequenceDiagram
participant meetingBaasWebhookRouteHandler
participant handleMeetingBaasWebhook
participant parseMeetingBaasWebhookEvent
participant ingestCallRecordingMediaFromUrls
participant normalizeMeetingBaasTranscript
participant updateCallRecording
meetingBaasWebhookRouteHandler->>handleMeetingBaasWebhook: forward validated webhook body
handleMeetingBaasWebhook->>parseMeetingBaasWebhookEvent: parse Meeting BaaS event
alt COMPLETED event
handleMeetingBaasWebhook->>ingestCallRecordingMediaFromUrls: ingest audio/video URLs
handleMeetingBaasWebhook->>normalizeMeetingBaasTranscript: normalize transcript payload
end
handleMeetingBaasWebhook->>updateCallRecording: persist status, ids, and transcript
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts`:
- Around line 685-700: The transcript fetch in
converge-diverged-call-recordings.util.ts should be hardened before parsing the
body. In the transcript retrieval logic around fetch/transcriptRecord, validate
that the response has an expected JSON Content-Type and reject unexpected or
empty bodies before calling response.json(). Also add a bounded body-size check
or use a safer read path so large malformed responses cannot stall the
per-candidate convergence loop, and consider reducing the AbortSignal.timeout
used in this flow to a more appropriate per-recording limit.
- Around line 573-581: The failure gate in convergeDivergedCallRecordings logic
is short-circuiting too early for Meeting BaaS because
extractMeetingBaasConvergence populates externalRecordingId from bot.bot_id even
when no media artifact exists. Update the terminal-artifact check in
converge-diverged-call-recordings.util.ts so it keys off actual artifact
availability for Meeting BaaS (for example, audioUrl/videoUrl presence from the
Meeting BaaS convergence data) rather than !isUndefined(externalRecordingId),
and keep the existing candidate.status, updateData.status,
convergence.isRecordingDone, and hasRecordingArtifactPath conditions intact.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-meeting-baas-webhook.util.ts`:
- Around line 79-89: The `handleMeetingBaasWebhook` completion path is
hardcoding `hasAudio` and `hasVideo` to false, which causes
`ingestCallRecordingMediaFromUrls` to reprocess media on repeated
`bot.completed` events. Update this flow so the flags come from a richer
`callRecording` fetch that includes existing media presence, or move the
dedup/skip check into `ingestCallRecordingMediaFromUrls` itself; use the `status
=== CallRecordingStatus.COMPLETED` block and `ingestCallRecordingMediaFromUrls`
call as the main fix points.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-api-config.util.ts`:
- Around line 60-70: The config builder in get-meeting-baas-api-config.util.ts
returns apiKey and callbackSecret without trimming, unlike botName and
callbackUrl. Update the returned config object to normalize these two values
with trim() before use so the x-meeting-baas-api-key header and x-mb-secret
comparison do not include accidental whitespace; keep the change localized to
the getMeetingBaasApiConfig logic and preserve the existing fallback behavior
for botName.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-bot.util.ts`:
- Line 32: The getMeetingBaasBot result handling currently treats a missing
payload as success by returning an empty bot object, which can mask malformed
responses. Update the get-meeting-baas-bot.util.ts logic around
getMeetingBaasBot so that only a real bot payload from result.data?.data returns
ok: true, and any absent/invalid payload is returned as a failure instead of
defaulting to {}. Keep the response shape aligned with the existing bot/result
types used by this utility.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/meeting-baas-api-request.util.ts`:
- Around line 28-35: The Meeting BaaS fetch in meeting-baas-api-request.util.ts
currently has no timeout, so slow upstream calls can hang the logic-function.
Update the request in the request helper that builds the fetch options to attach
an AbortSignal created with AbortSignal.timeout(...), and pass it as the signal
alongside method, headers, and optional body. Keep the change localized to the
request utility so all Meeting BaaS calls fail fast consistently.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/schedule-meeting-baas-bot.util.ts`:
- Around line 77-85: In schedule-meeting-baas-bot.util.ts, the external bot ID
check only rejects undefined, so `result.data?.data?.bot_id` can still be null
or an empty string and later break downstream API calls. Update the validation
around `externalBotId` to require a non-empty string before returning success,
and keep the existing error return path when the value is missing or invalid.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.ts`:
- Line 52: The webhook handler in meeting-baas-webhook is using a function
timeout that is shorter than the internal COMPLETED ingestion work, so adjust
the budget to match the downstream media and transcript timeouts or reduce those
internal timeouts accordingly. Update the logic around the webhook entrypoint
and the COMPLETED path that calls the media download and transcript fetch
helpers so the total execution window safely covers both sequential steps before
updateCallRecording runs.
- Line 33: The x-mb-secret check in meeting-baas-webhook should use a
constant-time comparison instead of a direct string inequality. Update the
secret validation logic in the webhook handler to convert both values to
equal-length buffers and compare them with crypto.timingSafeEqual, keeping the
existing webhookSecret and routePayload.headers['x-mb-secret'] flow intact.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/providers/get-call-recorder-provider.util.ts`:
- Line 10: Normalize the provider input inside getCallRecorderProvider by
trimming whitespace and converting to lowercase before the comparison, so values
like stray-spaced or differently cased meeting-baas still resolve correctly.
Update the matching logic around the rawProvider return path to compare against
the normalized value rather than the original string, and keep the fallback to
recall only for truly unknown providers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 87d67f73-aba4-49eb-981c-0815be8e5023
📒 Files selected for processing (32)
packages/twenty-apps/public/call-recorder/README.mdpackages/twenty-apps/public/call-recorder/src/application-config.tspackages/twenty-apps/public/call-recorder/src/constants/meeting-baas-webhook-logic-function-universal-identifier.tspackages/twenty-apps/public/call-recorder/src/logic-functions/constants/call-recorder-provider-env-var-name.tspackages/twenty-apps/public/call-recorder/src/logic-functions/constants/meeting-baas-api-base-url-env-var-name.tspackages/twenty-apps/public/call-recorder/src/logic-functions/constants/meeting-baas-api-key-env-var-name.tspackages/twenty-apps/public/call-recorder/src/logic-functions/constants/meeting-baas-callback-secret-env-var-name.tspackages/twenty-apps/public/call-recorder/src/logic-functions/constants/meeting-baas-callback-url-env-var-name.tspackages/twenty-apps/public/call-recorder/src/logic-functions/domain/map-meeting-baas-status-to-call-recording-status.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/cancel-call-recording-request.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/ensure-call-recorder.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-meeting-baas-webhook.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/ingest-call-recording-media.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/reap-orphaned-call-recorders.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/flows/reschedule-call-recording-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/cancel-meeting-baas-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-api-config.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/meeting-baas-api-request.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/normalize-meeting-baas-transcript.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/parse-meeting-baas-webhook-event.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/reschedule-meeting-baas-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/schedule-meeting-baas-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.tspackages/twenty-apps/public/call-recorder/src/logic-functions/providers/call-recorder-provider.type.tspackages/twenty-apps/public/call-recorder/src/logic-functions/providers/cancel-call-recorder-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/providers/get-call-recorder-provider.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/providers/reschedule-call-recorder-bot.util.tspackages/twenty-apps/public/call-recorder/src/logic-functions/providers/schedule-call-recorder-bot.util.ts
| if ( | ||
| candidate.status === CallRecordingStatus.COMPLETED || | ||
| updateData.status === CallRecordingStatus.FAILED || | ||
| !convergence.isRecordingDone || | ||
| !isUndefined(externalRecordingId) || | ||
| hasRecordingArtifactPath({ candidate, updateData }) | ||
| ) { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd 'get-meeting-baas-bot.util.ts' --exec cat -n {}
fd 'has-recording-artifact-path' -e ts --exec cat -n {}Repository: Meeting-BaaS/twenty
Length of output: 1336
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant implementation and helper.
fd 'converge-diverged-call-recordings.util.ts' packages -x sed -n '1,260p' {}
printf '\n--- SPLIT ---\n'
fd 'converge-diverged-call-recordings.util.ts' packages -x sed -n '260,430p' {}
printf '\n--- SPLIT ---\n'
fd 'converge-diverged-call-recordings.util.ts' packages -x sed -n '430,660p' {}
printf '\n--- SPLIT ---\n'
fd 'converge-diverged-call-recordings.util.ts' packages -x sed -n '660,760p' {}
printf '\n--- SPLIT ---\n'
fd 'has-recording-artifact-path' packages -e ts -x sed -n '1,220p' {}
printf '\n--- SPLIT ---\n'
fd 'extract-meeting-baas-convergence' packages -e ts -x sed -n '1,260p' {}
printf '\n--- SPLIT ---\n'
fd 'get-meeting-baas-bot.util.ts' packages -x sed -n '1,220p' {}Repository: Meeting-BaaS/twenty
Length of output: 22636
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrow down the exact files if the initial names differ.
git ls-files 'packages/**/converge-diverged-call-recordings.util.ts' \
'packages/**/has-recording-artifact-path*.ts' \
'packages/**/extract-meeting-baas-convergence*.ts' \
'packages/**/get-meeting-baas-bot.util.ts'Repository: Meeting-BaaS/twenty
Length of output: 375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the convergence flow and the artifact-path helper.
files=(
"$(git ls-files 'packages/**/converge-diverged-call-recordings.util.ts' | head -n 1)"
"$(git ls-files 'packages/**/has-recording-artifact-path*.ts' | head -n 1)"
"$(git ls-files 'packages/**/extract-meeting-baas-convergence*.ts' | head -n 1)"
"$(git ls-files 'packages/**/get-meeting-baas-bot.util.ts' | head -n 1)"
)
for f in "${files[@]}"; do
[ -n "$f" ] || continue
echo "--- $f ---"
wc -l "$f"
sed -n '1,260p' "$f"
echo
doneRepository: Meeting-BaaS/twenty
Length of output: 10890
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for Meeting BaaS bot payload assumptions and any docs/examples.
rg -n --hidden --no-ignore-vcs "bot_id|transcriptionUrl|raw_transcription|audioUrl|videoUrl|meeting-baas" packages/twenty-apps/public/call-recorder -g '!**/dist/**' -g '!**/build/**'Repository: Meeting-BaaS/twenty
Length of output: 19158
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for Meeting BaaS payload assumptions and any docs/examples.
rg -n --hidden --no-ignore-vcs "bot_id|transcriptionUrl|raw_transcription|audioUrl|videoUrl|meeting-baas" packages/twenty-apps/public/call-recorder -g '!**/dist/**' -g '!**/build/**'Repository: Meeting-BaaS/twenty
Length of output: 19158
🌐 Web query:
Meeting BaaS API GET /v2/bots/{bot_id} response bot_id field documentation
💡 Result:
In the Meeting BaaS API v2, the bot_id field is a primary identifier used across multiple endpoints to reference a specific bot instance. When interacting with the bot management endpoints, the bot_id is passed as a path parameter in the URI, typically in the format /v2/bots/{bot_id} [1][2]. This field is a string-type identifier, commonly represented as a UUID [3][4]. Key points regarding the bot_id field: - Purpose: It serves as the unique reference for a deployed bot, allowing you to retrieve details, check status, obtain screenshots, remove the bot from a meeting, or manage its data [1][2]. - Usage in Requests: Methods such as getBotDetails, getBotStatus, leaveBot, and deleteBotData require the bot_id to be provided within the request path [1]. - Source of bot_id: The bot_id is generated and returned by the API upon the successful creation of a bot (e.g., via a POST request to /bots) [5][2][4]. - Webhook Payload: The bot_id is also included within the data object of webhook events sent by Meeting BaaS, allowing you to correlate status updates or completion events with the original bot deployment [3]. For precise implementation details, you can refer to the official Meeting BaaS API v2 documentation or the OpenAPI specification available in their documentation repository [6][7].
Citations:
- 1: https://github.com/Meeting-BaaS/sdk-generator/blob/main/API-REFERENCE-V2.md
- 2: https://www.meetingbaas.com/en/api/bots-api
- 3: https://docs.meetingbaas.com/api/getting-started/getting-the-data
- 4: https://github.com/Meeting-Baas/docs/blob/main/content/docs/typescript-sdk/complete-reference.mdx
- 5: https://docs.meetingbaas.com/typescript-sdk/complete-reference
- 6: https://github.com/Meeting-Baas/docs
- 7: https://docs.meetingbaas.com/
🌐 Web query:
Meeting BaaS API GET /v2/bots/{bot_id} response bot_id field documentation
💡 Result:
In the Meeting BaaS v2 API, the endpoint GET /v2/bots/{bot_id} is used to retrieve detailed information about a specific bot [1]. In the API response body, the bot_id field is a string that represents the unique identifier (UUID) assigned to the bot [1]. This identifier is consistent across the Meeting BaaS API and is required as a path parameter in subsequent requests related to that specific bot, such as checking its status, retrieving screenshots, leaving a meeting, or deleting its data [1][2]. Example response structure for GET /v2/bots/{bot_id}: { "success": true, "data": { "bot_id": "123e4567-e89b-12d3-a456-426614174000", "status": "completed", "meeting_url": "https://meet.google.com/...",... } } [1] The bot_id serves as the primary reference key for managing the bot's lifecycle and retrieving post-meeting data like recordings, transcripts, and speaker diarization [1].
Citations:
- 1: https://docs.meetingbaas.com/api-v2/getting-started/getting-the-data
- 2: https://github.com/Meeting-BaaS/sdk-generator/blob/main/API-REFERENCE-V2.md
Meeting BaaS can’t reach the terminal-artifact failure gate
packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts:573-581, 664-671
extractMeetingBaasConvergence() maps bot.bot_id into externalRecordingId, and GET /v2/bots/{bot_id} returns that field. That makes !isUndefined(externalRecordingId) short-circuit this gate for Meeting BaaS, so a completed bot with no audioUrl/videoUrl never transitions to failed. Gate this on actual artifact availability for Meeting BaaS instead of externalRecordingId.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts`
around lines 573 - 581, The failure gate in convergeDivergedCallRecordings logic
is short-circuiting too early for Meeting BaaS because
extractMeetingBaasConvergence populates externalRecordingId from bot.bot_id even
when no media artifact exists. Update the terminal-artifact check in
converge-diverged-call-recordings.util.ts so it keys off actual artifact
availability for Meeting BaaS (for example, audioUrl/videoUrl presence from the
Meeting BaaS convergence data) rather than !isUndefined(externalRecordingId),
and keep the existing candidate.status, updateData.status,
convergence.isRecordingDone, and hasRecordingArtifactPath conditions intact.
| try { | ||
| const response = await fetch(transcriptUrl, { | ||
| signal: AbortSignal.timeout(120_000), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| return undefined; | ||
| } | ||
|
|
||
| const transcript = await response.json(); | ||
| const transcriptRecord = asRecord(transcript); | ||
|
|
||
| return transcriptRecord?.transcript ?? transcript; | ||
| } catch { | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
Harden the provider transcript fetch.
The transcript body is parsed with no Content-Type or size guard, and the 120s AbortSignal.timeout is large for a per-candidate pass that may iterate many recordings. A malformed or very large response could delay/strain the convergence loop. Consider validating the response content type and bounding the body size before response.json().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/flows/converge-diverged-call-recordings.util.ts`
around lines 685 - 700, The transcript fetch in
converge-diverged-call-recordings.util.ts should be hardened before parsing the
body. In the transcript retrieval logic around fetch/transcriptRecord, validate
that the response has an expected JSON Content-Type and reject unexpected or
empty bodies before calling response.json(). Also add a bounded body-size check
or use a safer read path so large malformed responses cannot stall the
per-candidate convergence loop, and consider reducing the AbortSignal.timeout
used in this flow to a more appropriate per-recording limit.
| if (status === CallRecordingStatus.COMPLETED) { | ||
| Object.assign( | ||
| updateData, | ||
| await ingestCallRecordingMediaFromUrls({ | ||
| callRecordingId: callRecording.id, | ||
| hasAudio: false, | ||
| hasVideo: false, | ||
| audioUrl: webhookEvent.audioUrl, | ||
| videoUrl: webhookEvent.videoUrl, | ||
| }), | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -e ts call-recording-record.type.ts packages/twenty-apps/public/call-recorder/src --exec cat -n {}
rg -nP "audio|video" packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-record.type.tsRepository: Meeting-BaaS/twenty
Length of output: 767
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the webhook handler and the media-ingest helper signatures/usages.
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-meeting-baas-webhook.util.ts
printf '\n--- ingest helper ---\n'
rg -n "function ingestCallRecordingMediaFromUrls|const ingestCallRecordingMediaFromUrls|export .*ingestCallRecordingMediaFromUrls" packages/twenty-apps/public/call-recorder/src -A 40 -B 20
printf '\n--- callRecording media fields / updates ---\n'
rg -n "audioUrl|videoUrl|hasAudio|hasVideo|ingestCallRecordingMediaFromUrls|uploadFile|callRecording" packages/twenty-apps/public/call-recorder/src -A 2 -B 2Repository: Meeting-BaaS/twenty
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrowly inspect the helper implementation and nearby types, without running repo code.
fd -e ts ingest-call-recording-media-from-urls.util.ts packages/twenty-apps/public/call-recorder/src --exec sh -c 'echo "--- {} ---"; sed -n "1,220p" "$1"' sh {}
printf '\n--- record/wire types ---\n'
fd -e ts call-recording-record.type.ts packages/twenty-apps/public/call-recorder/src --exec sh -c 'echo "--- {} ---"; sed -n "1,120p" "$1"' sh {}
fd -e ts call-recording-wire.type.ts packages/twenty-apps/public/call-recorder/src --exec sh -c 'echo "--- {} ---"; sed -n "1,200p" "$1"' sh {}Repository: Meeting-BaaS/twenty
Length of output: 795
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the webhook handler and the helper implementation.
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-meeting-baas-webhook.util.ts
printf '\n--- helper lookup ---\n'
rg -n "ingestCallRecordingMediaFromUrls" packages/twenty-apps/public/call-recorder/src -A 30 -B 20
printf '\n--- record type ---\n'
sed -n '1,120p' packages/twenty-apps/public/call-recorder/src/logic-functions/types/call-recording-record.type.tsRepository: Meeting-BaaS/twenty
Length of output: 42318
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the helper and all call sites to determine what the boolean flags mean.
fd -e ts ingest-call-recording-media-from-urls.util.ts packages/twenty-apps/public/call-recorder/src --exec sh -c 'echo "--- {} ---"; sed -n "1,220p" "$1"' sh {}
printf '\n--- call sites ---\n'
rg -n "ingestCallRecordingMediaFromUrls\(" packages/twenty-apps/public/call-recorder/src -A 6 -B 6Repository: Meeting-BaaS/twenty
Length of output: 3883
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/data/find-call-recordings-by-filter.util.ts
printf '\n--- update-call-recording util ---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.ts
printf '\n--- any record types with audio/video ---\n'
rg -n "type CallRecording.*audio|type CallRecording.*video|audio\?:|video\?:|audio:|video:" packages/twenty-apps/public/call-recorder/src/logic-functions/types packages/twenty-apps/public/call-recorder/src/logic-functions/data -A 2 -B 2Repository: Meeting-BaaS/twenty
Length of output: 5805
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the record-reading path to see whether media fields are available there.
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/data/find-call-recordings-by-filter.util.ts
printf '\n--- update util ---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/data/update-call-recording.util.tsRepository: Meeting-BaaS/twenty
Length of output: 4967
Don’t hardcode both media flags to false here. Every repeated bot.completed will re-download and re-upload any returned audio/video. This query shape doesn’t include existing media presence, so derive the flags from a richer record fetch or move the skip logic into ingestCallRecordingMediaFromUrls.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/flows/handle-meeting-baas-webhook.util.ts`
around lines 79 - 89, The `handleMeetingBaasWebhook` completion path is
hardcoding `hasAudio` and `hasVideo` to false, which causes
`ingestCallRecordingMediaFromUrls` to reprocess media on repeated
`bot.completed` events. Update this flow so the flags come from a richer
`callRecording` fetch that includes existing media presence, or move the
dedup/skip check into `ingestCallRecordingMediaFromUrls` itself; use the `status
=== CallRecordingStatus.COMPLETED` block and `ingestCallRecordingMediaFromUrls`
call as the main fix points.
| return { | ||
| success: true, | ||
| config: { | ||
| apiKey, | ||
| baseUrl: normalizeBaseUrl(rawBaseUrl), | ||
| botName: isNonEmptyString(rawBotName) | ||
| ? rawBotName.trim() | ||
| : DEFAULT_CALL_RECORDER_NAME, | ||
| callbackUrl: callbackUrl.trim(), | ||
| callbackSecret, | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Inconsistent trimming of apiKey and callbackSecret.
callbackUrl and botName are trimmed, but apiKey and callbackSecret are returned as-is. A trailing newline/space (common when copying secrets into env config) would flow into the x-meeting-baas-api-key header and into the x-mb-secret comparison, causing hard-to-diagnose auth failures. Trim them for consistency unless whitespace is intentionally significant.
♻️ Proposed fix
config: {
- apiKey,
+ apiKey: apiKey.trim(),
baseUrl: normalizeBaseUrl(rawBaseUrl),
botName: isNonEmptyString(rawBotName)
? rawBotName.trim()
: DEFAULT_CALL_RECORDER_NAME,
callbackUrl: callbackUrl.trim(),
- callbackSecret,
+ callbackSecret: callbackSecret.trim(),
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return { | |
| success: true, | |
| config: { | |
| apiKey, | |
| baseUrl: normalizeBaseUrl(rawBaseUrl), | |
| botName: isNonEmptyString(rawBotName) | |
| ? rawBotName.trim() | |
| : DEFAULT_CALL_RECORDER_NAME, | |
| callbackUrl: callbackUrl.trim(), | |
| callbackSecret, | |
| }, | |
| return { | |
| success: true, | |
| config: { | |
| apiKey: apiKey.trim(), | |
| baseUrl: normalizeBaseUrl(rawBaseUrl), | |
| botName: isNonEmptyString(rawBotName) | |
| ? rawBotName.trim() | |
| : DEFAULT_CALL_RECORDER_NAME, | |
| callbackUrl: callbackUrl.trim(), | |
| callbackSecret: callbackSecret.trim(), | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-api-config.util.ts`
around lines 60 - 70, The config builder in get-meeting-baas-api-config.util.ts
returns apiKey and callbackSecret without trimming, unlike botName and
callbackUrl. Update the returned config object to normalize these two values
with trim() before use so the x-meeting-baas-api-key header and x-mb-secret
comparison do not include accidental whitespace; keep the change localized to
the getMeetingBaasApiConfig logic and preserve the existing fallback behavior
for botName.
| return result; | ||
| } | ||
|
|
||
| return { ok: true, bot: result.data?.data ?? {} }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not treat missing bot payload as success.
Line 32 currently returns success with {} when result.data?.data is absent. That hides malformed API responses and can derail convergence logic that expects real bot state.
Suggested fix
- return { ok: true, bot: result.data?.data ?? {} };
+ if (!result.data?.data) {
+ return {
+ ok: false,
+ status: result.status,
+ errorMessage:
+ 'Meeting BaaS returned success without data.data for bot details',
+ };
+ }
+
+ return { ok: true, bot: result.data.data };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return { ok: true, bot: result.data?.data ?? {} }; | |
| if (!result.data?.data) { | |
| return { | |
| ok: false, | |
| status: result.status, | |
| errorMessage: | |
| 'Meeting BaaS returned success without data.data for bot details', | |
| }; | |
| } | |
| return { ok: true, bot: result.data.data }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/get-meeting-baas-bot.util.ts`
at line 32, The getMeetingBaasBot result handling currently treats a missing
payload as success by returning an empty bot object, which can mask malformed
responses. Update the get-meeting-baas-bot.util.ts logic around
getMeetingBaasBot so that only a real bot payload from result.data?.data returns
ok: true, and any absent/invalid payload is returned as a failure instead of
defaulting to {}. Keep the response shape aligned with the existing bot/result
types used by this utility.
| const response = await fetch(`${config.baseUrl}${path}`, { | ||
| method, | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| 'x-meeting-baas-api-key': config.apiKey, | ||
| }, | ||
| ...(body === undefined ? {} : { body: JSON.stringify(body) }), | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
🧩 Analysis chain
🌐 Web query:
Does AbortSignal.timeout have support in Node.js fetch and what Node version introduced it?
💡 Result:
Yes, AbortSignal.timeout is supported in Node.js and can be used with the native fetch API [1][2][3]. The AbortSignal.timeout(delay) method was introduced in Node.js versions 17.3.0 and 16.14.0 [1][4]. When using this with fetch, the signal will abort the request once the specified delay has passed, causing the fetch promise to reject with a TimeoutError DOMException [2][5][3]. While support exists, users have occasionally reported issues regarding the integration of AbortSignal.timeout with certain fetch implementations or edge-case behaviors in older versions [6]. As of modern Node.js versions, it is a standard way to implement timeouts for network requests [1][3].
Citations:
- 1: https://nodejs.org/api/globals.html
- 2: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
- 3: https://devcraftly.com/nodejs/abortcontroller/
- 4: https://r2.nodejs.org/docs/v20.6.0/api/globals.html
- 5: Introduce AbortSignal.timeout() whatwg/dom#1032
- 6: fetch() AbortSignal.timeout() does not work as expected nodejs/undici#1926
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect repository files that may define the runtime and existing timeout patterns.
git ls-files | rg '(^|/)(package\.json|tsconfig\.json|\.nvmrc|\.node-version|README\.md|package-lock\.json|pnpm-lock\.yaml|yarn.lock)$' || true
printf '\n--- package.json candidates ---\n'
for f in $(git ls-files | rg '(^|/)package\.json$'); do
echo "### $f"
sed -n '1,220p' "$f" | rg -n '"engines"|"node"|AbortSignal|fetch' || true
echo
done
printf '\n--- direct timeout usage around meeting-baas request helper ---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/meeting-baas-api-request.util.tsRepository: Meeting-BaaS/twenty
Length of output: 24793
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where this logic-function package declares its runtime assumptions.
fd -a -t f 'package.json|.nvmrc|.node-version|README.md' packages/twenty-apps/public/call-recorder . 2>/dev/null || true
printf '\n--- search for Node/runtime references ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' \
'AbortSignal\.timeout|AbortController|fetch\(|node\s+version|engines|runtime|nodejs|node\.js' \
packages/twenty-apps/public/call-recorder . 2>/dev/null | sed -n '1,220p'Repository: Meeting-BaaS/twenty
Length of output: 32391
Add a timeout to the Meeting BaaS request
packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/meeting-baas-api-request.util.ts runs on Node ^24.5.0, so AbortSignal.timeout(...) is available here. Attach a signal so slow or unresponsive upstream calls fail fast instead of hanging the logic-function.
🛠️ Suggested change
const response = await fetch(`${config.baseUrl}${path}`, {
method,
headers: {
'content-type': 'application/json',
'x-meeting-baas-api-key': config.apiKey,
},
+ signal: AbortSignal.timeout(30_000),
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch(`${config.baseUrl}${path}`, { | |
| method, | |
| headers: { | |
| 'content-type': 'application/json', | |
| 'x-meeting-baas-api-key': config.apiKey, | |
| }, | |
| ...(body === undefined ? {} : { body: JSON.stringify(body) }), | |
| }); | |
| const response = await fetch(`${config.baseUrl}${path}`, { | |
| method, | |
| headers: { | |
| 'content-type': 'application/json', | |
| 'x-meeting-baas-api-key': config.apiKey, | |
| }, | |
| signal: AbortSignal.timeout(30_000), | |
| ...(body === undefined ? {} : { body: JSON.stringify(body) }), | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/meeting-baas-api-request.util.ts`
around lines 28 - 35, The Meeting BaaS fetch in meeting-baas-api-request.util.ts
currently has no timeout, so slow upstream calls can hang the logic-function.
Update the request in the request helper that builds the fetch options to attach
an AbortSignal created with AbortSignal.timeout(...), and pass it as the signal
alongside method, headers, and optional body. Keep the change localized to the
request utility so all Meeting BaaS calls fail fast consistently.
| const externalBotId = result.data?.data?.bot_id; | ||
|
|
||
| if (isUndefined(externalBotId)) { | ||
| return { | ||
| ok: false, | ||
| status: null, | ||
| errorMessage: | ||
| 'Meeting BaaS created a scheduled bot but did not return data.bot_id', | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Validate data.bot_id as a non-empty string.
Line 79 only rejects undefined; invalid runtime values like '' (and null) can be accepted as externalBotId, causing downstream API operations to fail.
Suggested fix
- const externalBotId = result.data?.data?.bot_id;
-
- if (isUndefined(externalBotId)) {
+ const externalBotId = result.data?.data?.bot_id;
+ if (!isNonEmptyString(externalBotId)) {
return {
ok: false,
status: null,
errorMessage:
'Meeting BaaS created a scheduled bot but did not return data.bot_id',
};
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-api/schedule-meeting-baas-bot.util.ts`
around lines 77 - 85, In schedule-meeting-baas-bot.util.ts, the external bot ID
check only rejects undefined, so `result.data?.data?.bot_id` can still be null
or an empty string and later break downstream API calls. Update the validation
around `externalBotId` to require a non-empty string before returning success,
and keep the existing error return path when the value is missing or invalid.
| ); | ||
| } | ||
|
|
||
| if (routePayload.headers['x-mb-secret'] !== webhookSecret) { |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP "timingSafeEqual|from ['\"]crypto['\"]|require\(['\"]crypto['\"]\)" packages/twenty-apps/public/call-recorder/src
fd -e ts . packages/twenty-apps/public/call-recorder/src/logic-functions/utils | xargs rg -ln "secret|compare" 2>/dev/nullRepository: Meeting-BaaS/twenty
Length of output: 974
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.ts
printf '\n---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts
printf '\n---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.tsRepository: Meeting-BaaS/twenty
Length of output: 10789
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.ts
printf '\n---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts
printf '\n---\n'
sed -n '1,220p' packages/twenty-apps/public/call-recorder/src/logic-functions/__tests__/recall-webhook.test.tsRepository: Meeting-BaaS/twenty
Length of output: 10789
Use a constant-time comparison for x-mb-secret. crypto.timingSafeEqual is already available in this package, so compare equal-length buffers instead of !==.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.ts`
at line 33, The x-mb-secret check in meeting-baas-webhook should use a
constant-time comparison instead of a direct string inequality. Update the
secret validation logic in the webhook handler to convert both values to
equal-length buffers and compare them with crypto.timingSafeEqual, keeping the
existing webhookSecret and routePayload.headers['x-mb-secret'] flow intact.
| name: 'meeting-baas-webhook', | ||
| description: | ||
| 'Receives Meeting BaaS webhook events and updates the matching CallRecording lifecycle status.', | ||
| timeoutSeconds: 30, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
timeoutSeconds: 30 is shorter than downstream media/transcript timeouts (120s), so COMPLETED events can be force-aborted.
On COMPLETED, the handler downloads audio/video (MEDIA_DOWNLOAD_TIMEOUT_MS = 120_000) and fetches the transcript (AbortSignal.timeout(120_000) in handle-meeting-baas-webhook.util.ts Line 178), each sequentially. With a 30s function budget, the logic function can be terminated mid-ingestion, leaving the recording partially updated and never reaching updateCallRecording. Align the budget with the internal timeouts (raise timeoutSeconds, or lower the download/fetch timeouts to fit within it).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/meeting-baas-webhook.ts`
at line 52, The webhook handler in meeting-baas-webhook is using a function
timeout that is shorter than the internal COMPLETED ingestion work, so adjust
the budget to match the downstream media and transcript timeouts or reduce those
internal timeouts accordingly. Update the logic around the webhook entrypoint
and the COMPLETED path that calls the media download and transcript fetch
helpers so the total execution window safely covers both sequential steps before
updateCallRecording runs.
| CALL_RECORDER_PROVIDER_ENV_VAR_NAME, | ||
| ); | ||
|
|
||
| return rawProvider === 'meeting-baas' ? 'meeting-baas' : 'recall'; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Normalize the raw provider before matching to avoid silent fallback.
A value like ' meeting-baas' (stray whitespace) or 'Meeting-BaaS' will not equal 'meeting-baas' and silently resolves to 'recall', sending the operator down the wrong provider path without any signal. Trim and lowercase before comparing.
♻️ Proposed normalization
- return rawProvider === 'meeting-baas' ? 'meeting-baas' : 'recall';
+ return rawProvider?.trim().toLowerCase() === 'meeting-baas'
+ ? 'meeting-baas'
+ : 'recall';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return rawProvider === 'meeting-baas' ? 'meeting-baas' : 'recall'; | |
| return rawProvider?.trim().toLowerCase() === 'meeting-baas' | |
| ? 'meeting-baas' | |
| : 'recall'; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/twenty-apps/public/call-recorder/src/logic-functions/providers/get-call-recorder-provider.util.ts`
at line 10, Normalize the provider input inside getCallRecorderProvider by
trimming whitespace and converting to lowercase before the comparison, so values
like stray-spaced or differently cased meeting-baas still resolve correctly.
Update the matching logic around the rawProvider return path to compare against
the normalized value rather than the original string, and keep the fallback to
recall only for truly unknown providers.
Summary
Verification
Notes