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: Pro Plus 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_fc640df8-1ba1-457c-a82d-66ce8dfcde36) |
…reation and register webhook route aliases 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_dda91314-7d68-41ee-8cf6-9923cd839e88) |
…-connect gateway - Add Telegram Bot API client and types in internal/telegram - Implement inbound webhook handler and customer message ingestion in TelegramInboundService - Implement asynchronous outbound message delivery via TelegramOutboundService and channel outbox queue - Add Telegram channel management and automated setup in Dashboard Channels UI - Update webhook signature verification and timestamp checking for event synchronizations - Add full unit and integration tests across Telegram and Webhook services 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_0e7255a3-0c71-4c95-8ac2-b2de6c534cdb) |
…upabase example config 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) |




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
Medium Risk
Touches webhook HMAC auth, OIDC token exchange, and new inbound third-party endpoints that create customers/conversations; misconfiguration or weak webhook secrets could allow spoofed events or message injection.
Overview
This PR turns the fork into Crove Desk with richer ops/docs (changelog, expanded
.env.example,.gitignorefor secrets) and a scheduled GitHub Action that merges upstreamhuabeitech/agent-deskreleases intodevand publishes GHCR images.Ecosystem sync extends org webhooks: more event names and payload fields, company/customer upsert handlers, outbound
company.created/customer.createddispatch withX-DOS-Event, and HMAC verification supportingt=…,v1=…plus replay windows. Extra webhook route aliases (/crm-sync,/dos-events,/events,/ecosystem) hit the same handler.Omnichannel adds Telegram, Zalo OA, and email as first-class channel types—config parsing, inbound webhooks under
/api/third/*, outbox enqueue from agent/AI replies, and cron dispatch. Telegram channels can auto register/delete webhooks fromPUBLIC_URL. Email adds a Brevo/SMTP client, inbound parsing (generic + Brevo), and outbound ticket replies.AI & knowledge: new
aiconfig/env bindings,InitAIupserts default LLM/embedding rows on startup,InitDefaultKnowledgeBaseseeds Vietnamese Crove FAQs with background Qdrant indexing, MCP CRM server injection from env, and OIDC client auth style selection. Public API exposes favicon branding.Tests and live integration suites cover webhooks, channels, and DOS.AI; MCP tool catalog skips failing servers instead of failing the whole catalog.
Reviewed by Cursor Bugbot for commit 3817f4d. Configure here.