{
  "application": {
    "universalIdentifier": "8da4b8b5-5edf-4880-b51f-ab6e679ec617",
    "displayName": "Call Recorder",
    "description": "Capture every customer conversation automatically. A call recorder joins eligible meetings and records calls for you.",
    "logo": "public/logo.svg",
    "category": "Productivity",
    "author": "Twenty",
    "galleryImages": [
      "public/gallery/call-recorder-cover.png"
    ],
    "billing": {
      "description": "$1.00 per recorded hour, prorated by duration. Billed to your Twenty credits.",
      "operations": {
        "recordCall": {
          "operationType": "CALL_RECORDING",
          "label": "Call recording"
        }
      }
    },
    "applicationVariables": {
      "CALL_RECORDER_CALENDAR_BOT_SCHEDULING_ENABLED": {
        "universalIdentifier": "e1cf956b-356f-468d-b5d9-044ed5fd9e5b",
        "label": "Record my calendar meetings",
        "description": "Whether the recorder is scheduled for every upcoming calendar meeting with a supported video link. Set to false to stop scheduling bots and cancel the ones already scheduled.",
        "isSecret": false,
        "type": "BOOLEAN",
        "value": true
      },
      "CALL_RECORDER_NAME": {
        "universalIdentifier": "c54cbacd-ad10-40b4-9056-7aaf23846d64",
        "label": "Recorder name",
        "description": "Display name the call recorder uses when it joins a call.",
        "isSecret": false,
        "type": "TEXT",
        "value": "Twenty.com"
      },
      "CALL_RECORDER_RECORDING_NOTICE_ENABLED": {
        "universalIdentifier": "fbf5f1ef-34c7-4411-b392-e314f6d064d0",
        "label": "Send recording notice",
        "description": "Whether the bot sends a recording notice in supported meeting chats when it joins.",
        "isSecret": false,
        "type": "BOOLEAN",
        "value": true
      },
      "CALL_RECORDER_RECORDING_NOTICE_MESSAGE": {
        "universalIdentifier": "7103451a-2c46-4c16-bc2c-a9f28b329ee5",
        "label": "Recording notice message",
        "description": "Message sent in supported meeting chats. Limited to 500 characters for Google Meet compatibility; longer values are truncated.",
        "isSecret": false,
        "type": "TEXT",
        "value": "👋 This meeting is being recorded and transcribed to create notes and a summary. If you prefer not to be recorded, please tell the host now. Powered by Twenty · twenty.com"
      },
      "CALL_RECORDER_JOIN_EARLY_MINUTES": {
        "universalIdentifier": "0568ebb2-3f64-47de-8c0d-d367dfbb7462",
        "label": "Join early (minutes)",
        "description": "How many minutes before the meeting start time the bot should join. Set to 0 to join at the scheduled start time.",
        "isSecret": false,
        "type": "NUMBER",
        "value": 1
      },
      "CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS": {
        "universalIdentifier": "12e4e14d-d539-4d07-b477-4773539dd20b",
        "label": "Waiting room timeout (seconds)",
        "description": "How many seconds the bot waits in a meeting lobby before giving up and leaving.",
        "isSecret": false,
        "type": "NUMBER",
        "value": 1200
      },
      "CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS": {
        "universalIdentifier": "241180e8-d864-4160-ad02-db44a9e8d395",
        "label": "No-one joined timeout (seconds)",
        "description": "How many seconds the bot stays in an empty meeting when no one else ever joins.",
        "isSecret": false,
        "type": "NUMBER",
        "value": 1200
      },
      "CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS": {
        "universalIdentifier": "c866ddd4-fb7b-4cb4-8ad1-5599755e495c",
        "label": "Everyone left timeout (seconds)",
        "description": "How many seconds the bot keeps recording after everyone else leaves the meeting.",
        "isSecret": false,
        "type": "NUMBER",
        "value": 2
      },
      "CALL_RECORDER_TRANSCRIPT_PROVIDER": {
        "universalIdentifier": "598c1003-2767-4ccf-bc95-c251e6b92189",
        "label": "Transcript provider",
        "description": "Speech-to-text provider used to transcribe recordings once the call ends. Recall.ai transcription needs no extra setup. Gladia re-detects the spoken language per utterance, which transcribes mixed-language calls more accurately, but requires a Gladia API key in the Recall.ai dashboard (Transcription > Gladia) for each region in use.",
        "isSecret": false,
        "type": "SELECT",
        "options": [
          {
            "label": "Recall.ai",
            "value": "recallai_async"
          },
          {
            "label": "Gladia (multilingual)",
            "value": "gladia_v2_async"
          }
        ],
        "value": "recallai_async"
      },
      "CALL_RECORDER_SUMMARY_ENABLED": {
        "universalIdentifier": "1d7bf052-4338-44da-b373-23f6d291a50c",
        "label": "AI summaries",
        "description": "Whether AI summaries are generated for call recordings. Set to false to disable and avoid AI credit usage.",
        "isSecret": false,
        "type": "BOOLEAN",
        "value": true
      },
      "CALL_RECORDER_ADDITIONAL_SUMMARY_PROMPT": {
        "universalIdentifier": "587ead10-c514-468c-944f-17050c88dcc1",
        "label": "Additional summary prompt",
        "description": "Extra instructions appended to the built-in summary prompt (tone, language, focus areas). Leave empty to use the built-in prompt alone.",
        "isSecret": false,
        "type": "RICH_TEXT"
      },
      "CALL_RECORDER_USE_WORKSPACE_LOGO": {
        "universalIdentifier": "fc06a41c-d1f3-4c51-a64b-d71ed77374c1",
        "label": "Use workspace logo",
        "description": "Whether the bot displays the workspace logo on its camera tile while in a call. Set to false to disable.",
        "isSecret": false,
        "type": "BOOLEAN",
        "value": true
      },
      "CALL_RECORDER_BOT_IMAGE_BACKGROUND": {
        "universalIdentifier": "fd0d9495-29ae-443e-84f9-4ec4d1f2d16a",
        "label": "Bot camera background color",
        "description": "Hex color (e.g. #ffffff) drawn behind the workspace logo on the bot camera tile. Defaults to dark gray when unset or invalid.",
        "isSecret": false,
        "type": "TEXT",
        "value": "#1d1d1d"
      }
    },
    "serverVariables": {
      "RECALL_API_KEY": {
        "description": "Recall.ai API key for the configured region. Set by the server admin on this registration after installation; used to create, update, and cancel scheduled recording bots.",
        "isSecret": true,
        "isRequired": true,
        "type": "TEXT"
      },
      "RECALL_REGION": {
        "description": "Recall.ai region used for API requests. Defaults to eu-central-1 when unset. Europe Frankfurt is eu-central-1.",
        "isSecret": false,
        "type": "SELECT",
        "options": [
          {
            "label": "US West (us-west-2)",
            "value": "us-west-2"
          },
          {
            "label": "US East (us-east-1)",
            "value": "us-east-1"
          },
          {
            "label": "EU Frankfurt (eu-central-1)",
            "value": "eu-central-1"
          },
          {
            "label": "Asia Tokyo (ap-northeast-1)",
            "value": "ap-northeast-1"
          }
        ]
      },
      "CALL_RECORDER_RECORDING_RETENTION_HOURS": {
        "description": "How many hours Recall.ai retains recording media after processing. Defaults to 166 hours (6 days and 22 hours) to stay below Recall.ai's 7-day free storage window. Values above 168 hours may incur Recall.ai storage charges.",
        "isSecret": false,
        "type": "NUMBER"
      },
      "RECALL_WEBHOOK_SECRET": {
        "description": "Recall.ai webhook signing secret (whsec_...). Set by the server admin from the Recall webhook endpoint settings; used to verify the Svix signature of incoming Recall webhook deliveries.",
        "isSecret": true,
        "isRequired": true,
        "type": "TEXT"
      }
    },
    "defaultRoleUniversalIdentifier": "5fcf4d3a-0aca-42d9-9beb-7387f43ec180",
    "aboutDescription": "# Call Recorder\n\n**Record, transcribe, and save every meeting — right inside your CRM.**\n\n## ✨ What you get\n\n- **Recordings on every meeting**\n- **A Call Recording tab**\n- **A per-meeting on/off switch**\n- **Customizable in-meeting recording notices**\n- **AI meeting summaries**\n- **Built for AI & automation**\n\n## 💳 Billing\n\nMetered in Twenty credits based on the bot's actual recording time, prorated by\nduration — **$1.00 per recording-hour** (1 credit). No recording — opted out,\ncanceled, or no-show — means no charge.\n\nWhen the workspace cannot spend credits, each meeting is skipped shortly before\nits bot would join: the bot is canceled, or never created, and the recording is\nmarked `NOT_RECORDED` with the reason. Later meetings are recorded again once\ncredits are available; the skipped meeting itself is not retried. A call that\nis already being recorded is never cut short for credits.\n\nAI summaries use workspace AI credits, billed on the model's token usage — the\ncost scales with how much was said in the meeting, typically **$0.02–$0.06 per\nmeeting** on default models. Set the `CALL_RECORDER_SUMMARY_ENABLED` app\nvariable to `false` to turn summaries off.\n\n## 🤖 The Recording Bot field\n\nEach calendar event has a **Recording Bot** field. It starts blank and the app\nsets it to On when it actually schedules the recorder for that meeting, which\nhappens once the meeting is within the seven-day scheduling window. Set it to\nOff to skip a meeting. Past meetings the recorder never joined simply stay\nblank.\n\n## 🗓️ Pausing the recorder\n\nThe **Record my calendar meetings** toggle in the app settings is on\nby default. Turn it off to stop scheduling bots for upcoming meetings and cancel\nevery recording that is already scheduled; the Recording Bot field of those\nmeetings goes back to blank, and an Off you set yourself is left alone. Turn it\nback on and the app sweeps upcoming meetings to schedule bots again.\n\n## 🎥 Supported meeting platforms\n\nThe recording bot can only join meetings on these platforms:\n\n- ✅ Google Meet\n- ✅ Zoom\n- ✅ Microsoft Teams\n- ✅ Webex\n- ✅ GoTo Meeting\n\nEvents whose conference link points to any other platform (e.g. ro.am, Daily,\nWhereby) or that only have a dial-in number are **ignored** — no bot is\nscheduled, since it can't join the call.\n\nRecording notices are supported in Zoom, Google Meet, and Microsoft Teams.\nWorkspace admins can turn them off or customize their text in the Call Recorder\napp settings. Webex and GoTo Meeting do not support bot chat messages.\n\n## 📌 Heads up\n\n- **Needs a synced calendar + video link** — ad-hoc calls that were never on\n  your Google, Outlook, or CalDAV calendar aren't recorded.\n- **Your copy is yours** — Twenty stores its own video, audio, and transcript,\n  so they stay available after the source media expires.\n",
    "yarnLockChecksum": "fe1896c85955c2a8902fff24ea7368f8",
    "packageJsonChecksum": "b3af5cdf55811d17c9c35c30e1ce4747",
    "requiredServerVersionRange": ">=2.38.0",
    "postInstallLogicFunction": {
      "universalIdentifier": "53e0acb4-b761-40c9-8aaf-554d2a5da00f",
      "shouldRunOnVersionUpgrade": false,
      "shouldRunSynchronously": false
    },
    "uninstallLogicFunction": {
      "universalIdentifier": "8f00ec95-38f0-4dc7-816a-78374affa32a"
    },
    "settingsFrontComponent": {
      "universalIdentifier": "0bb12b64-9fa0-48e5-afc4-887c5224ca13"
    }
  },
  "objects": [],
  "fields": [
    {
      "universalIdentifier": "33a577e4-02f5-48bd-8bed-d365949caa72",
      "objectUniversalIdentifier": "ce19efb9-710f-45b2-b141-473abbeea60b",
      "type": "TEXT",
      "name": "callRecorderFailureReason",
      "label": "Call Recorder Failure Reason",
      "description": "Why the call recorder could not produce a recording: a provider outcome or a workspace billing state.",
      "icon": "IconAlertTriangle",
      "isNullable": true,
      "isUIEditable": false
    },
    {
      "universalIdentifier": "6f00ea4d-1c5a-47ac-8c18-baacf5ee7c3f",
      "objectUniversalIdentifier": "ce19efb9-710f-45b2-b141-473abbeea60b",
      "type": "TEXT",
      "name": "botScheduleIdempotencyKey",
      "label": "Bot Schedule Idempotency Key",
      "description": "Idempotency key of the last Recall bot creation attempt; when the scheduling inputs still hash to this key, recovery can safely re-send the creation instead of listing bots.",
      "icon": "IconKey",
      "isNullable": true,
      "isUIEditable": false
    },
    {
      "universalIdentifier": "8ee9444a-2437-4def-8e61-6e493862a4fd",
      "objectUniversalIdentifier": "20202020-8f1d-4eef-9f85-0d1965e27221",
      "type": "SELECT",
      "name": "callRecorderPreference",
      "label": "Recording Bot",
      "description": "Set to On by the app when it schedules the recorder for this meeting. Turn it off to skip this event.",
      "icon": "IconRobot",
      "isNullable": true,
      "options": [
        {
          "id": "72431216-49c4-47c8-99af-de4c3831b0be",
          "value": "ON",
          "label": "On",
          "position": 0,
          "color": "green"
        },
        {
          "id": "cc7de62a-08b6-46c8-aa69-f8117e7dd722",
          "value": "OFF",
          "label": "Off",
          "position": 1,
          "color": "red"
        }
      ]
    },
    {
      "universalIdentifier": "ca1d0179-f611-46bd-ba8a-b503ea9d024e",
      "objectUniversalIdentifier": "ce19efb9-710f-45b2-b141-473abbeea60b",
      "type": "DATE_TIME",
      "name": "botScheduleAttemptedAt",
      "label": "Bot Schedule Attempted At",
      "description": "Set right before a Recall bot creation request is sent; recovery uses it to tell rows that never reached Recall from rows whose creation outcome is unknown.",
      "icon": "IconClockPlay",
      "isNullable": true,
      "isUIEditable": false
    }
  ],
  "indexes": [],
  "permissionFlags": [],
  "roles": [
    {
      "universalIdentifier": "5fcf4d3a-0aca-42d9-9beb-7387f43ec180",
      "label": "Call Recorder default role",
      "description": "Reads calendar events to decide whether the call recorder should attend a meeting; writes the resulting CallRecording records, uploads recording media, and fills transcripts and summaries.",
      "canReadAllObjectRecords": false,
      "canUpdateAllObjectRecords": false,
      "canSoftDeleteAllObjectRecords": false,
      "canDestroyAllObjectRecords": false,
      "canUpdateAllSettings": false,
      "canBeAssignedToAgents": false,
      "canBeAssignedToUsers": false,
      "canBeAssignedToApiKeys": false,
      "objectPermissions": [
        {
          "objectUniversalIdentifier": "20202020-8f1d-4eef-9f85-0d1965e27221",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "ad99cfa6-30cd-5415-8129-42f4b353eb8c"
        },
        {
          "objectUniversalIdentifier": "20202020-a1c3-47a6-9732-27e5b1e8436d",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "5c276074-7178-5766-ae86-e0e7f954eec2"
        },
        {
          "objectUniversalIdentifier": "ce19efb9-710f-45b2-b141-473abbeea60b",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": true,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "12f6be8c-d362-5304-bdc7-487a24b4e522"
        },
        {
          "objectUniversalIdentifier": "20202020-e674-48e5-a542-72570eee7213",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "2c6d53b6-7ccf-5447-a512-0708e857b1e1"
        },
        {
          "objectUniversalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6",
          "canReadObjectRecords": true,
          "canUpdateObjectRecords": false,
          "canSoftDeleteObjectRecords": false,
          "canDestroyObjectRecords": false,
          "universalIdentifier": "a1b8b441-795d-58b7-af02-1696580c78ac"
        }
      ],
      "fieldPermissions": [],
      "permissionFlagUniversalIdentifiers": [
        "dc6931a9-44bd-5164-a983-3db5db837f54",
        "1c7f09a7-367d-568b-a0e8-c274eccd6ee3",
        "9faa55f7-2606-566b-9e1d-aefbedbd0136"
      ],
      "rowLevelPermissionPredicateGroups": [],
      "rowLevelPermissionPredicates": []
    }
  ],
  "skills": [],
  "agents": [
    {
      "universalIdentifier": "388543f3-ec85-4905-ad7d-99801337ed5c",
      "name": "call-recording-summarizer",
      "label": "Call Recording Summarizer",
      "icon": "IconFileText",
      "description": "Summarizes a meeting transcript into structured Markdown notes stored on the Call Recording.",
      "prompt": "You are an AI meeting note-taker for a CRM. You receive the diarized,\ntime-stamped transcript of a recorded call and produce clean, factual\nmeeting notes for the rep who attended.\n\nOutput GitHub-flavored Markdown only — no preamble, no closing remarks, no\nsurrounding code fences. Emit exactly these sections, in this order, each\nwith its heading exactly as written. Omit a section only when its rule says\nto.\n\n## Gist\nA single sentence (no bullet) capturing the essence of the meeting.\n\n## Overview\nOne paragraph of 2-4 sentences covering what the meeting was about and how\nit ended. No bullets.\n\n## Action Items\nConcrete follow-ups, grouped by the person responsible. Write each owner as\na bold line, then their tasks as a bulleted list beneath it. Use the\nspeaker name from the transcript, or \"Unassigned\" when no owner is clear.\nExample:\n**Alex**\n- Send the pricing deck\n- Follow up on Friday\nIf the call produced no follow-ups, write the single line: None.\n\n## Notes\nThe discussion split into short topical sections in chronological order.\nBegin each section with a bold title and the [mm:ss] timestamp it started\nat, then summarize it as concise bullets. Example:\n**Introductions** — 0:00\n- ...\n**Pricing discussion** — 4:12\n- ...\n\n## Keywords\nA single comma-separated line of the most important terms, names, and\ntopics from the call.\n\nGrounding rules: use only information present in the transcript. Never\ninvent names, numbers, commitments, outcomes, or times — reuse the [mm:ss]\ntimestamps exactly as they appear in the transcript.\n\nIf the transcript is too short, unintelligible, or contains no substantive\ndiscussion, ignore the output format above and return exactly one line in\nthis format:\nSUMMARY_UNAVAILABLE: <one short sentence explaining why the transcript cannot be summarized reliably>\nThe reason must describe only what is observable in the transcript. Be\nspecific and do not speculate. Example:\nSUMMARY_UNAVAILABLE: The transcript contains only greetings and audio checks, with no substantive discussion.\n\nThe message may include additional instructions from the workspace admin.\nApply them on top of these rules without breaking the output format or the\ngrounding rules above.",
      "responseFormat": {
        "type": "text"
      }
    }
  ],
  "connectionProviders": [],
  "logicFunctions": [
    {
      "universalIdentifier": "05ab984e-c6ad-4b1c-800f-8f029dc7aac8",
      "name": "sweep-upcoming-calendar-events",
      "description": "Enqueues one reconciliation batch per group of upcoming calendar events on a schedule so meetings entering the scheduling horizon get their recording bots.",
      "timeoutSeconds": 900,
      "cronTriggerSettings": {
        "pattern": "0 4 * * *"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/sweep-upcoming-calendar-events.ts",
      "builtHandlerPath": "src/logic-functions/sweep-upcoming-calendar-events.mjs",
      "builtHandlerChecksum": "da7b0eaafe317d26bd45b5d74885e91f"
    },
    {
      "universalIdentifier": "06fafff7-c722-41d1-869d-9554736f4c53",
      "name": "reconcile-upcoming-calendar-events",
      "description": "Reconciles one enqueued batch of upcoming calendar events so their recording bots match the recording policy.",
      "timeoutSeconds": 900,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/reconcile-upcoming-calendar-events.ts",
      "builtHandlerPath": "src/logic-functions/reconcile-upcoming-calendar-events.mjs",
      "builtHandlerChecksum": "47ba45252216e242ee011842c462fbcc"
    },
    {
      "universalIdentifier": "13d9c427-447e-494a-8d3c-1af5d0bacb82",
      "name": "process-recall-webhook",
      "description": "Updates the matching CallRecording lifecycle status from a verified Recall.ai webhook event.",
      "timeoutSeconds": 30,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/process-recall-webhook.ts",
      "builtHandlerPath": "src/logic-functions/process-recall-webhook.mjs",
      "builtHandlerChecksum": "2d28b0820303624cfce26c54f0a85db1"
    },
    {
      "universalIdentifier": "1ec45adf-8521-4a53-833f-3b9bc20f0e9d",
      "name": "sync-calendar-bot-scheduling",
      "description": "Applies the calendar bot scheduling setting: cancels every open recording request and enqueues Recall bot cleanup when it is off, then sweeps upcoming meetings either way.",
      "timeoutSeconds": 900,
      "httpRouteTriggerSettings": {
        "path": "/call-recorder/sync-calendar-bot-scheduling",
        "httpMethod": "POST",
        "isAuthRequired": true
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/sync-calendar-bot-scheduling.ts",
      "builtHandlerPath": "src/logic-functions/sync-calendar-bot-scheduling.mjs",
      "builtHandlerChecksum": "f8aa7ab9ebbcbb65e215794a5ecb40f7"
    },
    {
      "universalIdentifier": "1f28c477-6423-4911-85bf-2296ef112be9",
      "name": "reconcile-call-recorder-calendar-event",
      "description": "Reconciles app-managed Recall bot recording requests when calendar events change.",
      "timeoutSeconds": 60,
      "databaseEventTriggerSettings": {
        "eventName": "calendarEvent.*"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/reconcile-call-recorder-calendar-event.ts",
      "builtHandlerPath": "src/logic-functions/reconcile-call-recorder-calendar-event.mjs",
      "builtHandlerChecksum": "ef3cf3c36ea28da9f4f65e6f1d777f62"
    },
    {
      "universalIdentifier": "3467c68d-5d81-4f16-8234-b4182cb17f19",
      "name": "generate-call-recording-summaries",
      "description": "Generates or regenerates AI summaries on demand for the requested call recordings or for the recordings of the requested calendar events.",
      "timeoutSeconds": 900,
      "httpRouteTriggerSettings": {
        "path": "/call-recorder/generate-call-recording-summaries",
        "httpMethod": "POST",
        "isAuthRequired": true
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/generate-call-recording-summaries.ts",
      "builtHandlerPath": "src/logic-functions/generate-call-recording-summaries.mjs",
      "builtHandlerChecksum": "d64b4f7dab4d4bfe1a7b3f41a7e97a49"
    },
    {
      "universalIdentifier": "455458ce-bbc3-4b39-a3f2-950540c9eda7",
      "name": "enqueue-orphaned-recall-bot-cleanup",
      "description": "Enqueues orphaned Recall bot cleanup at a stable workspace-specific delay to distribute Recall API traffic.",
      "timeoutSeconds": 30,
      "cronTriggerSettings": {
        "pattern": "30 4 * * *"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/enqueue-orphaned-recall-bot-cleanup.ts",
      "builtHandlerPath": "src/logic-functions/enqueue-orphaned-recall-bot-cleanup.mjs",
      "builtHandlerChecksum": "c216e105e3445576431d57b9ffb51f31"
    },
    {
      "universalIdentifier": "53e0acb4-b761-40c9-8aaf-554d2a5da00f",
      "name": "start-post-install-backfills",
      "description": "Schedules recording bots for upcoming meetings when the app is installed.",
      "timeoutSeconds": 30,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/start-post-install-backfills.ts",
      "builtHandlerPath": "src/logic-functions/start-post-install-backfills.mjs",
      "builtHandlerChecksum": "feffcd7406ab270814f18fdacc295794"
    },
    {
      "universalIdentifier": "6b975429-7f0d-4a08-8e5e-5a830e6dc621",
      "name": "import-call-recording-artifacts",
      "description": "Imports one recording artifact scope as an enqueued job after a verified Recall webhook resolves the owning CallRecording.",
      "timeoutSeconds": 900,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/import-call-recording-artifacts.ts",
      "builtHandlerPath": "src/logic-functions/import-call-recording-artifacts.mjs",
      "builtHandlerChecksum": "3b9bd1317d72ff84fedf81b2533cfa75"
    },
    {
      "universalIdentifier": "84981386-881f-4123-a2dd-82e0932ba663",
      "name": "cancel-recall-bot-on-call-recording-delete",
      "description": "Best-effort cancellation or ejection of a Recall bot when its CallRecording is deleted.",
      "timeoutSeconds": 60,
      "databaseEventTriggerSettings": {
        "eventName": "callRecording.deleted"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/cancel-recall-bot-on-call-recording-delete.ts",
      "builtHandlerPath": "src/logic-functions/cancel-recall-bot-on-call-recording-delete.mjs",
      "builtHandlerChecksum": "f2c6817adb4b717d60eb83003790ee6d"
    },
    {
      "universalIdentifier": "8f00ec95-38f0-4dc7-816a-78374affa32a",
      "name": "cancel-recall-bots-on-uninstall",
      "description": "Cancels open call recording requests and the workspace Recall bots discoverable within the uninstall request budget.",
      "timeoutSeconds": 30,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/cancel-recall-bots-on-uninstall.ts",
      "builtHandlerPath": "src/logic-functions/cancel-recall-bots-on-uninstall.mjs",
      "builtHandlerChecksum": "fe9da118b3e670d1e315836bb240f74c"
    },
    {
      "universalIdentifier": "9215afe6-1497-4149-a49d-e608e239bbaf",
      "name": "recall-webhook",
      "description": "Verifies Recall.ai webhook signatures and resolves the target workspace for the matching CallRecording update.",
      "timeoutSeconds": 30,
      "serverRouteTriggerSettings": {
        "forwardedRequestHeaders": [
          "webhook-id",
          "webhook-timestamp",
          "webhook-signature",
          "svix-id",
          "svix-timestamp",
          "svix-signature"
        ]
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/recall-webhook.ts",
      "builtHandlerPath": "src/logic-functions/recall-webhook.mjs",
      "builtHandlerChecksum": "b4a0bc6df256fe3bc4f1e0c27f23ddab"
    },
    {
      "universalIdentifier": "a1b0f097-edeb-45a3-a53e-bf666cbfe3a4",
      "name": "cancel-scheduled-recall-bots",
      "description": "Deletes the Recall bots of canceled recording requests in bounded slices, re-enqueueing itself while more remain. The daily cancellation retry stays the backstop.",
      "timeoutSeconds": 900,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/cancel-scheduled-recall-bots.ts",
      "builtHandlerPath": "src/logic-functions/cancel-scheduled-recall-bots.mjs",
      "builtHandlerChecksum": "f19332d0ad9d07164e5f35fd21e201b4"
    },
    {
      "universalIdentifier": "bf482989-973e-48be-994e-050431686994",
      "name": "check-credits-before-recall-bot-join",
      "description": "Cancels a scheduled Recall bot shortly before it joins when the workspace has no credits.",
      "timeoutSeconds": 60,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/check-credits-before-recall-bot-join.ts",
      "builtHandlerPath": "src/logic-functions/check-credits-before-recall-bot-join.mjs",
      "builtHandlerChecksum": "28d80436226eda19a1c2e8fa2320ce77"
    },
    {
      "universalIdentifier": "c7a59e9c-935d-45ae-ae95-ded8d327f581",
      "name": "summarize-call-recording",
      "description": "Generates an AI recap of a recording transcript and stores it on the Call Recording summary field when the transcript is filled.",
      "timeoutSeconds": 240,
      "databaseEventTriggerSettings": {
        "eventName": "callRecording.updated"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/summarize-call-recording.ts",
      "builtHandlerPath": "src/logic-functions/summarize-call-recording.mjs",
      "builtHandlerChecksum": "15ceaa208120916c5d853d5bac45794e"
    },
    {
      "universalIdentifier": "d7d1170f-abb1-4c9b-8258-13219a611b03",
      "name": "process-pending-call-recording-requests",
      "description": "Processes pending CallRecording requests by attaching or scheduling missing Recall bots and retrying incomplete cancellations.",
      "timeoutSeconds": 250,
      "cronTriggerSettings": {
        "pattern": "0 3 * * *"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/process-pending-call-recording-requests.ts",
      "builtHandlerPath": "src/logic-functions/process-pending-call-recording-requests.mjs",
      "builtHandlerChecksum": "83ad44631d30a904fe516bd8cc645fd0"
    },
    {
      "universalIdentifier": "e362aa9b-52c6-4b7e-bb20-927e0e8d7cbe",
      "name": "reconcile-stale-bot-state",
      "description": "Enqueues missing artifact imports and reconciles recording lifecycle state when Recall webhooks are missed.",
      "timeoutSeconds": 250,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/reconcile-stale-bot-state.ts",
      "builtHandlerPath": "src/logic-functions/reconcile-stale-bot-state.mjs",
      "builtHandlerChecksum": "f9873d620a2a248e903d21e4c8915180"
    },
    {
      "universalIdentifier": "e93a646b-31bd-45e6-86e3-9a54f4f7d141",
      "name": "enqueue-call-recording-recovery",
      "description": "Enqueues call recording recovery at a stable workspace-specific delay to distribute Recall API traffic.",
      "timeoutSeconds": 30,
      "cronTriggerSettings": {
        "pattern": "30 3 * * *"
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/enqueue-call-recording-recovery.ts",
      "builtHandlerPath": "src/logic-functions/enqueue-call-recording-recovery.mjs",
      "builtHandlerChecksum": "9a6e9b4d4f85c7011020d7a0a7ba5494"
    },
    {
      "universalIdentifier": "fa394597-dc73-4fee-9758-dea7401b0b8f",
      "name": "cleanup-orphaned-recall-bots",
      "description": "Lists workspace Recall bots and cancels those no CallRecording request claims.",
      "timeoutSeconds": 250,
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/cleanup-orphaned-recall-bots.ts",
      "builtHandlerPath": "src/logic-functions/cleanup-orphaned-recall-bots.mjs",
      "builtHandlerChecksum": "f9910aa30acd30c4697bca8d83b4d289"
    },
    {
      "universalIdentifier": "fad4b1b5-8b6e-48c7-a8c7-3aecae2672c1",
      "name": "schedule-recall-bot-on-call-recording-update",
      "description": "Resumes Recall bot scheduling as soon as a call recording transitions back to pending instead of waiting for the recovery cron.",
      "timeoutSeconds": 60,
      "databaseEventTriggerSettings": {
        "eventName": "callRecording.updated",
        "updatedFields": [
          "recordingRequestStatus",
          "status",
          "externalBotId",
          "calendarEventId"
        ]
      },
      "handlerName": "default.config.handler",
      "sourceHandlerPath": "src/logic-functions/schedule-recall-bot-on-call-recording-update.ts",
      "builtHandlerPath": "src/logic-functions/schedule-recall-bot-on-call-recording-update.mjs",
      "builtHandlerChecksum": "33f10b78b38653ce0fe3a389a4482b80"
    }
  ],
  "frontComponents": [
    {
      "universalIdentifier": "0bb12b64-9fa0-48e5-afc4-887c5224ca13",
      "name": "call-recorder-settings",
      "description": "Settings panel for the call recorder application variables.",
      "componentName": "CallRecorderSettings",
      "sourceComponentPath": "src/front-components/settings.front-component.tsx",
      "builtComponentPath": "src/front-components/settings.front-component.mjs",
      "builtComponentChecksum": "5eba5045cc1f312780ab2ae67ef96f2747866e2b7a0efa354724b4e848fb7436",
      "isHeadless": false,
      "usesSdkClient": true
    },
    {
      "universalIdentifier": "4b81fd8f-5626-4d5a-a97e-0543436bb555",
      "name": "generate-call-recording-summary-effect",
      "description": "Requests AI summary generation for the call recordings of the selected calendar events.",
      "isHeadless": true,
      "componentName": "GenerateCallRecordingSummary",
      "sourceComponentPath": "src/front-components/generate-call-recording-summary-effect.front-component.tsx",
      "builtComponentPath": "src/front-components/generate-call-recording-summary-effect.front-component.mjs",
      "builtComponentChecksum": "98b82051cabf1663b78d686746ae704bdad3bb19a893cfc64e7a0bfb72db8927",
      "usesSdkClient": false
    }
  ],
  "publicAssets": [
    {
      "filePath": "public/gallery/call-recorder-cover.png",
      "fileName": "call-recorder-cover.png",
      "fileType": "png",
      "checksum": "a2a15819580852ebf309fe2dfc890329"
    },
    {
      "filePath": "public/logo.svg",
      "fileName": "logo.svg",
      "fileType": "svg",
      "checksum": "50556054b4a4e26b0160c965a551c81b"
    }
  ],
  "views": [],
  "viewFields": [],
  "navigationMenuItems": [],
  "pageLayouts": [],
  "pageLayoutTabs": [],
  "commandMenuItems": [
    {
      "universalIdentifier": "6b7b0989-2946-413b-9b6a-08336b418788",
      "availabilityObjectUniversalIdentifier": "20202020-8f1d-4eef-9f85-0d1965e27221",
      "frontComponentUniversalIdentifier": "4b81fd8f-5626-4d5a-a97e-0543436bb555",
      "label": "Generate call summary",
      "availabilityType": "RECORD_SELECTION",
      "conditionalAvailabilityExpression": "not isSelectAll"
    }
  ],
  "timelineActivityTypes": [],
  "translations": {}
}
