2e84ebb4deae866e7201bcdcaa67cd7cbc879f56
901
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
11c65f3b9a |
feat: Intercom import workflow (#14922)
## Description Adds an admin-only Intercom import workflow under Settings > Data. Admins can connect an Intercom access token, start named historical contact/conversation imports, monitor active and previous import runs, review paginated skip/error logs, download skip logs, and route imported conversations into source-bucket API inboxes that can be renamed later. The import path stores durable source mappings, batches Intercom contact/conversation pages through Sidekiq, records already-imported records as skipped, and writes historical messages without normal outbound delivery callbacks. The PR also includes the Intercom import PRD/TDD document for review context. Closes [CW-7519](https://linear.app/chatwoot/issue/CW-7519/explore-intercom-import) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [x] This change requires a documentation update ## How Has This Been Tested? Tested importing using actual data through integration. Screenshots: <img width="1800" height="948" alt="Screenshot 2026-07-02 at 10 48 48 PM" src="https://github.com/user-attachments/assets/e74d9ed6-0bca-47de-b6ef-e589afcddfde" /> <img width="1800" height="1008" alt="Screenshot 2026-07-02 at 10 49 03 PM" src="https://github.com/user-attachments/assets/1bd12fdb-0a47-4287-ac1d-ea308e70a9cd" /> <img width="1800" height="1005" alt="Screenshot 2026-07-02 at 10 49 21 PM" src="https://github.com/user-attachments/assets/3d8145f5-1794-4cc3-b3fa-de5cd80e6ca3" /> <img width="1800" height="1002" alt="Screenshot 2026-07-02 at 10 49 38 PM" src="https://github.com/user-attachments/assets/6f818efd-4193-43c2-84eb-66970dca4490" /> Passed locally: ```sh eval "$(rbenv init -)" && bundle exec rspec spec/models/data_import_spec.rb spec/jobs/data_import_job_spec.rb spec/requests/api/v1/accounts/data_imports_spec.rb spec/requests/api/v1/accounts/integrations/intercom_spec.rb spec/jobs/data_imports/intercom/import_jobs_spec.rb spec/services/data_imports/intercom/importer_spec.rb spec/services/data_imports/intercom/placeholder_inbox_builder_spec.rb spec/services/data_imports/intercom/source_bucket_spec.rb ``` ```sh eval "$(rbenv init -)" && bundle exec rubocop app/controllers/api/v1/accounts/data_imports_controller.rb app/controllers/api/v1/accounts/integrations/intercom_controller.rb app/jobs/data_imports/intercom app/models/data_import.rb app/models/data_import_error.rb app/models/data_import_item.rb app/models/data_import_mapping.rb app/models/integrations/hook.rb app/policies/data_import_policy.rb app/policies/hook_policy.rb app/services/data_imports/intercom db/migrate/20260702000000_expand_data_imports_for_intercom_imports.rb db/migrate/20260702000001_create_data_import_items.rb db/migrate/20260702000002_create_data_import_mappings.rb db/migrate/20260702000003_create_data_import_errors.rb spec/jobs/data_imports/intercom spec/requests/api/v1/accounts/data_imports_spec.rb spec/requests/api/v1/accounts/integrations/intercom_spec.rb spec/services/data_imports/intercom ``` ```sh pnpm exec eslint app/javascript/dashboard/api/dataImports.js app/javascript/dashboard/api/integrations.js app/javascript/dashboard/routes/dashboard/settings/data/Index.vue app/javascript/dashboard/routes/dashboard/settings/data/Show.vue app/javascript/dashboard/routes/dashboard/settings/data/data.routes.js app/javascript/dashboard/routes/dashboard/settings/data/importStatus.js app/javascript/dashboard/routes/dashboard/settings/integrations/Intercom.vue app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js app/javascript/dashboard/routes/dashboard/settings/settings.routes.js app/javascript/dashboard/components-next/sidebar/Sidebar.vue app/javascript/dashboard/routes/dashboard/settings/inbox/Index.vue ``` ```sh git diff --check ``` Note: the RSpec boot logs the existing local `chatwoot_dev` purge warning because other database sessions are open, then continues and completes with 52 examples, 0 failures. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
9c10fe4eb1 |
fix: default assignment age exclusion (#14998)
## Description Auto-assignment was skipping the 7-day staleness check entirely for inboxes that don't have an assignment policy attached. Those inboxes would pull unassigned conversations of any age off the backlog and hand them to agents — including conversations untouched for months — while the activity log still credited "Default Policy" for the assignment. This makes the default behaviour match what that label implies: with no policy configured, conversations with no activity in the last 7 days are now excluded, the same window a freshly created policy uses. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
b560720e08 |
feat: allow switching embedded signup whatsapp embedded inboxes to manual setup (#14975)
Since embedded signup has been disabled on production, WhatsApp inboxes that were created through it have no way to be managed going forward. This PR lets admins transfer an embedded signup inbox to a manual Cloud API setup: the inbox settings Configuration tab now shows the webhook verification token and a "Switch to Manual Setup" form (pre-populated with the Phone Number ID, Business Account ID, and API key stored during the embedded signup journey) instead of the old Reconfigure button. Saving the form updates the channel's `provider_config` without the `source: embedded_signup` marker, so the inbox becomes a regular manually-configured WhatsApp Cloud inbox. The backend re-validates the submitted credentials against Meta before accepting the change. ## How to test 1. Open the settings page of a WhatsApp inbox created via embedded signup → Configuration tab. 2. The Reconfigure button is gone; you see the webhook verification token and a Switch to Manual Setup form pre-filled with the stored credentials. 3. Configure the webhook in your own Meta app using the verification token, enter a permanent access token from that app, and click "Switch to Manual Setup". 4. On success the page switches to the standard manual configuration view (verify token, API key update), and messaging continues to work with the new credentials. Invalid credentials are rejected with an error. ## What changed - `ConfigurationPage.vue`: replaced the embedded-signup Reconfigure section (and the hidden reauthorize component) with the manual transfer form. - New `WHATSAPP_MANUAL_TRANSFER_*` translation keys. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
66cfb26c77 |
feat: Add unread count filters feature flag (1/6) (#14885)
## Description Adds the account-level `unread_count_for_filters` feature flag as the dark-launch gate for filtered sidebar unread counts. This reuses the deprecated `quoted_email_reply` flag slot, resets the reused bit for existing accounts, and removes stale defaults so new accounts do not reference the old flag. This also adds the feature where we are now calculating the unread counts for built in filters like mentions, participating and unattended along with unread count for saved filters/folders. Closes [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update |
||
|
|
873d16f54c |
feat: Extend account feature flag storage (#14947)
# Pull Request Template ## Description Extends account-level feature flags by adding a second bigint bitset column, `feature_flags_ext_2`, while preserving the existing `flag_shih_tzu` feature check and enable/disable APIs. Existing flags continue to live on `feature_flags`; future flags can opt into the extension column through `config/features.yml` metadata. Fixes [CW-7238](https://linear.app/chatwoot/issue/CW-7238/feature-flag-extension) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - `eval "$(rbenv init -)" && RAILS_ENV=test POSTGRES_DATABASE=chatwoot_test_31a6 bundle exec rspec spec/models/concerns/featurable_spec.rb spec/models/account_spec.rb spec/lib/config_loader_spec.rb spec/controllers/platform/api/v1/accounts_controller_spec.rb spec/controllers/super_admin/accounts_controller_spec.rb spec/enterprise/models/account_spec.rb` - 144 examples, 0 failures - `eval "$(rbenv init -)" && bundle exec rubocop app/models/concerns/featurable.rb app/models/account.rb db/migrate/20260706215758_add_feature_flags_ext_2_to_accounts.rb spec/models/concerns/featurable_spec.rb spec/models/account_spec.rb spec/lib/config_loader_spec.rb spec/enterprise/models/account_spec.rb` - 7 files inspected, no offenses detected - `ruby -ryaml -e "features = YAML.safe_load(File.read('config/features.yml')); abort unless features.size == 63; puts features.group_by { |f| f['column'] || 'feature_flags' }.transform_values(&:size).inspect"` - `{"feature_flags" => 63}` - `git diff --check` ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
ce8c8e9a11 |
fix: sanitize control characters in team names (#14865)
Team names created via the API could contain control characters (for
example a trailing newline). Because the team-delete confirmation dialog
requires you to retype the team name and matches it against the stored
value, a hidden control character meant the typed name never matched —
leaving the team impossible to delete from the UI. This sanitizes team
names on save so they stay clean and deletable.
#### How to reproduce
1. Create a team via `POST /api/v1/accounts/{account_id}/teams` with
`{"name": "test\n"}`.
2. The team is created with the trailing newline stored in `name`.
3. In **Settings → Teams**, click delete and type the team name to
confirm — the match fails, so the team cannot be deleted.
#### What changed
- `app/models/team.rb`: the existing `before_validation` now strips
control characters and surrounding whitespace before downcasing the
name. Names that reduce to blank (e.g. only newlines/tabs) are rejected
loudly by the existing `presence` validation.
- Fixing at the model layer covers the API and every other create/update
path, rather than relying on the frontend confirm-dialog `.trim()`
(which only handles leading/trailing whitespace, not internal control
characters).
Note: this prevents new malformed names. Any team already saved with a
control character can be made deletable again simply by renaming it (an
update re-runs the same sanitization).
| Input | Stored as | Result |
|---|---|---|
| `"test\n"` | `"test"` | valid, deletable |
| `"te\nst"` (internal) | `"test"` | valid |
| `"\t\n "` (only control/ws) | — | rejected: "Name must not be blank" |
| `"Customer Support"` | `"customer support"` | unchanged behavior |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a5fcecb3f6 |
feat: assistant overview page [CW-7408] (#14889)
This PR adds a Captain Assistant **Overview** page to show some KPI metrics (conversations handled, auto-resolution, handoff, hours saved, reopen-after-resolve, conversation depth) with trend deltas vs the previous window, a real knowledge card, and a lazily-loaded, cached LLM welcome summary. ### Highlights - **Two contextual banners** on the overview: - **Inbox banner** — prompts the user to connect an inbox when the assistant has none, so it can actually do work. - **Coverage banner** — warns when FAQ coverage is below 85% with more than 100 responses pending review, linking straight to the pending queue. Dismissal persists per-assistant for 24h via localStorage. - **Batched stats builder** (`Captain::AssistantStatsBuilder`) computes both windows in single FILTER-aggregated scans to cut round trips, behind new `stats`/`summary` endpoints. - **Cards included but intentionally left dummy / not rendered yet:** `ResponseQualityCard` (flagged responses) and `CreditUsageCard` (credit usage + daily chart). Credits are an account-wide counter with no per-assistant or daily history, so there is no real data to back them yet; they ship in the codebase but are not wired into the page. ### Index migration - Replaces `index_messages_on_sender_type_and_sender_id` with `index_messages_on_sender_and_created` `(sender_type, sender_id, created_at)`. - **Why it helps:** the per-assistant windowed lookups filter `sender_*` *and* a `created_at` range. The old 2-column index matched every lifetime row for the assistant and filtered the time slice at the heap (~89% of rows discarded); adding `created_at` as a range column lets Postgres scan only the window, and fixes the row-count estimate so the planner picks a hash join over a nested loop on `reporting_events`. - **Why dropping the old index is safe:** the new index is a left-prefix superset `(sender_type, sender_id, ...)`, so every query the old one served is still served. No code references it by name, and dropping it keeps write amplification on `messages` neutral. Built/dropped with `CONCURRENTLY` and `if_not_exists`/`if_exists` guards. ## Preview <img width="2572" height="1754" alt="CleanShot 2026-06-29 at 22 38 51@2x" src="https://github.com/user-attachments/assets/3798d09e-7850-48e4-b2cd-508533f15cea" /> ## Banners #### Inbox connect alert <img width="2178" height="612" alt="CleanShot 2026-06-30 at 14 26 55@2x" src="https://github.com/user-attachments/assets/373c371c-bb7d-4291-a0f9-620673078302" /> #### Coverage alert <img width="2178" height="612" alt="CleanShot 2026-06-30 at 14 25 41@2x" src="https://github.com/user-attachments/assets/e12d6308-11b6-4ba2-88a2-8a3077dd3e8f" /> --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
34d8741b9b | feat: drag to reorder help center articles across pages (#14910) | ||
|
|
b8b62ad0f1 |
feat: Harden model override preferences (5/6) (#14846)
## Description Hardens the Captain model override preferences API so account-level overrides follow the same feature-router contract used by runtime LLM calls. The API now permits model and feature keys from `llm.yml`, removes blank model overrides, rejects invalid saved model combinations, and returns each feature's effective model, provider, and source for UI clients. Fixes https://linear.app/chatwoot/issue/CW-7425/test-new-models ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Verified the account preferences API and account model validation behavior for valid overrides, invalid model values, unknown feature keys, blank override removal, and effective model/provider/source payload metadata. - `eval "$(rbenv init -)" && bundle exec rspec spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb spec/models/account_spec.rb spec/models/concerns/captain_featurable_spec.rb spec/lib/llm/feature_router_spec.rb` - `eval "$(rbenv init -)" && bundle exec rubocop app/controllers/api/v1/accounts/captain/preferences_controller.rb app/models/concerns/account_settings_schema.rb app/models/concerns/captain_featurable.rb spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb spec/models/account_spec.rb spec/models/concerns/captain_featurable_spec.rb spec/lib/llm/feature_router_spec.rb` - `git diff --check` ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
43e0f8a141 |
feat: Route Enterprise LLM services (3/6) (#14841)
# Pull Request Template ## Description Routes Enterprise assistant, copilot, FAQ, contact memory, action-classifier, and false-promise detector LLM paths through feature-specific model resolution. `Llm::BaseAiService` now accepts feature/account context and uses `Llm::FeatureRouter` when that context is present, while retaining the installation-model fallback for unmigrated callers. This also adds a `document_faq_generation` feature default for generative FAQ/document content. Linear: https://linear.app/chatwoot/issue/CW-7425/test-new-models Depends on #14840 ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - `bundle exec rspec spec/lib/llm/models_spec.rb spec/enterprise/services/llm/base_ai_service_spec.rb spec/enterprise/services/captain/copilot/chat_service_spec.rb spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb spec/enterprise/services/captain/llm/faq_generator_service_spec.rb spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb spec/enterprise/services/captain/llm/assistant_action_classifier_service_spec.rb spec/enterprise/services/captain/llm/assistant_false_promise_service_spec.rb spec/enterprise/jobs/captain/conversation/response_builder_job_spec.rb` passed with 112 examples, 0 failures. - `bundle exec rspec spec/models/concerns/captain_featurable_spec.rb spec/models/account_spec.rb spec/controllers/api/v1/accounts/captain/preferences_controller_spec.rb spec/lib/llm/feature_router_spec.rb` passed with 87 examples, 0 failures. - `bundle exec rubocop enterprise/app/services/llm/base_ai_service.rb enterprise/app/services/captain/copilot/chat_service.rb enterprise/app/services/captain/llm/assistant_chat_service.rb enterprise/app/services/captain/llm/faq_generator_service.rb enterprise/app/services/captain/llm/conversation_faq_service.rb enterprise/app/services/captain/llm/contact_notes_service.rb enterprise/app/services/captain/llm/contact_attributes_service.rb enterprise/app/services/captain/llm/assistant_action_classifier_service.rb enterprise/app/services/captain/llm/assistant_false_promise_service.rb spec/enterprise/services/llm/base_ai_service_spec.rb spec/enterprise/services/captain/copilot/chat_service_spec.rb spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb spec/enterprise/services/captain/llm/faq_generator_service_spec.rb spec/enterprise/services/captain/llm/conversation_faq_service_spec.rb spec/enterprise/services/captain/llm/assistant_action_classifier_service_spec.rb spec/enterprise/services/captain/llm/assistant_false_promise_service_spec.rb spec/enterprise/jobs/captain/conversation/response_builder_job_spec.rb` passed with no offenses. - `bundle exec ruby -e "require 'yaml'; config = YAML.load_file('config/llm.yml'); abort('missing document_faq_generation') unless config.dig('features', 'document_faq_generation'); abort('missing default') unless config.dig('features', 'document_faq_generation', 'default'); puts 'llm.yml ok'"` passed. - `git diff --check` passed. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
8b977b35a8 |
feat: Add LLM feature router (1/6) (#14839)
## Description Adds the foundation for feature-specific LLM model routing so Captain AI features can resolve their effective provider/model from code defaults and account-level overrides. This fixes the provider metadata key in `config/llm.yml`, adds `Llm::FeatureRouter`, and routes existing `CaptainFeaturable` model defaults through the shared resolver. Fixes https://linear.app/chatwoot/issue/CW-7425/test-new-models ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - `bundle exec rspec spec/lib/llm/models_spec.rb spec/lib/llm/feature_router_spec.rb spec/models/concerns/captain_featurable_spec.rb` - 23 examples, 0 failures - `bundle exec rubocop lib/llm/models.rb lib/llm/feature_router.rb app/models/concerns/captain_featurable.rb spec/lib/llm/models_spec.rb spec/lib/llm/feature_router_spec.rb spec/models/concerns/captain_featurable_spec.rb` - no offenses - `bundle exec ruby -e "require 'yaml'; config = YAML.load_file('config/llm.yml'); abort('missing providers') unless config['providers']; abort('missing models') unless config['models']; abort('missing features') unless config['features']; puts 'llm.yml ok'"` - `git diff --check` ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
74db16158d |
feat: migrate dyte integration to cloudflare realtimekit (#14752)
Dyte is sunsetting its existing infrastructure after the Cloudflare acquisition, so this migrates Chatwoot’s video call integration to Cloudflare RealtimeKit. The integration now uses Cloudflare Account ID, RealtimeKit App ID, and a Cloudflare API token with Realtime Admin permissions. Meeting creation and participant token generation now call Cloudflare’s RealtimeKit APIs, while the existing Chatwoot call experience remains unchanged for agents and customers. This also adds setup-time credential validation, so admins get clearer errors when the API token is invalid, the Cloudflare account or permissions are incorrect, or the RealtimeKit App ID does not belong to the selected account. Fixes https://linear.app/chatwoot/issue/PLA-176/migrate-dyte-integration-to-cloudflare-realtimekit **How to test** 1. Go to Settings → Integrations → Cloudflare RealtimeKit. 2. Add a Cloudflare Account ID, RealtimeKit App ID, and API token with Realtime Admin permissions. 3. Confirm the integration saves successfully with valid credentials. 4. Try invalid credentials and confirm the error identifies whether the token, account/permissions, or app ID is wrong. 5. Start a video call from a conversation and confirm the RealtimeKit meeting opens. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Sony Mathew <sony@chatwoot.com> |
||
|
|
e8edc9ebf5 | feat: allow users to set an icon or emoji for teams (#14767) | ||
|
|
84fda8a323 |
fix(captain): move false promise harness setting to account (#14823)
# Pull Request Template ## Description moves the harness to account settings rather than assistant settings ## Type of change refactor ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally and specs ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
df79c0bbde |
feat: exclude stale conversations via assignment policy age threshold (#14766)
## Linear ticket - https://linear.app/chatwoot/issue/CW-7137/assignment-v2-backlog-flush-overloads-agents ## Description Assignment policies now skip stale unassigned conversations automatically. Each policy carries an age threshold (defaults to 7 days), so auto-assignment only picks up recent backlog instead of draining very old, forgotten conversations. The threshold is configurable per policy and can be cleared to assign conversations regardless of age. Previously this control existed only on Enterprise capacity policies (`exclude_older_than_hours`); it now lives on the assignment policy itself, so every V2 inbox benefits without needing a capacity policy. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Local UI flows ## Screenshots? <img width="645" height="822" alt="image" src="https://github.com/user-attachments/assets/ec58db86-c1fa-4f9e-be87-2e24ff02e077" /> ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sony Mathew <sony@chatwoot.com> |
||
|
|
f66b551c7d |
revert: Sidebar unread counts for filters (CW-7262) (#14769)
## Description Reverts [#14726](https://github.com/chatwoot/chatwoot/pull/14726) (\"feat: Add sidebar unread counts for filters (CW-7262)\"), which shipped in 4.15.0. After 4.15.0 rolled out to prod the unread-counts-for-filters code path caused a cascading incident: - `Counter#ensure_filters_cache!` fires on every `/unread_counts/index` and `update_last_seen` request. - On cache miss it calls `Builder#build_filters_for!`, which: - invokes `store.clear_user_filters!` -> `delete_matching` -> a Redis `SCAN_each` over a per-user pattern keyspace, and - runs 4 fresh SQL passes per user (mentions, participating, unattended, and per-folder `Conversations::FilterService` queries). - Threads blocked in the SCAN held their DB connections, the connection pool exhausted, Sidekiq jobs were discarded with `ActiveJob::DeserializationError: could not obtain a connection from the pool`, and the enqueued queue blew past 200K. Related: [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
fe6368b42e |
feat: Add sidebar unread counts for filters (CW-7262) (#14726)
## Description Extends the conversation unread-count system so the left sidebar can show unread badges for Mentions, Participating, Unattended, and saved conversation folders. Folder badges reuse the existing `custom_filters` conversation filter semantics, store user-scoped Redis sets lazily, and skip unsupported folder filters so invalid saved folders continue to render without a badge. The Unattended badge counts all visible unread open conversations that match the existing unattended conversation scope. Closes - [CW-7262](https://linear.app/chatwoot/issue/CW-7262/unread-counts-for-filters-folders) ## What changed - Added user-scoped unread-count Redis keys and cache builders for mentions, participating conversations, unattended conversations, and saved folder filters. - Reused `Conversations::FilterService` through a relation-returning path so folder counts match the folder conversation list behavior. - Invalidated user filter caches from mention, participant, custom-filter, and relevant conversation update events. - Extended the unread-count endpoint payload and sidebar Vuex/sidebar rendering for the new badge counts, including the Unattended sidebar item. - Added Ruby, Enterprise, request, listener, and frontend store coverage for the new unread-count dimensions. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Created local validation folders for `john@acme.inc` and confirmed the unread-count payload includes open, resolved, and high-priority folder badges while excluding the invalid unsupported folder. - Added coverage for the Unattended badge rule: all visible unread open conversations matching `Conversation.unattended`. - Ran focused unread-count Ruby specs, including service, listener, request, and Enterprise counter coverage. - Ran frontend unread-count store specs. - Ran RuboCop on the touched Ruby files. - Ran ESLint through the project script; it completed with warnings in existing unrelated files and no errors. <img width="369" height="525" alt="Screenshot 2026-06-13 at 10 51 39 PM" src="https://github.com/user-attachments/assets/36b1d2c4-dac1-4f6f-9c0e-7ef5a6cc2975" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] Documentation changes are not required for this internal unread-count behavior - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] No dependent downstream changes are required --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
41a3ab6dfa |
feat(companies): add contact company selector (#14496)
Adds a company selector to the contact details form so agents can associate a contact with an existing company directly from the contact page. Closes - None Why Contacts already expose company information through the CRM fields, but the form only accepted free-text company names. As we split company CRM work into smaller PRs, this keeps the contact page aligned with the structured company model while preserving the existing company-name behavior used by automations. What changed - Shows a company dropdown in the contact details form when the Companies feature is enabled. - Keeps legacy free-text company names editable when a contact has no structured `company_id`. - Allows Enterprise contact create/update APIs to accept account-scoped `company_id`. - Syncs `additional_attributes.company_name` when a contact is associated with a company, including the existing email-domain auto-association path. - Serializes `company_id` in the contact model payload so the form can show the current association. How to test 1. Enable Companies for an account and open a contact details page. 2. In Edit contact details, use the Company field to select an existing company. 3. Save the contact and refresh the page. 4. Confirm the selected company remains visible and the contact is associated with that company. 5. Confirm contacts with only a legacy free-text company name still show the text input instead of an empty selector. --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com> |
||
|
|
4816e923b6 |
chore: added a new sort by unread option for conversations (CW-7152) (#14512)
## Description Added a new option for the sort by option in conversation filters called unread. This is to filter out unread conversations. Fixes # CW-7152 ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tested manually in local. <img width="1397" height="603" alt="Screenshot 2026-05-20 at 10 40 20 PM" src="https://github.com/user-attachments/assets/6c60263e-907b-419f-a7ed-06010bbe8736" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
ba0ba46c9c |
fix: skip session tracking and use short-lived token for impersonation (CW-7169) (#14622)
## Description SuperAdmin impersonation SSO logins no longer create UserSession rows visible to the customer. Impersonation tokens use a 2-day lifespan instead of ~2 months, so they naturally evict first and don't linger in the user's token list. Server-side detection via Redis value (`'impersonation'` vs `'normal'`) without changing the `valid_sso_auth_token?` signature. Backward compatible with in-flight tokens. Depends on #14556. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Specs cover: impersonation login skips UserSession creation, impersonation token has short lifespan, normal SSO login still creates UserSession row. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
27404b4a27 |
fix: redact sensitive integration secrets from API responses (#14147)
## Summary The `GET /api/v1/accounts/:id/integrations/apps` endpoint returns raw secret values (OpenAI API keys, Google service account private keys, Linear refresh tokens, etc.) in hook settings within the JSON response. Although gated behind an administrator check, these secrets are visible in the browser network tab. This PR filters hook settings through the existing `visible_properties` whitelist defined in `config/integration/apps.yml`, and adds explicit whitelists to integrations that were missing them. Closes #14042 ## Bug reproduction **Setup:** Created an OpenAI integration hook with a fake API key (`sk-test-secret-12345`). **Step 1 — Browser Network tab shows raw secrets:** <img width="1676" height="869" alt="Screenshot 2026-04-24 at 14 32 28" src="https://github.com/user-attachments/assets/6fc69463-365e-458b-b216-98a7390bf528" /> Navigate to Settings > Integrations as an admin. Open DevTools Network tab and observe the response from `GET /api/v1/accounts/:id/integrations/apps`. The hook settings contain the full API key in plaintext: ```json "hooks": [ { "id": 1, "app_id": "openai", "settings": { "api_key": "sk-test-secret-12345", "label_suggestion": false } } ] ``` **Step 2 — API call confirms the leak:** ```bash curl -s "http://localhost:3000/api/v1/accounts/2/integrations/apps" \ -H "access-token: <token>" \ -H "client: <client>" \ -H "uid: user@test.com" \ | jq '.payload[] | select(.id == "openai") | {id, name, hooks: [.hooks[] | {id, app_id, settings}]}' ``` Response: ```json { "id": "openai", "name": "OpenAI", "hooks": [ { "id": 1, "app_id": "openai", "settings": { "api_key": "sk-test-secret-12345", "label_suggestion": false } } ] } ``` Any admin user can extract the raw API key from the response. The same applies to other integrations — Dialogflow exposes full Google service account credentials, Linear exposes refresh tokens, etc. ## After fix After applying this change, the GET /api/v1/accounts/:id/integrations/apps endpoint no longer returns sensitive secret values in hook settings. Instead, the response is filtered using each integration’s visible_properties whitelist, ensuring only safe, user-facing fields are exposed. For example, OpenAI integrations return non-sensitive fields like label_suggestion while excluding raw API keys. This prevents secrets from being exposed in the browser network tab or API responses, even for authenticated admin users. ```bash curl -X GET "http://localhost:3000/api/v1/accounts/2/integrations/apps" \ -H "Accept: application/json" \ -H "Authorization: Bearer eyJhY2Nlc3MtdG9rZW4iOiJqZG5jOWg4TnljaWFJa3JlZkxGQzRnIiwidG9rZW4tdHlwZSI6IkJlYXJlciIsImNsaWVudCI6Ik81bjVnTDFEOVVhbGpwbWxjaHZNanciLCJleHBpcnkiOiIxNzgyMzMyNTA4IiwidWlkIjoidXNlckB0ZXN0LmNvbSJ9" \ -H "access-token: jdnc9h8NyciaIkrefLFC4g" \ -H "client: O5n5gL1D9UaljpmlchvMjw" \ -H "uid: user@test.com" \ | jq '.payload[] | select(.id == "openai") | {id, name, hooks: [.hooks[] | {id, app_id, settings}]}' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 10174 100 10174 0 0 33232 0 --:--:-- --:--:-- --:--:-- 33357 { "id": "openai", "name": "OpenAI", "hooks": [ { "id": 1, "app_id": "openai", "settings": { "label_suggestion": false } } ] } ``` ## What changed - Added `visible_properties` accessor to `Integrations::App` model to expose the whitelist from config - Updated `_hook.json.jbuilder` to filter `resource.settings` through the associated app's `visible_properties` instead of returning the full hash - Added `visible_properties` to integrations that were missing it: - Linear: `[]` (settings contain refresh_token) - Notion: `[]` (OAuth-based, no user-facing settings) - Slack: `['channel_name']` (UI needs this to display connected channel) - Shopify: `[]` (no settings) App config metadata (`_app.json.jbuilder`) is left unchanged — hook_type, settings_form_schema, etc. are not secrets and the frontend depends on them. ## How to test 1. Create an OpenAI integration hook with an API key 2. As an admin, call `GET /api/v1/accounts/:id/integrations/apps` 3. Verify hook settings include `api_key` (whitelisted) but not raw credential objects 4. For Dialogflow hooks, verify `credentials` (private key JSON) is excluded while `project_id` is included 5. For Slack hooks, verify `channel_name` is still returned 6. For Linear hooks, verify `refresh_token` is not returned --------- Co-authored-by: Botshelo Nokoane (Konstruktors) <botshelo@entersekt.com> Co-authored-by: Sony Mathew <sony@chatwoot.com> Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com> |
||
|
|
92a1fb8ab7 |
feat: manage active user sessions from profile (CW-7169) (#14556)
## Description First PR of user_sessions feature - enforcement, impersonation and mfa will be handled separately. Adds an Active Sessions section under Profile where users can see every device currently logged in and revoke any session they don't recognize. Helps users lock down stale or unrecognized logins on their own without needing support. **Behavior at the limit, by client:** - **Browser:** returns 409 with a picker overlay; user picks a session to revoke or chooses "End all sessions" to clear them. - **Mobile / API client:** silently evicts the oldest session and proceeds with login (no picker UI to render). - **Pre-tracking users** (token rows without `user_sessions`, i.e. anyone already logged in before this ships): silent-evict any untracked token first, so freshly tracked sessions are never killed in favor of legacy ones. Sessions are stored in a new `user_sessions` table keyed on `(user_id, client_id)` with browser, platform, IP, last activity and (when configured) geo. Kept in sync with `user.tokens` via an after_save callback so revoking a token from any path cleans up the row. Fixes https://linear.app/chatwoot/issue/CW-7169 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? - Added specs. - Manual local testing: browser picker fires at limit; pre-tracking user silent-evicts; mixed tracked/untracked correctly drops the untracked one first; profile page revoke succeeds; current session cannot be revoked from profile. ## Checklist - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
e055cead35 |
fix: only subscribe calls webhook field when voice calling enabled (#14718)
## Description Solves issue https://github.com/chatwoot/chatwoot/issues/14690 ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How has this been tested? - UI flows ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
6d26e7930a |
fix(contacts): truncate inbound contact names (IMAP sender display names) (#14631)
## Description Truncates long contact names in `ContactInboxWithContactBuilder` before persisting them, so inbound IMAP sender display names over the `contacts.name` 255-character limit no longer raise `ActiveRecord::RecordInvalid` from `Inboxes::FetchImapEmailsJob`. This keeps email ingestion moving while preserving the existing contact name length constraint. Linear ticket: [https://linear.app/chatwoot/issue/CW-7263/sentry-active-record-invalid-contact-create-from-imap-fetch-job](https://linear.app/chatwoot/issue/CW-7263/sentry-active-record-invalid-contact-create-from-imap-fetch-job) ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - [x] `bundle exec rspec spec/builders/contact_inbox_with_contact_builder_spec.rb` - [x] `bundle exec rspec spec/mailboxes/imap/imap_mailbox_spec.rb` - [x] `bundle exec rubocop app/builders/contact_inbox_with_contact_builder.rb spec/builders/contact_inbox_with_contact_builder_spec.rb` ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes |
||
|
|
8d5d02ea97 |
feat: add per-inbox toggle to disable incoming calls (#14645)
## Description Adds a per-inbox "Allow incoming calls" toggle for voice-enabled WhatsApp and Twilio inboxes. When turned off, the setting is persisted on the channel; actually rejecting inbound calls is handled in a follow-up PR. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## Screenshot <img width="804" height="384" alt="Screenshot 2026-06-04 at 11 44 07 AM" src="https://github.com/user-attachments/assets/df8bb026-0387-4031-bcba-6d9a56872eb7" /> ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
64c5aeebee |
feat(help-center): support per-locale portal title, name & header (#14642)
Portals can now override their name, page title and header text per locale, with a fallback chain of locale override → default locale → base value. Overrides are stored under portal.config.locale_translations and validated with a JSON schema. Editing is exposed as a "Localize content" action in each non-default locale's menu on the Locale page, and all public-facing portal views (classic + documentation layouts) render the localized values. The live chat widget is also loaded in the active portal locale. <img width="494" height="395" alt="Screenshot 2026-06-03 at 2 48 59 PM" src="https://github.com/user-attachments/assets/e55a06e8-f20f-4d8a-9352-92fde28a9a19" /> https://github.com/user-attachments/assets/f5affbd2-7ad4-415a-b376-57c759fc2aaa --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0d59fb4459 | fix: validate portal color format (#14632) | ||
|
|
87df43bdd0 |
revert: restore conversation unread count feature flag (#14623)
This reverts #14610 so conversation unread counts are again controlled by the `conversation_unread_counts` feature flag across the API, ActionCable broadcasts, notifier/listener paths, and dashboard sidebar fetching. ## Closes - None ## What changed - Restores feature-flag checks for conversation unread count reads and broadcasts. - Restores the dashboard feature flag constant and sidebar/store behavior for disabled unread counts. - Restores the specs that cover disabled-feature behavior. ## How to test - In an account with `conversation_unread_counts` enabled, verify sidebar unread counts are fetched and updated in real time. - Disable `conversation_unread_counts` for the account and verify unread count requests/broadcasts are skipped. |
||
|
|
ecd9c26c8c |
feat: Implemented search results page functionality (#11086)
# Pull Request Template
## Description
Implemented search results page functionality. Now you can press "Enter"
to search by term and display results in a results page. Also now you
can link to /hc/{account}/en/search?query=XXXXXX to view search results
for XXXXXX query.
fixes: https://github.com/chatwoot/chatwoot/issues/10945
## Screenshots
Classic layout search results:
<img width="3840" height="2160" alt="classic-results"
src="https://github.com/user-attachments/assets/3bbb3272-33ca-4eb4-b80a-76ed77442088"
/>
Classic layout pagination:
<img width="3840" height="2160" alt="classic-page-two"
src="https://github.com/user-attachments/assets/062b09d3-7c58-4d3b-8611-b94375e7db51"
/>
Classic layout empty search:
<img width="3840" height="2160" alt="no-results"
src="https://github.com/user-attachments/assets/c5e3f47a-cd9a-4e14-ae92-ccba00c89e98"
/>
Documentation layout search results:
<img width="3840" height="2160" alt="documentation-results"
src="https://github.com/user-attachments/assets/9e45d8d9-c975-4589-b6c6-3bc7bb3c588e"
/>
Documentation layout dark theme:
<img width="3840" height="2160" alt="documentation-dark"
src="https://github.com/user-attachments/assets/cdb6ed63-4241-4b32-9f79-7d92ed479fc8"
/>
Plain embedded dark layout:
<img width="3840" height="2160" alt="plain-embedded-dark"
src="https://github.com/user-attachments/assets/7deb02b9-9f24-48fb-8979-a2ecd7002c05"
/>
---------
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
|
||
|
|
88e2661ca6 |
feat(conversations): remove unread count feature flag (CW-7237) (#14610)
## Description Make conversation unread counts always available at runtime by removing account feature checks from the API endpoint, unread-count listener, notifier, and ActionCable broadcast path. Update the dashboard to fetch sidebar unread counts for the active account without checking FEATURE_FLAGS.CONVERSATION_UNREAD_COUNTS, and remove the now-unused store clear action that only supported the disabled state. Keep the feature entry in config/features.yml to preserve flag bit order, but mark it enabled and deprecated so fresh installs default to the always-on behavior while feature-management UI hides it. Leave existing installation default rows untouched; no migration is included, so upgraded installs may still store the old flag value but runtime behavior no longer depends on it. Update specs around the new always-on contract and remove obsolete disabled-feature assertions. Fixes # CW-7237 ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Update specs around the new always-on contract and remove obsolete disabled-feature assertions. Ran specs locally for the changes. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
f27bbef73b |
feat: show processing status for one-off campaigns (#14592)
## Summary One-off SMS and WhatsApp campaigns now show a `Processing` state while the audience send is in progress. The campaign moves to `Completed` after processing finishes, and already-processing campaigns are skipped by the scheduler to avoid duplicate sends. ## Closes - [CW-6037: feat: Introduce an in-progress status for campaigns](https://linear.app/chatwoot/issue/CW-6037/feat-introduce-an-in-progress-status-for-campaigns) ## Screenshot SMS campaign card showing the new `Processing` status. <img width="3840" height="2160" alt="framed-campaign-processing-status" src="https://github.com/user-attachments/assets/de7913b5-65fb-4121-9034-24a568eb0382" /> ## What changed - Added `processing` as a campaign status. - Mark one-off campaigns as `processing` under a row lock before the send service runs. - Complete SMS, Twilio SMS, and WhatsApp one-off campaigns after audience processing finishes. - Keep campaigns in `processing` if an unexpected service error escapes, so the scheduler does not automatically resend the audience. - Added the `Processing` label for SMS and WhatsApp campaign cards. ## Known operational behavior If a worker is interrupted or an unexpected service error escapes after a campaign is marked `processing`, the campaign can remain in `processing`. This is intentional for now to avoid automatic full-audience resends. Installation admins can decide whether to mark the campaign completed or restart it manually from the Rails console after checking what was sent. ## How to test - Create a one-off SMS or WhatsApp campaign scheduled for now. - Run the scheduled job or trigger the campaign job. - Confirm the campaign card shows `Processing` while the audience is being processed. For small audiences, refresh during processing or use a larger audience so the state is observable. - Confirm the campaign moves to `Completed` after audience processing finishes. - Confirm an already-processing campaign is not enqueued again by the scheduled job. |
||
|
|
68e358d732 |
feat: voice-call UX fixes (#14579)
## Linear ticket https://linear.app/chatwoot/issue/CW-7187/voice-calls-followup-tasks ## Description Improvements to the WhatsApp voice-calling experience plus a cheaper, more accurate audio-transcription model. - First-time callers now get a real name. An inbound WhatsApp call creates the contact from the caller's WhatsApp profile name instead of the bare phone number. - Clear, consistent call attribution. Call bubbles show a unified "Handled by {agent}" - Cleaner call widget. The dismiss (✕) button is shown only for incoming calls - WhatsApp calling for manual inboxes. voice_calling_supported? now covers any whatsapp_cloud inbox - Transcription: whisper-1 → gpt-4o-mini-transcribe. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
c4a6a19e9b |
feat(voice): WhatsApp Cloud Calling — UI [6] (#14346)
## Summary Frontend for WhatsApp Cloud Calling: header / contact-panel call buttons, ringing widget, accept/reject/hangup, mute, in-bubble audio player + transcript, recording-on-hangup upload, mid-call reload warning. WebRTC is browser-direct to Meta — no media server bridge. ## Closes - https://linear.app/chatwoot/issue/PLA-150 ## How to test Requires backend support — the controller, services, model changes, and routes ship in **#14334** (`feature/pla-150`). Merge / deploy that first (or simultaneously); the FE alone won't function without those endpoints. Then on staging, for a WhatsApp Cloud + embedded-signup inbox with the new \`Configuration → Enable voice calling\` toggle ON and webhook registered: 1. **Outbound** — open a conversation, click the phone icon in the conversation header (or contact panel), grant mic, your phone rings, answer, audio both ways, hang up. Recording + transcript land in the bubble within ~10s. 2. **Inbound** — call the business number from your phone. The FloatingCallWidget appears bottom-right with caller name. Click accept, audio both ways, hang up. Recording + transcript appear. 3. **Mute** — during an active WhatsApp call, click the mic icon next to hangup. Speech stops reaching Meta until you click again. 4. **Mid-call reload guard** — try `Cmd-R` during an active call; browser shows a confirm prompt. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
bef25781de |
feat(attachments): add XML and PFX file support (#14539)
Update frontend allowed file types and FileIcon mapping, and backend Attachment constants to accept .xml and .pfx files # Pull Request Template ## Description Customer also wanted XML support along with .pfx Following up on #14456 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally <img width="864" height="512" alt="CleanShot 2026-05-22 at 11 43 20@2x" src="https://github.com/user-attachments/assets/4cbf65d4-b919-4a4b-bf75-a4f2e8690586" /> <img width="870" height="1440" alt="CleanShot 2026-05-22 at 11 44 03@2x" src="https://github.com/user-attachments/assets/e763b49d-4365-4c45-9b43-b0c39af87656" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
3c67c41544 |
chore: support PFX filetype in attachment uploads (#14456)
# Pull Request Template ## Description This PR expands the default upload rules to support PFX certificate files (`application/x-pkcs12`, `application/pkcs12`, `.pfx`) across private notes, Website, Email, and Telegram channels. Also adds `.xls` / `.xlsx` extension fallbacks for cases where browsers upload Excel files with an empty or generic MIME type. ### Utils Repo PR: https://github.com/chatwoot/utils/pull/61 Fixes https://linear.app/chatwoot/issue/CW-7085/support-more-file-types-in-private-notes-and-in-app ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshots <img width="330" height="218" alt="image" src="https://github.com/user-attachments/assets/80823250-893e-4509-adb9-61f845359151" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: aakashb95 <aakashbakhle@gmail.com> |
||
|
|
d0ecdc14d8 |
feat(webhooks): Emit inbox_updated when an inbox is disconnected (#14504)
Chatwoot now lets external apps know when an inbox loses its connection and needs re-authentication. When a channel's authorization expires (for example, an email inbox disconnects), Chatwoot fires an `inbox_updated` webhook reflecting the new `reauthorization_required` status, and fires it again once the inbox is re-authenticated. Integrators can keep their own view of which inboxes are healthy without polling the API. This is gated behind the `ENABLE_INBOX_EVENTS` installation flag — the **Inbox updated** webhook subscription only appears in the dashboard when that flag is enabled, so no event is offered that the backend wouldn't dispatch. Fixes https://linear.app/chatwoot/issue/CW-7148/emit-inbox-webhook-when-an-inbox-is-disconnected ## How to test 1. Set `ENABLE_INBOX_EVENTS=true` and restart the app. 2. In **Settings → Integrations → Webhooks**, add a webhook and subscribe to **Inbox updated**. 3. Disconnect an inbox — let an email/Instagram channel hit its auth-error threshold, or run `inbox.channel.prompt_reauthorization!` in a console. 4. The endpoint receives an `inbox_updated` event whose `changed_attributes` shows `reauthorization_required` flipping to `true`. 5. Re-authenticate the inbox (or run `inbox.channel.reauthorized!`) — the endpoint receives the `true → false` transition. 6. Confirm the **Inbox updated** option is hidden when `ENABLE_INBOX_EVENTS` is unset. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> |
||
|
|
3cd8cf43ce |
fix: atomically claim conversation to prevent duplicate assignment (#14495)
## Description Fixes a bug under Assignment V2 where a single conversation could be reassigned dozens of times in a row by the system, producing long stacks of "Assigned to X by Automation System via <policy>" activity messages alternating between agents. After this change each unassigned conversation is assigned exactly once, even on busy inboxes. ## Fixes # (issue) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ## How to reproduce 1. Enable `assignment_v2` on an account with at least 2 online agents in an inbox. 2. Generate sustained resolve/snooze activity in the inbox (each one enqueues `AutoAssignment::AssignmentJob` for the whole inbox). 3. Watch any one unassigned conversation while the jobs drain — pre-fix it picks up multiple back-to-back "Assigned to …" activity rows alternating between agents. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules |
||
|
|
f33e469e9a |
feat: Unread Count: Frontend changes for showing unread count badges (3/3)[CW-6851] (#14372)
# Pull Request Template ## Description This is the third and final PR in a series of PRs for Introducing unread counts in the sidebar for inboxes and labels. In this PR: * Added frontend changes to show the badges for unread counts for Inboxes and Labels * Added specs for the changes Issue: https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Tested this locally. Cases to test: * Send a message from the widget and see if the count changes * Mark a conversation as unread and see the count change for inbox * Open an unread conversation as agent and see the count go down * Add a label to an unread conversation from sidebar right click action without opening the conversation and see the count of un-reads on the label change Added the screenshot of how it will look like <img width="614" height="990" alt="Screenshot 2026-05-05 at 7 00 11 PM" src="https://github.com/user-attachments/assets/99fbaa9f-bcf2-4d8d-86e2-5727f652a9dd" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> |
||
|
|
27f2c2b392 |
feat: Unread Count: added api, store refresher, invalidation and events (2/3)[CW-6851] (#14369)
# Pull Request Template ## Description This is the second PR in a series of PRs for Introducing unread counts in the sidebar for inboxes and labels. In this PR: * added api for unread counts * Added the store refresher and invalidation with event listeners * Added action cable event * Added specs for the changes Issue: https://linear.app/chatwoot/issue/CW-6851/support-unread-conversation-counts ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> |
||
|
|
6560dbb68d |
feat: Add an option on the dashboard to allow switching help center layout (#14491)
<img width="633" height="431" alt="Screenshot 2026-05-18 at 12 32 55 PM" src="https://github.com/user-attachments/assets/682d4c5f-4c76-465b-8d2f-92fbc2bb2a40" /> --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> |
||
|
|
64585faff0 |
feat: Add a documentation layout design for public help center portal (#14403)
https://github.com/user-attachments/assets/fc4d15f9-2b54-4627-940f-94772ec739b1 --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> |
||
|
|
3253e863ed |
fix: validate OpenAI hook credentials (#14068)
# Pull Request Template ## Description - Validates openai key while configuring hooks - added backfill logic Fixes # (issue) ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. locally <img width="1710" height="1234" alt="CleanShot 2026-04-15 at 16 15 02@2x" src="https://github.com/user-attachments/assets/3d319fe0-19f9-4fd0-9308-74987daac2e1" /> <img width="2884" height="1136" alt="CleanShot 2026-05-11 at 19 22 53@2x" src="https://github.com/user-attachments/assets/5eae8650-985b-4c4a-af42-35f7175ff52d" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com> |
||
|
|
059d840272 |
feat: Refresh llm settings when superadmin configs change [AI-151] (#14388)
# Pull Request Template ## Description fixes: https://linear.app/chatwoot/issue/AI-151/captains-super-admin-config-dont-get-applied-into-rails-without ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. specs and locally To test locally: go to super admin -> settings -> captain -> Change endpoint to something incorrect go to local app -> captain -> playground -> try chatting (should fail due to incorrect endpoint) now in super admin captain settings, set the correct endpoint then chat in playground. Now it should work. Current develop code doesn't reflect the changes in installation config for captain instantly, needs a server restart. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules |
||
|
|
ffbf40c720 |
fix: harden Active Storage direct uploads and proxy streaming (#14440)
Hardens Active Storage handling on Rails 7.1 by filtering internal direct-upload metadata keys and limiting proxy range requests, while keeping audio playback on redirect URLs so large recordings are not routed through the proxy limiter. Closes - CVE-2026-33173 - CVE-2026-33174 - CVE-2026-33658 Why Rails 7.1 does not currently have patched releases for these Active Storage advisories, and Chatwoot exposes Active Storage direct-upload endpoints and media URLs. This keeps the Rails dependency unchanged while adding small local mitigations until Rails can be upgraded to 7.2.3.1+. What changed - Filters `identified`, `analyzed`, and `composed` from direct-upload blob metadata. - Limits Active Storage proxy range requests to one range under 100 MB. - Uses redirect URLs for inline audio attachments so normal playback of large recordings avoids the proxy streaming path. - Adds scoped bundle-audit ignores for the locally mitigated Active Storage advisories and the remaining Rails advisories that are not reachable through current Chatwoot usage. How to test - Upload an attachment from the dashboard reply composer and confirm it sends successfully. - Upload an attachment from the website widget and confirm it appears in the conversation. - POST a direct-upload request with `blob.metadata.identified`, `blob.metadata.analyzed`, and `blob.metadata.composed`; confirm those keys are not persisted while custom metadata remains. - Play an audio/call-recording attachment and confirm the audio URL loads through Active Storage redirect rather than proxy. - Run `bundle exec bundle audit check -v`. --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
dd7f5c27e5 |
perf: eliminate N+1 queries on inboxes#index (#14451)
The `inboxes#index` API endpoint was firing 3 queries per inbox while rendering the response — one each for the polymorphic channel, avatar attachment, and working hours. For accounts with many inboxes this turned a routine list call into a multi-second request — measured at 35 seconds on an account with 5,000 inboxes. Two root causes: 1. `policy_scope` was silently discarding the controller's `.includes(...)` chain because `InboxPolicy::Scope#resolve` returns a fresh `user.assigned_inboxes` relation, ignoring whatever scope is passed in. So the eager loading never actually applied. 2. `Inbox#weekly_schedule` called `working_hours.order(...).select(...)` which fires a new query per inbox even when the association is preloaded. The fix chains the eager loads and ordering after `policy_scope` so they apply to the relation the policy actually returns, adds `:portal` and `:working_hours` to the preload list, and refactors `weekly_schedule` to sort the preloaded collection in Ruby and build the hash directly. Result: queries drop from O(n) to a constant ~15 regardless of inbox count, and total request time drops 5–6× across every account size tested. Response payload is byte-identical. ## How to test 1. Open the agent dashboard for an account with a large number of inboxes (1000+). On a stock dev DB you can seed quickly with `Seeders::AccountSeeder` or by creating a few hundred `Channel::Api` inboxes via the rails console. 2. Hit any UI surface that lists inboxes (settings → inboxes, sidebar inbox list, agent assignment dropdowns). Should feel near-instant where it previously hung. 3. Confirm every inbox shows the same data as before — channel type, working hours, avatar, portal/help-center link. No fields should be missing or different. ## Benchmarks Real HTTP requests via curl against a dev Rails server (median of 5 timed trials after warmup; "Server total" is the time reported in Rails' `Completed 200 OK in <X>ms` log line). | Account | Channel mix | OLD | NEW | Speedup | |---|---|---|---|---| | 1000 inboxes | 3 types, no portals/avatars | 5.87 s | 0.96 s | **6.1×** | | 5000 inboxes | 3 types, no portals/avatars | 33.07 s | 6.93 s | **4.8×** | | 5000 inboxes | 10 types, 25% portals, 10% avatars | 35.04 s | 6.55 s | **5.4×** | Detailed breakdown for the realistic 5000-inbox case: | | OLD | NEW | Δ | |-----------------|-----------|----------|----------| | Server total | 35,042 ms | 6,550 ms | −81% | | Views | 32,034 ms | 6,452 ms | −80% | | ActiveRecord | 2,948 ms | 92 ms | −97% | | Allocations | 42.3 M | 11.3 M | −73% | | Queries | ~15,000 | 15 | −99.9% | ## What changed - `app/controllers/api/v1/accounts/inboxes_controller.rb` — chain `.includes(:channel, :portal, :working_hours, avatar_attachment: :blob).order_by_name` *after* `policy_scope(...)`. The previous code passed them into `policy_scope` but the policy resolver dropped the chain. - `app/models/concerns/out_of_offisable.rb` — `weekly_schedule` now sorts the preloaded `working_hours` collection in Ruby and constructs the result hashes inline, avoiding both the per-inbox query from `.order(...).select(...)` and the `as_json` reflection overhead. Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
05bda5f742 |
feat: don't let onboarding write domain (#14442)
Stop the onboarding flow from writing the user's company website into `accounts.domain`. That column is reserved for the inbound email domain used to construct reply-to addresses (`reply+<uuid>@<domain>`), and silently overloading it from onboarding was breaking email continuity for accounts whose domain MX didn't point at Chatwoot's inbound — customer replies were going to an unreachable address. The website value now lives in `custom_attributes.website`, which is what the rest of the app already treats as the "company website" field. |
||
|
|
de696a55cb |
feat(voice): add WhatsApp inbound call webhook pipeline [3] (#14315)
Adds the server-side flow that turns Meta WhatsApp Cloud Calling webhooks into Chatwoot Calls, conversations, voice_call message bubbles, and ActionCable broadcasts. Stacked on top of #14312 (PR-2 — provider methods); intentionally does not include the HTTP controller, routes, or frontend (those land in PR-4 and PR-9). ## Closes - Part of the WhatsApp Cloud Calling rollout. Linear: TBD ## What changed **Webhook routing** - `app/jobs/webhooks/whatsapp_events_job.rb` — append `prepend_mod_with('Webhooks::WhatsappEventsJob')` so EE can extend it without forking. - `enterprise/app/jobs/enterprise/webhooks/whatsapp_events_job.rb` (new) — overlay that prepends `handle_message_events` to intercept `field: 'calls'` payloads (route to `Whatsapp::IncomingCallService`) and `interactive.call_permission_reply` messages (route to `Whatsapp::CallPermissionReplyService`); falls through with `super` for regular messages. **Services** - `enterprise/app/services/whatsapp/incoming_call_service.rb` (new) — gated on `provider_config['calling_enabled']`; processes `connect` (creates inbound call via `Voice::InboundCallBuilder` or transitions an existing outbound call to `in_progress`) and `terminate` events; updates conversation `additional_attributes` and broadcasts `voice_call.incoming`/`voice_call.outbound_connected`/`voice_call.ended`. - `enterprise/app/services/whatsapp/call_permission_reply_service.rb` (new) — handles WhatsApp interactive `call_permission_reply` replies; clears the conversation's `call_permission_requested_at` flag and broadcasts `voice_call.permission_granted` so the agent UI can re-enable the call button. **Builder/model adjustments** - `enterprise/app/services/voice/inbound_call_builder.rb` — provider-agnostic; accepts `provider:` and `extra_meta:` kwargs, drops `account:` (now derived from `inbox.account` to keep the param count under rubocop's ceiling without disabling cops), uses digits-only `source_id` for WhatsApp ContactInbox (validation requires `^\d{1,15}\z`), skips Twilio-only `conference_sid` for non-Twilio providers. - `enterprise/app/services/voice/call_message_builder.rb` — adds `create!`/`update_status!` API and `CALL_TO_VOICE_STATUS` map; uses direct `Message.create!` (bypasses `Messages::MessageBuilder`'s incoming-on-non-Api-inbox guard, which would otherwise reject the system bubble); content is `'WhatsApp Call'` for WhatsApp and `'Voice Call'` for Twilio. Backwards-compatible `perform!` retained for the existing Twilio call sites. - `enterprise/app/models/call.rb` — adds `default_ice_servers` (driven by `VOICE_CALL_STUN_URLS` env), `direction_label` alias for the `inbound`/`outbound` strings the FE expects, and `ringing?`/`in_progress?`/`terminal?` predicates used throughout the pipeline. **Outgoing-channel guard** - `app/services/base/send_on_channel_service.rb` — extends `invalid_message?` to skip messages with `content_type == 'voice_call'`. Without this, agent-initiated outbound calls (PR-4) would deliver \"WhatsApp Call\" as a text message to the contact every time. **Twilio call-site update** - `enterprise/app/controllers/twilio/voice_controller.rb` — drops the now-redundant `account: current_account` kwarg from the `Voice::InboundCallBuilder.perform!` call. **Tests** - New: `spec/enterprise/services/whatsapp/incoming_call_service_spec.rb` (5 examples — calling-disabled, inbound connect, outbound connect, terminate completed, terminate no-answer, unknown event). - New: `spec/enterprise/services/whatsapp/call_permission_reply_service_spec.rb` (3 examples — accept, reject, calling-disabled). - Updated: `spec/enterprise/services/voice/inbound_call_builder_spec.rb` and `spec/enterprise/controllers/twilio/voice_controller_spec.rb` to drop the `account:` kwarg from call expectations. ## How to test In `rails console` against an account with a WhatsApp inbox where `provider_config['calling_enabled']` is true: ```ruby inbox = Inbox.find(<id>) params = { calls: [{ id: 'wacid_test', from: '15550001111', event: 'connect', session: { sdp: 'v=0...', sdp_type: 'offer' } }] } Whatsapp::IncomingCallService.new(inbox: inbox, params: params).perform # => Conversation + Call (status: 'ringing', provider: 'whatsapp') + voice_call message bubble # => ActionCable broadcasts `voice_call.incoming` to the assignee or account-wide # Then terminate it: Whatsapp::IncomingCallService.new(inbox: inbox, params: { calls: [{ id: 'wacid_test', event: 'terminate', duration: 0, terminate_reason: 'no_answer' }] } ).perform # => Call status flips to 'no_answer', message bubble updates, `voice_call.ended` broadcast fires ``` End-to-end browser flow (Meta → cable → UI) requires the controller from PR-4 and the frontend from PR-9. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
202403873d |
feat: Ability to specify the authentication type for imap server (#12306)
# Pull Request Template ## Description This PR adds IMAP authentication mechanism selection to Chatwoot's email inbox configuration. Users can now choose between 'plain', 'login', and 'cram-md5' authentication methods when configuring IMAP settings, providing flexibility for different email providers that require specific authentication types. https://github.com/chatwoot/chatwoot/issues/8867 The implementation includes: - Frontend dropdown with numeric keys (1, 2, 3) matching SMTP auth style - Backend API validation for allowed authentication mechanisms - Consistent 'cram-md5' format throughout the codebase - Updated IMAP service to handle different auth types properly This feature maintains consistency with existing SMTP authentication options and follows the established UI/UX patterns in the application. ## Type of change Please delete options that are not relevant. - [x] New feature (non-breaking change which adds functionality) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? ### Manual Testing: - Tested in Docker environment - Verified IMAP auth dropdown appears in inbox settings - Confirmed all three auth mechanisms (plain, login, cram-md5) can be selected and saved - Tested API validation by attempting to save invalid auth mechanisms ### Automated Testing: - Updated existing IMAP service tests to use consistent lowercase values - Updated API controller tests for authentication parameter handling - All tests pass locally with the new changes ### Test Configuration: - Tested with both new and existing inbox configurations ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] Any dependent changes have been merged and published in downstream modules ## Additional Notes - This feature is backward compatible and doesn't break existing IMAP configurations - The 'cram-md5' format is used consistently throughout (UI, API, storage, services) - Net::IMAP compatibility is maintained by converting to 'CRAM-MD5' internally - Follows the same pattern established by SMTP authentication configuration --------- Co-authored-by: João Santos <joao.santos@madigital.eu> Co-authored-by: Sony Mathew <sony@chatwoot.com> |
||
|
|
fe44b07147 | feat(companies): add company detail page (#14054) | ||
|
|
2a30e7b082 |
fix: render agent variables in automation messages (#14338)
# Pull Request Template
## Description
This PR fixes an issue where agent variables like
`{{agent.name}}`,`{{agent.first_name}}`, `{{agent.last_name}}`, and
`{{agent.email}}` were not rendering in automation messages.
In automation, these either showed blank or returned `Liquid error:
internal`, while the same variables worked fine in macros.
**Cause**
Automation messages are created without a sender, so agent data was
missing during variable rendering. This also caused errors in name
handling, and `email` was not defined at all.
**Solution**
* Handle missing agent data safely to avoid errors
* Add support for `{{agent.email}}`
* Fallback to conversation assignee when sender is not present
Fixes
https://linear.app/chatwoot/issue/CW-6979/template-variables-not-working-in-automated-messages
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Screenshots
**Automation**
<img width="759" height="284" alt="image"
src="https://github.com/user-attachments/assets/61a877b7-4984-4a7f-bbef-b8c510dcbdfe"
/>
**Before**
<img width="404" height="105" alt="image"
src="https://github.com/user-attachments/assets/da665ce8-137d-4249-8ee5-a1acc11391db"
/>
**After**
<img width="564" height="132" alt="image"
src="https://github.com/user-attachments/assets/6a80d67c-49c8-4658-b782-ae4acbc77256"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
|