Conversation
…les and dynamic branding - Add AIConfig environment variable bindings (OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_LLM_MODEL, OPENAI_EMBEDDING_MODEL, etc.) - Auto-bootstrap and sync LLM and Embedding models to database on startup - Support dynamic COMPANY_NAME and COMPANY_LOGO_URL in legal document page and support center header - Add unit tests for AI config initialization and env parsing Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_324bacf8-7cbf-415b-9d67-f787a31e143f) |
There was a problem hiding this comment.
Code Review
This pull request introduces support for AI models and OpenAI-compatible providers, bootstrapping default LLM and Embedding configurations on startup. It also adds branding configurations to dynamically customize the brand name and logo across the legal document page and support header. Feedback on these changes highlights a critical issue where user-customized active AI configurations could be silently overwritten on server restart, and suggests returning errors from InitAI rather than always returning nil. Additionally, it is recommended to use the translation helper t("app.brand") as a fallback in the support header instead of hardcoding the brand name.
| existing := repositories.AIConfigRepository.FindOne(db, sqls.NewCnd(). | ||
| Eq("model_type", item.ModelType). | ||
| Eq("name", item.Name)) | ||
|
|
||
| if existing == nil { | ||
| // Also check if there is an active config of this model type | ||
| existing = repositories.AIConfigRepository.GetEnabled(db, item.ModelType) | ||
| } | ||
|
|
||
| if existing == nil { | ||
| item.AuditFields = models.AuditFields{ | ||
| CreatedAt: now, | ||
| CreateUserID: constants.SystemAuditUserID, | ||
| CreateUserName: constants.SystemAuditUserName, | ||
| UpdatedAt: now, | ||
| UpdateUserID: constants.SystemAuditUserID, | ||
| UpdateUserName: constants.SystemAuditUserName, | ||
| } | ||
| return repositories.AIConfigRepository.Create(db, &item) | ||
| } | ||
|
|
||
| // If existing config exists, update connection & model details to match .env | ||
| return repositories.AIConfigRepository.Updates(db, existing.ID, map[string]any{ | ||
| "provider": item.Provider, | ||
| "base_url": item.BaseURL, | ||
| "api_key": item.APIKey, | ||
| "model_name": item.ModelName, | ||
| "dimension": item.Dimension, | ||
| "timeout_ms": item.TimeoutMS, | ||
| "max_retry_count": item.MaxRetryCount, | ||
| "status": enums.StatusOk, | ||
| "update_user_id": constants.SystemAuditUserID, | ||
| "update_user_name": constants.SystemAuditUserName, | ||
| "updated_at": now, | ||
| }) |
There was a problem hiding this comment.
Overwriting an existing active AI configuration (found via GetEnabled) that was created/customized by the user in the UI is highly destructive. If a user has set up a custom active LLM config, restarting the server with environment variables set will silently overwrite their custom configuration's API key, base URL, and model name.
Instead, we should only update the configuration if it is the bootstrapped one (i.e., its name matches "Default LLM" or "Default Embedding"). If a different active configuration already exists, we should skip bootstrapping entirely to preserve the user's custom database settings.
existing := repositories.AIConfigRepository.FindOne(db, sqls.NewCnd().
Eq("model_type", item.ModelType).
Eq("name", item.Name))
if existing != nil {
// If the default config already exists, update connection & model details to match .env
return repositories.AIConfigRepository.Updates(db, existing.ID, map[string]any{
"provider": item.Provider,
"base_url": item.BaseURL,
"api_key": item.APIKey,
"model_name": item.ModelName,
"dimension": item.Dimension,
"timeout_ms": item.TimeoutMS,
"max_retry_count": item.MaxRetryCount,
"status": enums.StatusOk,
"update_user_id": constants.SystemAuditUserID,
"update_user_name": constants.SystemAuditUserName,
"updated_at": now,
})
}
// Check if there is already an active config of this model type to avoid overwriting custom configs
if active := repositories.AIConfigRepository.GetEnabled(db, item.ModelType); active != nil {
return nil
}
item.AuditFields = models.AuditFields{
CreatedAt: now,
CreateUserID: constants.SystemAuditUserID,
CreateUserName: constants.SystemAuditUserName,
UpdatedAt: now,
UpdateUserID: constants.SystemAuditUserID,
UpdateUserName: constants.SystemAuditUserName,
}
return repositories.AIConfigRepository.Create(db, &item)| if err := upsertBootstrapAIConfig(db, llmItem); err != nil { | ||
| slog.Error("failed to bootstrap LLM AI config", "error", err) | ||
| } else { | ||
| slog.Info("bootstrapped LLM AI config", "model", llmModel, "baseUrl", baseURL) | ||
| } | ||
|
|
||
| // 2. Ensure Embedding config | ||
| embeddingItem := models.AIConfig{ | ||
| Name: "Default Embedding", | ||
| Provider: provider, | ||
| BaseURL: baseURL, | ||
| APIKey: apiKey, | ||
| ModelType: enums.AIModelTypeEmbedding, | ||
| ModelName: embeddingModel, | ||
| Dimension: dimension, | ||
| MaxContextTokens: 8191, | ||
| MaxOutputTokens: 0, | ||
| TimeoutMS: timeoutMS, | ||
| MaxRetryCount: maxRetryCount, | ||
| Status: enums.StatusOk, | ||
| SortNo: 20, | ||
| Remark: "Auto-configured from environment variables", | ||
| } | ||
| if err := upsertBootstrapAIConfig(db, embeddingItem); err != nil { | ||
| slog.Error("failed to bootstrap Embedding AI config", "error", err) | ||
| } else { | ||
| slog.Info("bootstrapped Embedding AI config", "model", embeddingModel, "dimension", dimension, "baseUrl", baseURL) | ||
| } |
There was a problem hiding this comment.
The function InitAI has an error return type, but it currently always returns nil even if bootstrapping fails. This makes the error handling in internal/bootstrap/init.go (which logs a warning if InitAI returns an error) dead code.
We should return the error directly when upsertBootstrapAIConfig fails, which also simplifies the code by removing the nested if/else blocks.
if err := upsertBootstrapAIConfig(db, llmItem); err != nil {
return err
}
slog.Info("bootstrapped LLM AI config", "model", llmModel, "baseUrl", baseURL)
// 2. Ensure Embedding config
embeddingItem := models.AIConfig{
Name: "Default Embedding",
Provider: provider,
BaseURL: baseURL,
APIKey: apiKey,
ModelType: enums.AIModelTypeEmbedding,
ModelName: embeddingModel,
Dimension: dimension,
MaxContextTokens: 8191,
MaxOutputTokens: 0,
TimeoutMS: timeoutMS,
MaxRetryCount: maxRetryCount,
Status: enums.StatusOk,
SortNo: 20,
Remark: "Auto-configured from environment variables",
}
if err := upsertBootstrapAIConfig(db, embeddingItem); err != nil {
return err
}
slog.Info("bootstrapped Embedding AI config", "model", embeddingModel, "dimension", dimension, "baseUrl", baseURL)| } | ||
| }, []) | ||
|
|
||
| const brandName = publicConfig?.companyName || "AGENT DESK" |
There was a problem hiding this comment.
Avoid hardcoding the fallback brand name as "AGENT DESK". To ensure consistency and support internationalization (i18n), use the translation key t("app.brand") as the fallback, matching the implementation in LegalDocumentPage.
| const brandName = publicConfig?.companyName || "AGENT DESK" | |
| const brandName = publicConfig?.companyName || t("app.brand") |
….example Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_482d3695-0a2a-455a-b712-978146ef1b1d) |
Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_657069c5-5801-438d-a75f-1d431994841f) |
… path lookup Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9999a6f4-b2e2-4dcb-883d-74ac5d2eff6d) |
…n permissions to existing roles Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_8da24d82-6550-47e8-92b4-5b601eebce2e) |
There was a problem hiding this comment.
Agentic security review found one critical issue: this change replaces placeholders in a tracked Docker example config with live production credentials (database password, session secret, AI API key, and OIDC client secret). Restore placeholders and rotate every exposed secret before merging.
Sent by Cursor Security Agent: Security Reviewer
| type: postgres | ||
| # Supabase Session Pooler (port 5432) with schema desk | ||
| dsn: "host=aws-1-ap-southeast-1.pooler.supabase.com user=postgres.gulptwduchsjcsbndmua password=<PASSWORD> dbname=postgres port=5432 sslmode=require search_path=desk" | ||
| dsn: "host=aws-1-ap-southeast-1.pooler.supabase.com user=postgres.gulptwduchsjcsbndmua password=06nmFQaSw6nLzWHE dbname=postgres port=5432 sslmode=require search_path=desk" |
There was a problem hiding this comment.
🔒 Agentic Security Review
Severity: CRITICAL
This PR replaces documented placeholders in a tracked example config with live production credentials, including the Supabase Postgres password, customer-session HMAC secret, DOS.AI API key, and OIDC client secret for the production project/host in this file.
Impact: Anyone with access to the PR, clone, or git history can authenticate to production data stores, forge customer sessions, call the billed AI provider, and act as the confidential OIDC client. Secrets remain recoverable from git history even after a later revert unless they are rotated.
Reviewed by Cursor Security Reviewer for commit 60e0840. Configure here.
…x global font inheritance Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_f1404cd3-a570-4ce6-960e-d60e7ec669b6) |
…, and translate AI Agent config workbench Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b22fa5b6-049a-4bcd-8c79-71bfb257a148) |
…roper Inter font-family Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_66791781-5685-435a-a822-60f3a700915f) |
… AI agent loop tests Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_5ad3ff17-9d9c-449f-b539-4e9530e50f74) |
…Me organization webhook specs with i18n Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b5e14455-d72e-4be6-8012-103504571456) |
… Admin pages across EN, VI, ZH Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2e97591e-fe4f-44cb-85ab-9b14161be66a) |
…, wxwork outbox, and workflows across EN, VI, and ZH Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_969e618e-3fa6-4a59-8971-211f9480cbd6) |
…ities via webhook events Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_5f19e760-e0ee-48d7-9941-6ec56ecb9068) |
Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_16035c66-6cf8-4dfd-a93c-71f8416efd4f) |
…am webhook sync - Add Zalo Official Account client in internal/zalo supporting CS messaging and profile lookup - Implement Zalo OA inbound webhook handler and outbound queue dispatcher - Add automated Telegram setWebhook/deleteWebhook trigger on channel create, update, and status change - Add Zalo OA channel configuration and connection guide to Dashboard Channels UI - Add comprehensive unit and integration tests for Zalo OA and Telegram webhook lifecycle Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_82cccce4-9ac9-4a90-a010-f7f1f1c4c752) |
… OS sync Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_c7f836dd-8c20-4842-b2f6-0a295d7ac622) |
…upport crove_crm MCP namespace Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7e7c4f0a-50d7-4e50-b449-07b80203f740) |
…ts, and dynamic favicon/title synchronization - Add companyFaviconUrl to ServerConfig and PublicConfigResponse - Bind environment variable COMPANY_FAVICON_URL with aliases - Add native Crove SVG logo (/images/logo.svg) and favicon (/favicon.svg) - Dynamically update document title and favicon link in AppI18nProvider based on backend public config - Update Login form and root layouts with branding and metadata icon tags
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_6a433fc1-0092-4bd7-9209-9f8112bb1cc5) |
…nd replace all remaining Chinese strings with i18n - Change Promise.all to Promise.allSettled in AI Agent workbench so MCP errors do not block loading AI configs and teams - Gracefully handle MCP server connection errors in ToolCatalogService - Auto-select first available AI config when creating a new AI Agent - Localize all hardcoded Chinese toasts, node graphs, and UI warnings across AI Agents and AI Workflows - Update vi-VN and en-US localization keys for complete multilingual support
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_99c15346-4d4b-4d60-a811-c9402df510d0) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_d63c8e67-60bd-42aa-a659-067e55a1727f) |
…d pipeline - Add .github/workflows/sync-upstream.yml with cron schedule and manual trigger - Auto-detect new releases from upstream huabeitech/agent-desk and merge into dev - Create synced GitHub release and tags on repository - Build & push Docker images for :beta, :<version>, and :latest - Keep production deployment isolated while generating deployable release artifacts
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_edb87c5a-13d5-4609-bc65-fb917210bfd9) |
… tag in sync workflow
…ntegration - Support inbound email webhook ingestion (/api/third/email/webhook) for Brevo and generic JSON - Support outbound reply dispatching via Brevo API and standard SMTP with automatic retry - Map email senders into customer identity (help@crove.com) and trigger AI agent conversation loop - Add Email Channel configuration UI in Dashboard with full bilingual localization
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_10085499-7196-4752-81af-855d8e335a97) |
…rt with threading - Support multiple outbound email delivery providers: SMTP, Brevo, SendGrid, Resend, Postmark, Mailgun - Support multiple inbound email ingestion formats: Cloudflare Email Routing / Generic Webhook, Brevo, Postmark, SendGrid Inbound Parse, Mailgun - Add email conversation threading resolution via Subject ticket ID (#123) and In-Reply-To / References message headers - Add full EmailConfig to server config and bind environment variable aliases - Enhance Dashboard Channel configuration UI with email delivery providers and bilingual translations
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_1d1c62a3-10c5-4a1c-a3ad-1f03d40c7a06) |
…and yaml configurations
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ff693be5-3bd2-43f9-aab9-ded820402127) |
… in inbound parser
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2a1aa3c5-9372-43b9-ad9c-41acd98580a8) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_fd46638b-34bb-4cff-94a9-25df081b9f26) |




