f15fabb5d9
https://github.com/user-attachments/assets/fb9001c4-195d-4735-898b-07ccbab01677 During onboarding, the workspace creator's work-email domain is enriched through People Data Labs and stored client-side. The stacked workspace-setup PR folds it into the invisible prompt that kicks off the setup chat, so the assistant knows the company from its first reply. - New `enrichWorkspaceCompany` mutation: throttled, creator-only, work domains only. Off by default: requires the `IS_WORKSPACE_COMPANY_ENRICHMENT_ENABLED` instance config variable (default false), a `PEOPLE_DATA_LABS_API_KEY`, and the `IS_ONBOARDING_AI_CHAT_ENABLED` workspace feature flag (the enrichment only feeds the AI-chat workspace setup). Every attempt past the throttle is recorded per workspace in a `keyValuePair`. - The frontend fetches once during onboarding and stores a matched result in localStorage. This PR does not deliver it to the model: the hidden-message plumbing it adds (`isHidden` on `agentMessage`, excluded from the chat UI, thread ranking and the admin transcript, included in the model conversation) is what the stacked workspace-setup PR uses to send the context and the setup prompt as one invisible first message. - The PDL wire protocol (base URL, wire types, envelope parsing, error extraction) is kept as a small self-contained copy inside the server `company-enrichment` module. The standalone people-data-labs app keeps its own copy; the two are intentionally not shared, since the app and the core-engine usage are expected to evolve independently. - `WorkspaceCompanyEnrichment` lives in `twenty-shared/workspace` so server and front share one shape. ## Flow ```mermaid flowchart LR effect[Onboarding effect] -- enrichWorkspaceCompany --> checks{creator + work domain?} checks -- no --> unavailable[unavailable] checks -- yes --> throttle{throttle 10/h/workspace} throttle -- limited --> transient[transientError] throttle -- ok --> pdl[PDL GET /company/enrich] pdl --> log[(keyValuePair attempt log)] pdl --> matched[matched] matched --> storage[(localStorage)] storage -- consumed by the stacked workspace-setup PR --> kickoff[hidden kickoff prompt] ``` 1. **Onboarding effect** — mounted app-wide, fires once per session while onboarding is in progress (before workspace activation), guarded by a sessionStorage attempt flag and the cached value. 2. **enrichWorkspaceCompany** — metadata-schema mutation returning a typed `WorkspaceCompanyEnrichmentResult` (`outcome` enum `matched`/`unavailable`/`transientError` + `enrichment` JSON). 3. **Creator + work domain checks** — only the workspace's earliest user, only non-consumer email domains, only when the config flag, API key and `IS_ONBOARDING_AI_CHAT_ENABLED` workspace flag are all on; anything else returns `unavailable` without consuming throttle quota. 4. **Throttle** — token bucket, 10 requests/hour per workspace, the sole cost bound on PDL calls; when limited the mutation returns `transientError` instead of surfacing an error. 5. **PDL call** — `GET /v5/company/enrich` with `website` + `min_likelihood` per the PDL spec; body-level statuses win over HTTP ones, 408/429/5xx map to `transientError`, other failures to `unavailable`. Every attempt past the throttle is recorded (`domain`, the pre-collapse PDL `outcome`, `httpStatus`/`message` when present, `attemptedAt`) in a workspace-scoped `keyValuePair`. 6. **matched** — the PDL payload is mapped to `WorkspaceCompanyEnrichment` through the same sanitizer as client input (all fields length-capped and control-character-stripped; summary 600 chars, 8 tags max) and returned. 7. **localStorage** — the frontend stores only a matched enrichment and never refetches it, making it the only cache; cleared on sign-out. Non-matched outcomes are not persisted; a sessionStorage flag caps retries at one attempt per browser session. 8. **Delivery** — out of scope here. The stacked workspace-setup PR reads the stored enrichment and combines it with the data-model proposal prompt into a single hidden `USER` message when the setup chat starts; it is never injected into the system prompt. Reviewer notes: sending the creator's email domain to a third party at signup is not yet disclosed in onboarding copy. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23199?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
112 lines
4.3 KiB
Bash
112 lines
4.3 KiB
Bash
# Use this for local setup
|
|
NODE_ENV=development
|
|
PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default
|
|
REDIS_URL=redis://localhost:6379
|
|
APP_SECRET=replace_me_with_a_random_string
|
|
SIGN_IN_PREFILLED=true
|
|
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=false
|
|
|
|
FRONTEND_URL=http://localhost:3001
|
|
|
|
# ———————— Optional ————————
|
|
# PORT=3000
|
|
# ACCESS_TOKEN_EXPIRES_IN=30m
|
|
# LOGIN_TOKEN_EXPIRES_IN=15m
|
|
# REFRESH_TOKEN_EXPIRES_IN=90d
|
|
# FILE_TOKEN_EXPIRES_IN=1d
|
|
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
|
|
# IS_IMAP_SMTP_CALDAV_ENABLED=true
|
|
# CALENDAR_PROVIDER_GOOGLE_ENABLED=false
|
|
# MESSAGING_PROVIDER_MICROSOFT_ENABLED=false
|
|
# CALENDAR_PROVIDER_MICROSOFT_ENABLED=false
|
|
# IS_BILLING_ENABLED=false
|
|
# BILLING_PLAN_REQUIRED_LINK=https://twenty.com/stripe-redirection
|
|
# AUTH_PASSWORD_ENABLED=false
|
|
# IS_MULTIWORKSPACE_ENABLED=false
|
|
# AUTH_MICROSOFT_ENABLED=false
|
|
# AUTH_MICROSOFT_CLIENT_ID=replace_me_with_azure_client_id
|
|
# AUTH_MICROSOFT_CLIENT_SECRET=replace_me_with_azure_client_secret
|
|
# AUTH_MICROSOFT_CALLBACK_URL=http://localhost:3000/auth/microsoft/redirect
|
|
# AUTH_MICROSOFT_APIS_CALLBACK_URL=http://localhost:3000/auth/microsoft-apis/get-access-token
|
|
# AUTH_GOOGLE_ENABLED=false
|
|
# AUTH_GOOGLE_CLIENT_ID=replace_me_with_google_client_id
|
|
# AUTH_GOOGLE_CLIENT_SECRET=replace_me_with_google_client_secret
|
|
# AUTH_GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/redirect
|
|
# AUTH_GOOGLE_APIS_CALLBACK_URL=http://localhost:3000/auth/google-apis/get-access-token
|
|
# CODE_INTERPRETER_TYPE=LOCAL
|
|
# LOGIC_FUNCTION_TYPE=LOCAL
|
|
# STORAGE_TYPE=local
|
|
# STORAGE_LOCAL_PATH=.local-storage
|
|
# SUPPORT_DRIVER=front
|
|
# SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret
|
|
# SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id
|
|
# LOGGER_DRIVER=CONSOLE
|
|
# LOGGER_IS_BUFFER_ENABLED=true
|
|
# EXCEPTION_HANDLER_DRIVER=sentry
|
|
# METER_DRIVER=opentelemetry,console
|
|
# SENTRY_ENVIRONMENT=main
|
|
# SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
|
# SENTRY_FRONT_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
|
# APPLICATION_LOG_DRIVER=CONSOLE
|
|
# LOG_LEVELS=error,warn
|
|
# SERVER_URL=http://localhost:3000
|
|
# Keep above your reverse proxy / load balancer idle timeout (nginx, ALB, ... 60s).
|
|
# SERVER_KEEP_ALIVE_TIMEOUT_MS=65000
|
|
# WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION=7
|
|
# WORKSPACE_INACTIVE_DAYS_BEFORE_SOFT_DELETION=14
|
|
# WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION=21
|
|
# Email Server Settings, see this doc for more info: https://docs.twenty.com/start/self-hosting/#email
|
|
# IS_EMAIL_VERIFICATION_REQUIRED=false
|
|
# EMAIL_VERIFICATION_TOKEN_EXPIRES_IN=1h
|
|
# EMAIL_FROM_ADDRESS=contact@yourdomain.com
|
|
# EMAIL_FROM_NAME='John from YourDomain'
|
|
# EMAIL_DRIVER=LOGGER
|
|
# EMAIL_SMTP_HOST=
|
|
# EMAIL_SMTP_PORT=
|
|
# EMAIL_SMTP_USER=
|
|
# EMAIL_SMTP_PASSWORD=
|
|
# PASSWORD_RESET_TOKEN_EXPIRES_IN=5m
|
|
# CAPTCHA_DRIVER=
|
|
# CAPTCHA_SITE_KEY=
|
|
# CAPTCHA_SECRET_KEY=
|
|
# API_RATE_LIMITING_TTL=
|
|
# API_RATE_LIMITING_LIMIT=
|
|
# MUTATION_MAXIMUM_AFFECTED_RECORDS=100
|
|
# PG_SSL_ALLOW_SELF_SIGNED=true
|
|
# ENTERPRISE_KEY=replace_me_with_a_valid_enterprise_key
|
|
# SERVER_ID=
|
|
# SSL_KEY_PATH="./certs/your-cert.key"
|
|
# SSL_CERT_PATH="./certs/your-cert.crt"
|
|
# CLOUDFLARE_API_KEY=
|
|
# CLOUDFLARE_ZONE_ID=
|
|
# CLOUDFLARE_WEBHOOK_SECRET=
|
|
# IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
|
|
# ANALYTICS_ENABLED=
|
|
# CLICKHOUSE_URL=http://default:clickhousePassword@localhost:8123/twenty
|
|
# HTTP_TOOL_SAFE_MODE_ENABLED=true
|
|
# ALLOW_REQUESTS_TO_TWENTY_ICONS=true
|
|
|
|
# ———————— ENRICHMENT ————————
|
|
# Enriches a new workspace with its own company data, used as AI chat context.
|
|
# Disabled by default; requires both the flag and the API key.
|
|
# IS_WORKSPACE_COMPANY_ENRICHMENT_ENABLED=true
|
|
# PEOPLE_DATA_LABS_API_KEY=
|
|
|
|
# ———————— AI ————————
|
|
# API keys for built-in providers (also editable from Admin Panel > Config Variables):
|
|
# OPENAI_API_KEY=
|
|
# ANTHROPIC_API_KEY=
|
|
# GOOGLE_API_KEY=
|
|
# XAI_API_KEY=
|
|
# GROQ_API_KEY=
|
|
# MISTRAL_API_KEY=
|
|
#
|
|
# Add custom providers (private gateway, extra regions, etc.):
|
|
# AI_PROVIDERS='{"my-gateway":{"type":"openai-compatible","baseUrl":"...","apiKey":"..."}}'
|
|
#
|
|
# Comma-separated model IDs (JSON array syntax also accepted, e.g. '["a","b"]'):
|
|
# AI_MODELS_DEFAULT_FAST=openai/gpt-5-mini,anthropic/claude-haiku-4-5-20251001
|
|
# AI_MODELS_DEFAULT_SMART=openai/gpt-5.2,anthropic/claude-sonnet-4-6
|
|
# AI_MODELS_DEFAULT_RECOMMENDED=openai/gpt-5.2,openai/gpt-4.1,anthropic/claude-sonnet-4-6
|
|
# AI_MODELS_DEFAULT_DISABLED=
|