4f938aa097
**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>
106 lines
6.3 KiB
TypeScript
106 lines
6.3 KiB
TypeScript
import { Module } from '@nestjs/common';
|
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
|
|
import { CronRegisterAllCommand } from 'src/database/commands/cron-register-all.command';
|
|
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
|
|
import { GenerateInstanceCommandCommand } from 'src/database/commands/generate-instance-command.command';
|
|
import { InstallPreInstalledAppsCommand } from 'src/database/commands/install-pre-installed-apps.command';
|
|
import { InstanceCommandGenerationService } from 'src/database/commands/instance-command-generation.service';
|
|
import { ListOrphanedWorkspaceEntitiesCommand } from 'src/database/commands/list-and-delete-orphaned-workspace-entities.command';
|
|
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
|
import { RunInstanceCommandsCommand } from 'src/database/commands/run-instance-commands.command';
|
|
import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/upgrade-version-command.module';
|
|
import { WorkspaceExportModule } from 'src/database/commands/workspace-export/workspace-export.module';
|
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
|
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
|
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
|
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
|
|
import { StaleRegistrationCleanupModule } from 'src/engine/core-modules/application/application-oauth/stale-registration-cleanup/stale-registration-cleanup.module';
|
|
import { PreInstalledAppsModule } from 'src/engine/core-modules/application/pre-installed-apps/pre-installed-apps.module';
|
|
import { ApplicationUpgradeModule } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.module';
|
|
import { RebuildApplicationDefaultDepsCommand } from 'src/database/commands/rebuild-application-default-deps.command';
|
|
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
|
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
|
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
|
import { EnforceUsageCapCronCommand } from 'src/engine/core-modules/billing/crons/commands/enforce-usage-cap.cron.command';
|
|
import { EnterpriseKeyValidationCronCommand } from 'src/engine/core-modules/enterprise/cron/command/enterprise-key-validation.cron.command';
|
|
import { EnterpriseModule } from 'src/engine/core-modules/enterprise/enterprise.module';
|
|
import { EventLogCleanupModule } from 'src/engine/core-modules/event-logs/cleanup/event-log-cleanup.module';
|
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
|
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
|
import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module';
|
|
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
|
import { UpgradeStatusCommand } from 'src/engine/core-modules/upgrade/commands/upgrade-status.command';
|
|
import { UpgradeModule } from 'src/engine/core-modules/upgrade/upgrade.module';
|
|
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
|
import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.module';
|
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
|
import { DevSeederModule } from 'src/engine/workspace-manager/dev-seeder/dev-seeder.module';
|
|
import { WorkspaceCleanerModule } from 'src/engine/workspace-manager/workspace-cleaner/workspace-cleaner.module';
|
|
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
|
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
|
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
|
|
import { CalendarEventImportManagerModule } from 'src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module';
|
|
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
|
|
import { WorkflowRunQueueModule } from 'src/modules/workflow/workflow-runner/workflow-run-queue/workflow-run-queue.module';
|
|
import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module';
|
|
|
|
@Module({
|
|
imports: [
|
|
UpgradeVersionCommandModule,
|
|
TypeOrmModule.forFeature([WorkspaceEntity]),
|
|
WorkspaceExportModule,
|
|
// Cron command dependencies
|
|
MessagingImportManagerModule,
|
|
CalendarEventImportManagerModule,
|
|
AutomatedTriggerModule,
|
|
FileModule,
|
|
WorkspaceModule,
|
|
WorkflowRunQueueModule,
|
|
// Data seeding dependencies
|
|
TypeORMModule,
|
|
FieldMetadataModule,
|
|
ObjectMetadataModule,
|
|
DevSeederModule,
|
|
WorkspaceManagerModule,
|
|
WorkspaceCacheStorageModule,
|
|
ApiKeyModule,
|
|
FeatureFlagModule,
|
|
WorkspaceCleanerModule,
|
|
WorkspaceMigrationModule,
|
|
TrashCleanupModule,
|
|
PublicDomainModule,
|
|
EventLogCleanupModule,
|
|
EnterpriseModule,
|
|
TwentyConfigModule,
|
|
MarketplaceModule,
|
|
ApplicationUpgradeModule,
|
|
StaleRegistrationCleanupModule,
|
|
PreInstalledAppsModule,
|
|
WorkspaceIteratorModule,
|
|
ApplicationModule,
|
|
WorkspaceCacheModule,
|
|
WorkspaceVersionModule,
|
|
UpgradeModule,
|
|
],
|
|
providers: [
|
|
DataSeedWorkspaceCommand,
|
|
ConfirmationQuestion,
|
|
CronRegisterAllCommand,
|
|
GenerateInstanceCommandCommand,
|
|
InstanceCommandGenerationService,
|
|
RunInstanceCommandsCommand,
|
|
ListOrphanedWorkspaceEntitiesCommand,
|
|
EnterpriseKeyValidationCronCommand,
|
|
GenerateApiKeyCommand,
|
|
EnforceUsageCapCronCommand,
|
|
UpgradeStatusCommand,
|
|
RebuildApplicationDefaultDepsCommand,
|
|
InstallPreInstalledAppsCommand,
|
|
],
|
|
})
|
|
export class DatabaseCommandModule {}
|