Commit Graph
6546 Commits
Author SHA1 Message Date
aakashb95 be73363b6e Merge remote-tracking branch 'origin/feature/cw-7495-api' into cook/pr-15017-review
# Conflicts:
#	enterprise/app/builders/captain/assistant_stats_builder.rb
#	enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb
#	spec/enterprise/builders/captain/assistant_stats_builder_spec.rb
2026-07-23 15:29:47 +05:30
Sivin VargheseandGitHub 7a76cee723 Merge branch 'feature/cw-7495-llm' into feature/cw-7495-api 2026-07-23 15:12:49 +05:30
Sivin VargheseandGitHub 4842f724ea Merge branch 'develop' into feature/cw-7495-llm 2026-07-23 15:12:34 +05:30
Nicky DuijfandGitHub bae20ca83e feat(conversation): add label search to right-click context menu (#15084) 2026-07-23 15:10:46 +05:30
Sony Mathew 954e5844a8 Merge branch 'release/4.16.1' into develop 2026-07-23 13:58:16 +05:30
Sony Mathew 0efab5fb43 Bump version to 4.16.1 2026-07-23 13:57:15 +05:30
Muhsin KelothandGitHub 34ad78b122 fix(instagram): remove resolved restriction banners (#15136) 2026-07-23 12:56:21 +05:30
aakashb95 8c28fb74ec feat(captain): allow agents to act on FAQ suggestions 2026-07-22 23:36:22 +05:30
aakashb95 2ec33da5cd fix(captain): use display IDs for FAQ source links 2026-07-22 23:35:22 +05:30
aakashb95 63d765b14b Merge remote-tracking branch 'origin/feature/cw-7495-api' into cook/pr-15017-review 2026-07-22 23:33:29 +05:30
aakashb95 b11f2e4a0c fix(captain): revalidate FAQ matches after edits 2026-07-22 23:32:24 +05:30
aakashb95 d5a9747b81 test(captain): cover restricted FAQ suggestion sources 2026-07-22 23:30:35 +05:30
aakashb95 a8ef787878 fix(captain): allow FAQ approval across languages 2026-07-22 23:30:08 +05:30
aakashb95 d85ab03444 fix(captain): retain approved FAQ suggestion sources 2026-07-22 23:29:40 +05:30
aakashb95 b4d2fa5554 Merge remote-tracking branch 'origin/feature/cw-7495-llm' into cook/pr-14979-review 2026-07-22 23:29:08 +05:30
aakashb95 f1753f08b2 fix(captain): match approved FAQs across languages 2026-07-22 23:28:53 +05:30
aakashb95 fc82c7939e refactor(captain): clarify accessible suggestion scope 2026-07-22 23:20:41 +05:30
aakashb95 45e065eca8 docs(captain): note FAQ search indexing follow-up 2026-07-22 23:20:13 +05:30
aakashb95 1ee939a910 feat(captain): allow agents to review FAQ suggestions 2026-07-22 23:19:49 +05:30
Shivam MishraandGitHub ddb0535a93 perf: reuse resolved count for reopen rate (#15122)
This improves the Captain overview by loading reporting metrics and FAQ
stats from separate endpoints. Range changes now refresh only the
metrics, while reopen-rate calculation reuses the resolved conversation
count to avoid redundant database queries.

## What changed

- Split Captain overview metrics and FAQ stats into separate APIs.
- Fetch FAQ stats independently from range-based metrics.
- Reuse resolved conversation totals when calculating reopen rate.
- Skip the reopen query when there are no resolved conversations.
2026-07-22 22:03:25 +05:30
Sivin VargheseandGitHub 42cbf7d3b9 fix: stray backslash after hard breaks before formatted list items (#15112) 2026-07-22 20:07:00 +05:30
Sivin VargheseandGitHub 3ffbaf680c Merge branch 'feature/cw-7495-api' into feature/cw-7496-frontend 2026-07-22 19:21:07 +05:30
887897ea98 fix: lock agent quota checks (#15029)
# Pull Request Template

## Description

Locks the agent quota check to the account row while creating account
users. This fixes a race where concurrent agent-create requests could
all observe the same remaining seat before any `account_users` row was
inserted.

The API continues to return the existing `402 Account limit exceeded.
Please purchase more licenses` response when the limit is reached. Bulk
create now preflights the requested email count while holding the
account lock, then creates each agent through the same locked builder
path. The Enterprise custom-role hook now no-ops when create did not
produce an agent.

Fixes:
[CW-7039](https://linear.app/chatwoot/issue/CW-7039/race-condition-in-agent-creation-bypasses-plan-agent-seat-limit)

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- `POSTGRES_DATABASE=chatwoot_test_c20f_agent_quota REDIS_DB=9 bundle
exec rspec spec/builders/agent_builder_spec.rb
spec/enterprise/builders/agent_builder_spec.rb
spec/controllers/api/v1/accounts/agents_controller_spec.rb
spec/enterprise/controllers/api/v1/accounts/agents_controller_spec.rb
spec/enterprise/controllers/enterprise/api/v1/accounts/agents_controller_spec.rb`
- `bundle exec rubocop app/builders/agent_builder.rb
app/controllers/api/v1/accounts/agents_controller.rb
enterprise/app/controllers/enterprise/api/v1/accounts/agents_controller.rb
spec/builders/agent_builder_spec.rb
spec/enterprise/controllers/api/v1/accounts/agents_controller_spec.rb`
- `git diff --check`
- One-off threaded Rails validation with 8 concurrent `AgentBuilder`
calls against an account with one remaining seat: `created: 1`,
`limited: 7`, final `count=2`, `limit=2`.

## 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

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-07-22 18:43:00 +05:30
8aee518149 fix(integrations): restrict Linear/Notion/Shopify hook deletion to admins (#15126)
Non-admin agents could delete an account's Linear, Notion, or Shopify
integration through the dedicated integration endpoints, which — unlike
the generic hooks endpoint — never checked the caller's role. This
restores the intended admin-only boundary for removing an integration.

## Closes
- https://linear.app/chatwoot/issue/CW-7383
- https://linear.app/chatwoot/issue/CW-7384
- https://linear.app/chatwoot/issue/CW-7189

## How to reproduce
As a non-admin **agent**, `DELETE
/api/v1/accounts/:id/integrations/{linear,notion,shopify}` returned
`200` and removed the account-wide integration. After this change it
returns `401` and the integration is preserved; administrators can still
remove it.

## What changed
- Route integration-hook deletion through `HookPolicy` (admin-only) via
a shared `Integrations::BaseController`, matching the generic hooks
controller.

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-07-22 17:53:25 +05:30
Vishnu NarayananandGitHub 5733b822e3 fix: guard widget email-transcript button against repeat clicks (#15095)
## Description

The widget email-transcript button (`ChatFooter.vue`) had no client-side
guard. Its visibility depends only on whether the contact has an email,
and the click handler fired a request on every click with no in-flight
lock, no disabled state, and no post-send handling. A user could
therefore trigger a large number of duplicate transcript emails from a
single conversation just by clicking repeatedly.

This adds a re-entry guard in the handler, disables the button while a
send is in flight, and applies a short cooldown (15s) after a successful
send. Normal use is unaffected: the button sends once, shows the success
toast, then briefly disables and automatically re-enables so a genuine
later re-request still works. On failure the button stays enabled so the
user can retry immediately. The cooldown timer is cleared on unmount.

Using a timed cooldown (rather than a permanent post-send lock) also
avoids the button getting stuck disabled if a resolved conversation is
reopened and later re-resolved.

This is the client-side complement to the server-side rate limit added
in #15085.

Fixes https://linear.app/chatwoot/issue/CW-7640
2026-07-22 17:36:36 +05:30
Aakash BakhleandGitHub 837343b12d Merge branch 'feature/cw-7495-llm' into feature/cw-7495-api 2026-07-22 16:06:36 +05:30
Sivin VargheseandGitHub 1e52d23d7a fix: guard agent sort against null names in assignment dropdown (#15125) 2026-07-22 15:27:04 +05:30
Sivin VargheseandGitHub fbb3479263 fix: guard agent sort against null names in assignment dropdown (#15125)
# Pull Request Template

## Description

This PR fixes a crash where opening a conversation threw `TypeError:
Cannot read properties of null (reading 'localeCompare')` and prevented
the agent assignment dropdown from rendering.

Since #14866, agent bots are included in the assignable agents list.
`AgentBot#name` is not presence-validated, so system bots (account-less,
global) can have a `null` name. Those nameless bots flowed into
name-based operations that assumed a string, causing crashes and
warnings across multiple surfaces:

* **Assignment dropdown sort:** `getAgentsByAvailability` called
`a.name.localeCompare(b.name)`, causing a `localeCompare` `TypeError`.
* **Dropdown search:** `MultiselectDropdownItems` called
`option.name.toLowerCase()`, causing a `toLowerCase` `TypeError`.
* **Agent Bots settings:** `Avatar` received `name=null` for a `String`
prop, triggering a Vue prop validation warning.

### What changed

* Keep nameless agent bots in the assignment dropdown and render a `-`
fallback label in `useAgentsList`. These are still valid,
assignable-by-ID records: the assignable agents API includes accessible
bots, and `Conversations::AssignmentService` assigns them by ID.
Preserving them avoids hiding valid assignment targets. Bots are still
included only when `includeAgentBots` is enabled.
* Make the sort in `getAgentsByAvailability` null-safe by coercing
missing names to an empty string (defense in depth).
* Make the search filter in `MultiselectDropdownItems` null-safe
(defense in depth).
* Pass a null-safe `name` prop to `Avatar` in the Agent Bots settings
list to eliminate the Vue prop validation warning.

Fixes
https://linear.app/chatwoot/issue/CW-7670/agent-assignment-dropdown-crashes-with-cannot-read-properties-of-null

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?


1. Have a system agent bot (`name: null`) that is assignable to an
inbox.
2. Open any conversation in that inbox.
   * The agent assignment dropdown renders without console errors.
   * The nameless bot is listed with a `-` label and can be assigned.
3. Go to **Settings → Agent Bots**.
   * The page renders without the `Avatar` prop validation warning.


## 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
2026-07-22 15:23:19 +05:30
166a41c31c fix(whatsapp): prevent invalid automation sends outside reply window (#15113)
WhatsApp automations now fail locally when they attempt to send a
free-form message after the 24-hour customer service window has closed.
This avoids sending an invalid template request to Meta and gives users
a clear, actionable error instead of “Template not found or invalid
template name.”

Template messages continue to be sent whenever template parameters are
present. Free-form messages continue to be sent normally while the
conversation is replyable.

Fixes
https://linear.app/chatwoot/issue/PLA-183/prevent-whatsapp-automations-outside-the-24-hour-window-from-producing

### How to reproduce

1. Create a WhatsApp automation that sends a message without template
parameters.
2. Trigger it on a conversation whose 24-hour customer service window is
closed.
3. Observe that the message previously reached the template send path
and failed with a misleading provider error.

### How to test

1. Trigger an automation with template parameters and confirm it sends
as a template message.
2. Trigger an automation without template parameters inside the 24-hour
window and confirm it sends as a free-form message.
3. Trigger an automation without template parameters outside the 24-hour
window and confirm it fails locally with a clear error and makes no
request to Meta.

### Things to know

This changes only the invalid closed-window, no-template path. Existing
template and in-window message behavior remains unchanged.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-07-22 12:40:28 +04:00
aakashb95 9f658fbfaa fix(captain): route FAQ matching model 2026-07-22 13:41:21 +05:30
aakashb95 c183b2aa80 fix(captain): allow FAQ answers across agent messages 2026-07-22 13:36:19 +05:30
aakashb95 9a780e8716 fix(captain): serialize FAQ suggestion grouping 2026-07-22 13:14:48 +05:30
aakashb95 e48d612a55 fix(captain): preserve FAQ suggestion assistant 2026-07-22 13:11:44 +05:30
aakashb95 3662224396 fix(captain): retry failed FAQ comparisons 2026-07-22 13:10:20 +05:30
e65e18e9c5 fix: normalize phone numbers during whatsapp channel lookup (#13709)
Fixes
https://linear.app/chatwoot/issue/PLA-99/whatsapp-messages-dropped-for-brazilargentina-numbers-due-to-phone
Fixes https://github.com/chatwoot/chatwoot/issues/14492

Meta's WhatsApp Cloud API includes `display_phone_number` in webhook
payloads, but its format can differ from the number stored in Chatwoot's
channel record.
In Brazil, Meta omits the mobile 9 prefix. For example, it sends
55419XXXXXXX (12 digits) instead of 554199XXXXXXX (13 digits). In
Argentina, Meta adds an extra 9 after the country code. For example, it
sends 549XXXXXXXXXX instead of 54XXXXXXXXXX.
The whatsapp event job uses `display_phone_number` for an exact-match
channel lookup. When the formats do not match, the lookup returns nil
and the incoming message is silently dropped, logging:

`Inactive WhatsApp channel: unknown - <phone_number>.`

The fix extends `get_channel_from_wb_payload` to fall back to normalized
phone number matching using the existing PhoneNumberNormalizationService
normalizers (Brazil, Argentina), which were previously only used for
contact-level lookups.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-07-22 10:33:40 +04:00
aakashb95 41082f845f test(captain): cover FAQ review behavior 2026-07-22 11:46:15 +05:30
aakashb95 d054c19770 Merge branch 'cook/faq-comparison-api' into cook/faq-suggestion-stack 2026-07-22 11:45:39 +05:30
aakashb95 961c4a8951 test(captain): cover FAQ suggestion details 2026-07-22 11:45:35 +05:30
aakashb95 9d8af17b95 Merge branch 'cook/faq-comparison-api' into cook/faq-suggestion-stack 2026-07-22 11:42:33 +05:30
aakashb95 439ac80019 test(captain): cover FAQ review through API 2026-07-22 11:42:25 +05:30
aakashb95 28fb05005d Merge remote-tracking branch 'origin/feature/cw-7495-api' into cook/faq-suggestion-stack 2026-07-22 10:40:55 +05:30
aakashb95 84c41f03d6 Merge remote-tracking branch 'origin/feature/cw-7495-llm' into cook/faq-comparison-api 2026-07-22 10:39:37 +05:30
aakashb95 a17734676c fix(captain): use mini model for FAQ matching 2026-07-22 10:37:25 +05:30
aakashb95 70a63695c3 fix(captain): fix FAQ suggestion review flows 2026-07-22 00:24:49 +05:30
Shivam MishraandGitHub 89b83c65c8 fix: close message generation popover when its trigger scrolls away (#15114) 2026-07-21 19:34:57 +05:30
aakashb95 325c306a1e Merge remote-tracking branch 'origin/feature/cw-7495-api' into codex/pr-15017-main
# Conflicts:
#	enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb
2026-07-21 18:21:09 +05:30
Aakash BakhleandGitHub e0aa099487 Merge branch 'feature/cw-7495-llm' into feature/cw-7495-api 2026-07-21 18:17:44 +05:30
Aakash BakhleandGitHub 398b84d5de Merge branch 'develop' into feature/cw-7495-llm 2026-07-21 18:17:36 +05:30
ed30ff9c22 fix(whatsapp): allow calling a contact with no existing conversation (#15014)
## Description

Agents can now place a WhatsApp call to a contact straight from the
contacts screen, even if that contact has never messaged in. Previously
the call only worked once a conversation already existed, so a freshly
added contact would fail with "Unable to start the call. Please try
again." — the only workaround was to get the contact to message the
channel first.


## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- Manually via UI

## 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>
2026-07-21 18:08:32 +05:30
aakashb95 c25aac7a99 fix(captain): scope overview suggestion counts 2026-07-21 18:07:02 +05:30