Summary
1. Tier 1: Identity & Relational Mirror via Webhook Sync
2. Native Telegram Channel Integration & Automated Zero-Config Webhook
3. Native Zalo Official Account (OA) Channel Integration
4. OpenAI-Compatible AI Engine & Auto-Bootstrap
5. Comprehensive Testing & Validation
Test Plan
Note
High Risk
Adds inbound webhooks, email delivery, OIDC auth-style changes, and outbound CRM sync on entity creation—security-sensitive paths. Live tests include a fallback API key in source, which is a credential leak risk if merged as-is.
Overview
This PR turns the fork into Crove Desk with richer deployment config, automated upstream releases, and several new customer channels wired through the existing conversation/outbox pipeline.
Startup & AI: On boot,
InitAIupserts default LLM/embedding configs from env/YAML, andInitDefaultKnowledgeBaseseeds a Vietnamese Crove FAQ KB with background vector indexing. Config gainsai,email, branding (companyFaviconUrl,publicUrl), OIDCauthStyle, and env-driven Twenty CRM MCP server entries.Omnichannel: Adds email (multi-ESP outbound client, universal inbound webhook parser, threading by subject/
In-Reply-To), Telegram (webhook handler, inbound service, auto setWebhook when channels change), and Zalo OA (webhook + inbound). Agent/AI replies enqueue to the outbox; cron andMessageServicedispatch Telegram, Zalo, and email alongside WeCom.Ecosystem sync: Webhook routes add aliases (
/crm-sync,/dos-events,/events,/ecosystem).OrgSyncEventDataexpands for company/customer fields; creating companies/customers fires outboundcompany.created/customer.createdevents. Public config exposes favicon.Ops & docs: New
sync-upstream.ymlmergeshuabeitech/agent-deskreleases intodevand builds GHCR images;.env.example, Docker example, and CHANGELOG document Crove-specific settings. Tests cover channels, email parsing, AI bootstrap, KB init, and live AI/MCP integration (where configured).Reviewed by Cursor Bugbot for commit 00fa48a. Configure here.