Files
twenty/packages/twenty-apps/internal/exa/public/exa-logo.svg
T
Félix Malfait 4f938aa097 feat(app): infrastructure for pre-installed apps (#19973)
**PR 1 of 2.** Follow-up PR ships the Exa app, sets it as a default
pre-installed app, and removes the current `WebSearchTool` /
`WebSearchService` / `ExaDriver`. This PR adds the plumbing; no
user-visible change yet.

## Summary

- Server admins can declare a list of npm app packages to auto-install
on every new workspace and backfill onto existing workspaces via CLI.
- Server-level secrets (like Exa's API key) live on the
`ApplicationRegistration` (one row per server, encrypted) and are
injected into logic function execution env at runtime. No more
per-workspace storage of global secrets.
- A generic `POST /app/billing/charge` endpoint lets app logic functions
emit workspace usage events for metered features. Exa uses it in PR 2;
future apps (call recorder, etc.) reuse it.
- `LogicFunctionToolProvider` tool name prefix changes `logic_function_`
→ `app_`. Shorter, accurate (they come from installed apps).

## What's in this PR

**Logic function executor — server-level variables**
- `LogicFunctionExecutorService.getExecutionEnvVariables` now resolves
env vars in the order: hardcoded defaults →
`ApplicationRegistrationVariable[]` (server-level) →
`ApplicationVariable[]` (workspace-level override). The manifest
`serverVariables` schema has existed; this closes the loop.

**Config**
- `PRE_INSTALLED_APPS` — comma-separated list of npm packages. Default:
empty.

**\`PreInstalledAppsService\`** (new module)
- \`onApplicationBootstrap()\` — fetches each package's manifest from
the app registry CDN, upserts an \`ApplicationRegistration\`, and seeds
declared \`serverVariables\` from matching env vars (e.g.
\`EXA_API_KEY\` env → encrypted registration variable).
- \`installOnWorkspace(workspaceId)\` — installs all pre-installed apps
on a single workspace. Tolerates per-app failures.

**Auto-install on new workspace activation**
- \`WorkspaceService.prefillCreatedWorkspaceRecords\` invokes
\`installOnWorkspace\` after prefilling standard records. Non-blocking
on failure.

**Backfill CLI command**
- \`install-pre-installed-apps\` — iterates active and suspended
workspaces, installs pre-installed apps that aren't yet installed.
Idempotent. Run after changing \`PRE_INSTALLED_APPS\`.

**App billing endpoint**
- \`POST /app/billing/charge\`. Authenticated via \`APPLICATION_ACCESS\`
token (already injected into logic function execution env as
\`DEFAULT_APP_ACCESS_TOKEN\`). Body: \`{ creditsUsedMicro, quantity,
unit, operationType, resourceContext? }\`. Emits \`USAGE_RECORDED\` with
\`applicationId\` as \`resourceId\`. Generic — reusable by any app.

**Tool name prefix**
- \`LogicFunctionToolProvider.buildLogicFunctionToolName\` now produces
\`app_<name>\` instead of \`logic_function_<name>\`. Only affects tools
sourced from logic functions; other tool providers unchanged.

## Stats

- 16 files, +501 / −2
- 7 new files (1 command, 1 service × 2, 1 controller, 1 DTO, 2 modules)
- Typecheck: 7 pre-existing errors, zero new
- Prettier clean

## Behavior deltas

- **\`PRE_INSTALLED_APPS\` default = empty**: existing servers see no
change on merge.
- **\`ApplicationRegistrationVariable\` is now read by the executor**:
apps that were using manifest \`serverVariables\` but expecting them to
be ignored by the executor will now see them injected. No apps ship with
\`isTool: true\` logic functions today, so this is latent — first
consumer is Exa in PR 2.
- **Tool prefix**: currently no logic-function tools are named
\`logic_function_*\` in any production flow. The prefix change affects
only future tools emitted by \`LogicFunctionToolProvider\`.

## Risks

- **CDN unavailability at startup**: if the app registry CDN is down,
\`ensureRegistrationsExist\` logs warnings but doesn't block server
start. Installation on new workspaces during this window will find no
registrations and log a non-blocking error. Backfill command can retry
after CDN recovers.
- **Cold-start overhead**: \`ensureRegistrationsExist\` is called once
per process on bootstrap. Current configurable default is empty, so zero
overhead. When an admin sets \`PRE_INSTALLED_APPS\`, they accept one
HTTP call per package at boot.
- **Server-level variables flow**:
\`ApplicationRegistrationVariable.encryptedValue\` is shared by all
workspaces of a server. Appropriate for a single-tenant Exa key. Not
appropriate for per-tenant keys — those go in workspace-level
\`ApplicationVariable\` and override.

## Test plan

- [ ] \`npx nx typecheck twenty-server\` passes (verified: 7
pre-existing unrelated errors, zero new)
- [ ] Set \`PRE_INSTALLED_APPS=@twenty-apps/hello-world\` (or any real
npm-published app), \`HELLO_WORLD_API_KEY=xxx\`, restart server:
\`ApplicationRegistration\` row is upserted,
\`ApplicationRegistrationVariable\` for HELLO_WORLD_API_KEY is populated
(encrypted).
- [ ] Create a new workspace: the app is auto-installed,
\`ApplicationEntity\` row created, \`LogicFunctionEntity\` rows created.
- [ ] Existing workspace: run \`yarn nx run twenty-server:command
install-pre-installed-apps\`: apps install across all workspaces,
idempotent on re-run.
- [ ] Trigger a logic function that reads
\`process.env.HELLO_WORLD_API_KEY\`: value resolves from the
server-level \`ApplicationRegistrationVariable\`.
- [ ] Log a charge from the handler: \`POST /app/billing/charge\` with
\`Authorization: Bearer \$DEFAULT_APP_ACCESS_TOKEN\` body
\`{creditsUsedMicro: 1000, quantity: 1, unit: "INVOCATION",
operationType: "WEB_SEARCH"}\` → returns \`{success: true}\`,
\`USAGE_RECORDED\` event emitted with correct
\`resourceId=applicationId\`.
- [ ] Tool name generated by \`LogicFunctionToolProvider\` starts with
\`app_\`.

## What's NOT in this PR (PR 2 scope)

- The Exa app itself (\`packages/twenty-apps/...\` directory)
- Removing \`WebSearchTool\`, \`WebSearchService\`, \`ExaDriver\`,
\`web-search\` module
- Removing \`WEB_SEARCH_DRIVER\` config var
- Removing the current \`exa_web_search\` entry in
\`ActionToolProvider\`
- Chat preload list updated to \`app_exa_web_search\`
- Frontend \`getToolDisplayMessage\` branch for \`app_exa_web_search\`
- Setting \`PRE_INSTALLED_APPS\` default to include \`@twenty-apps/exa\`

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:59:00 +02:00

7 lines
4.5 KiB
XML

<svg width="1386" height="480" viewBox="0 0 1386 480" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1247.78 71.5781C1289.16 71.5781 1321.17 82.4448 1343.82 104.178C1366.9 125.477 1378.44 157.425 1378.44 200.023V327.163C1378.44 358.182 1380.69 384.72 1385.18 406.779C1385.71 409.423 1383.71 411.924 1381.01 411.924H1337.41C1335.43 411.924 1333.71 410.55 1333.33 408.61C1330.74 395.478 1329.44 380.499 1329.44 363.675H1328.14C1304.18 400.622 1265.86 419.096 1213.16 419.096C1179.19 419.096 1151.96 410.185 1131.49 392.364C1111.02 374.108 1100.79 350.635 1100.79 321.947C1100.79 293.259 1110.15 270.656 1128.88 254.139C1148.04 237.187 1180.71 225.016 1226.88 217.627C1258.22 212.554 1289.36 208.92 1320.29 206.725C1321.77 206.621 1322.91 205.398 1322.91 203.924V193.503C1322.91 166.988 1316.16 147.428 1302.66 134.822C1289.59 122.217 1271.3 115.914 1247.78 115.914C1224.26 115.914 1205.97 122 1192.9 134.17C1180.73 145.109 1173.85 159.634 1172.26 177.746C1172.06 179.99 1170.22 181.767 1167.96 181.767H1118.95C1116.54 181.767 1114.61 179.739 1114.81 177.337C1117.41 146.757 1129.72 121.718 1151.75 102.222C1174.83 81.7928 1206.84 71.5781 1247.78 71.5781ZM1322.91 270.439V254.774C1322.91 252.322 1320.82 250.389 1318.37 250.588C1286.75 253.153 1259.3 256.51 1236.02 260.659C1208.15 265.006 1188.33 271.526 1176.57 280.219C1165.25 288.912 1159.59 301.518 1159.59 318.035C1159.59 335.422 1165.47 349.331 1177.23 359.763C1189.42 369.761 1206.19 374.76 1227.53 374.76C1256.71 374.76 1279.58 366.066 1296.13 348.679C1306.14 338.247 1313.11 327.598 1317.03 316.731C1320.95 305.43 1322.91 289.999 1322.91 270.439Z" fill="black"/>
<path d="M1069.28 80.7062C1072.7 80.7062 1074.69 84.56 1072.72 87.3456L964.411 239.795L1083.11 405.269C1085.11 408.052 1083.11 411.924 1079.68 411.924H1020.88C1019.48 411.924 1018.17 411.224 1017.38 410.059L929.786 279.567H928.479L840.882 410.059C840.1 411.224 838.786 411.924 837.38 411.924H779.234C775.804 411.924 773.81 408.052 775.807 405.269L894.507 239.795L786.203 87.3456C784.224 84.5601 786.22 80.7062 789.641 80.7062H847.17C848.581 80.7062 849.899 81.411 850.68 82.5838L928.479 199.371H929.786L1008.24 82.5705C1009.02 81.4053 1010.33 80.7062 1011.74 80.7062H1069.28Z" fill="black"/>
<path d="M602.456 376.716C625.975 376.716 645.356 370.848 660.6 359.112C675.353 348.069 684.901 333.754 689.242 316.169C689.721 314.229 691.436 312.819 693.438 312.819H743.669C746.324 312.819 748.321 315.243 747.711 317.822C740.936 346.486 725.4 370.681 701.105 390.408C675.844 410.837 642.961 421.052 602.456 421.052C554.111 421.052 516.002 404.969 488.128 372.804C460.254 340.203 446.316 296.519 446.316 241.751C446.316 191.764 460.036 150.905 487.474 119.174C515.349 87.4435 553.023 71.5781 600.496 71.5781C637.516 71.5781 668.657 81.7928 693.918 102.222C719.615 122.652 736.818 150.905 745.529 186.983C749.676 203.95 751.848 226.236 752.047 253.84C752.064 256.163 750.175 258.051 747.847 258.051H505.986C504.794 258.051 503.839 259.042 503.889 260.231C505.474 298.271 515.029 327.102 532.552 346.723C550.845 366.718 574.146 376.716 602.456 376.716ZM600.496 115.914C573.493 115.914 551.498 124.608 534.512 141.994C518.087 158.387 508.178 181.487 504.785 211.293C504.598 212.938 505.897 214.367 507.557 214.367H692.133C693.79 214.367 695.09 212.941 694.906 211.297C691.518 181.062 681.607 157.744 665.173 141.342C648.187 124.39 626.628 115.914 600.496 115.914Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 475.765C0 478.104 1.88926 480 4.21978 480H389.626C391.957 480 393.846 478.104 393.846 475.765V455.637C393.846 453.672 393.166 451.767 391.922 450.248L221.889 242.69C220.609 241.127 220.609 238.873 221.889 237.31L391.922 29.7517C393.166 28.2333 393.846 26.3285 393.846 24.3627V4.23529C393.846 1.89621 391.957 0 389.626 0H4.21978C1.88926 0 0 1.8962 0 4.23529V475.765ZM199.219 270.589C198.088 269.208 195.98 269.216 194.859 270.605L58.1475 440.106C56.6585 441.952 57.9676 444.706 60.334 444.706H335.902C338.278 444.706 339.584 441.932 338.075 440.089L199.219 270.589ZM38.9146 407.858C37.6636 409.409 35.1648 408.521 35.1648 406.525V260.471C35.1648 258.911 36.4244 257.647 37.978 257.647H154.171C156.538 257.647 157.847 260.401 156.358 262.247L38.9146 407.858ZM154.171 222.353C156.538 222.353 157.847 219.599 156.358 217.753L38.9146 72.1424C37.6636 70.5913 35.1648 71.4792 35.1648 73.4749V219.529C35.1648 221.089 36.4243 222.353 37.978 222.353H154.171ZM58.1475 39.8942C56.6585 38.0482 57.9676 35.2941 60.334 35.2941H335.902C338.278 35.2941 339.584 38.0685 338.075 39.9109L199.219 209.411C198.088 210.792 195.98 210.784 194.859 209.395L58.1475 39.8942Z" fill="#2043EC"/>
</svg>