Commit Graph
6284 Commits
Author SHA1 Message Date
Tanmay Deep Sharma 3d914065e0 feat(inbox): preserve channel logo with voice badge in inbox/sidebar
Inboxes with voice enabled were showing a generic phone icon in the
inbox list and sidebar, hiding the underlying channel (e.g. WhatsApp).
Keep the native channel logo and overlay an audio-waveform badge when
voice is enabled. The native Twilio Voice inbox (TwilioSms with no
WhatsApp medium) still resolves to the phone icon.
2026-05-13 16:28:40 +05:30
Tanmay Deep Sharma 5d9c979ace feat(voice): resolve country name from country_code in call widget
When a contact has only country_code (e.g. "US") stored in
additional_attributes and not the full country name, the floating call
widget now falls back to the shared countries list to render
"City, Country" in the location line.
2026-05-13 15:02:36 +05:30
Tanmay Deep Sharma a985010a40 feat(inbox): show voice badge on channel cards when voice is enabled
Adds an audio-waveform badge overlay on the WhatsApp and Voice (Twilio)
channel cards in the new-inbox selector when the channel_voice feature
flag is enabled on the account.
2026-05-13 15:02:28 +05:30
Tanmay Deep SharmaandClaude Opus 4.7 ddc863c6f7 feat(voice): Redesign call widget and in-thread call bubbles
Brings the floating call widget and in-thread voice-call bubbles in
line with the new Figma design across Twilio and WhatsApp, light and
dark.

- New CallCard component for the floating widget with incoming /
  outgoing / ongoing states, rounded-square avatar, status badges,
  duration timer, and "Go to conversation thread" footer.
- Floating widget header now shows the contact's city, country, and
  country flag (derived from country_code) when available, falling
  back to the channel icon + inbox name.
- In-thread VoiceCall bubble redesigned: 44x44 tonal icon container,
  title + duration/subtext, audio player + transcript when a
  recording exists, and a "Call back" pill for missed inbound calls.
- isOutbound on the call bubble now accepts both outgoing/outbound
  and incoming/inbound spellings and falls back to message
  orientation, so the label can no longer disagree with which side
  of the thread the bubble sits on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:44:20 +05:30
Tanmay Deep Sharma d765e7c106 Merge remote-tracking branch 'origin/develop' into feat/whatsapp-call-ui
# Conflicts:
#	spec/enterprise/services/voice/inbound_call_builder_spec.rb
2026-05-12 17:38:27 +05:30
71cc5168be feat(linear): Auto link Linear issues from private notes (#14405)
When an agent pastes a Linear issue URL into a private note on a
conversation, Chatwoot now links the issue to the conversation
automatically — no need to click "Link to Linear issue" first. The
standard activity message ("X linked Linear issue ABC-123") is posted
just like a manual link.

Fixes
[CW-7032](https://linear.app/chatwoot/issue/CW-7032/if-someone-post-a-linear-url-in-the-private-notes-automatically-link)

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-05-12 13:03:40 +04:00
58fdd20625 test(voice): WhatsApp Cloud Calling specs [5] (#14357)
Backend test coverage for the WhatsApp Cloud Calling pipeline introduced
in #14356. Stacked on top of that PR so the controller and service under
test exist when CI runs.

## Closes
- Replaces #14348 (which was based on the abandoned \`feature/pla-150\`)

## What's covered

-
\`spec/enterprise/controllers/api/v1/accounts/whatsapp_calls_controller_spec.rb\`
(new, ~210 lines)
- \`show / accept / reject / terminate / initiate / upload_recording\`
happy paths
- 422 paths: missing sdp_offer, missing recording, calling_disabled
inbox, missing contact phone, ringing-state guards, AlreadyAccepted,
NotRinging, CallFailed
- 138006 (no permission) → throttled opt-in template send under
conversation lock; idempotency on retry
  - \`upload_recording\` idempotency guard (\`already_uploaded\`)

- \`spec/enterprise/services/whatsapp/call_service_spec.rb\` (new, ~135
lines)
- State machine: ringing → in_progress → completed; ringing → failed
(reject); ringing → no_answer (terminate)
- Lock contention: concurrent terminate during accept doesn't corrupt
the message/conversation broadcast
- Provider failure paths surface as \`Voice::CallErrors::CallFailed\`
(transport and business)

- \`spec/models/channel/whatsapp_spec.rb\` — extends existing file with
\`voice_enabled?\` matrix (provider × source × calling_enabled)

## Verification

- 77/77 examples pass locally on this branch (controller + service +
channel + incoming-call + permission-reply + open-ai message builder)
- RuboCop clean

## Stack

- Backend: #14356 (\`feat/whatsapp-call-meta-bridge\` — base of this PR)
- FE: #14346 (\`feat/whatsapp-call-ui\`)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:40:13 +05:30
6c67eb9ba0 fix(notifications): Respect conversation access when notifying agents (#14412)
Agents with limited custom roles were receiving notifications (creation,
assignment, mentions, new messages, SLA) for conversations they couldn't
actually open. For example, an agent whose custom role only grants
`conversation_unassigned_manage` was getting notified about
conversations assigned to other agents.

Notifications now go through the same `ConversationPolicy#show?` check
that gates the conversation view itself, so an agent only gets notified
for conversations they're permitted to see. Administrators and agents
without custom roles are unaffected.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-05-12 10:57:29 +04:00
Sivin VargheseandGitHub 3df827c931 chore: update Captain documents filter UI (#14429) 2026-05-12 11:27:30 +05:30
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>
2026-05-12 11:23:57 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
79a7423f9f chore(deps): bump nokogiri from 1.19.1 to 1.19.3 (#14410)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.19.1
to 1.19.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sparklemotion/nokogiri/releases">nokogiri's
releases</a>.</em></p>
<blockquote>
<h2>v1.19.3 / 2026-04-27</h2>
<h3>Fixed / Security</h3>
<ul>
<li>Address exponential regex backtracking in CSS selector tokenizer.
See <a
href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-c4rq-3m3g-8wgx">GHSA-c4rq-3m3g-8wgx</a>
for more information.</li>
<li>[CRuby] Address memory leak in
<code>XSLT::Stylesheet#transform</code>. See <a
href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-v2fc-qm4h-8hqv">GHSA-v2fc-qm4h-8hqv</a>
for more information.</li>
</ul>
<!-- raw HTML omitted -->

<pre><code>46b89e5d7b9e844c2ee360794240c6ea2a4e6fa0c5892a4ed487db621224b639
nokogiri-1.19.3-aarch64-linux-gnu.gem
8392dfdcd21be7a94dbbe9ccc138dea01b97b24cb2dc02a114ca98bfb1d9a0b7
nokogiri-1.19.3-aarch64-linux-musl.gem
3919d5ffc334ad778a4a9eb88fda7dcb8b1fb58c8a52ac640c6dcd2f038e774f
nokogiri-1.19.3-arm-linux-gnu.gem
9ce1cb6346bb9c67b1550eb537aa183ead91e4b6eadb2f36ade02d8dd2a79fb6
nokogiri-1.19.3-arm-linux-musl.gem
71b9bd424b1b7abc18b05052a1a3cfd3627abdca62be280854cc411791357e42
nokogiri-1.19.3-arm64-darwin.gem
40ea6ebf5cf2005dae1dee26dd557d3afb41fb6de6c9764aca8cf06fdb841db1
nokogiri-1.19.3-java.gem
8bb7132cad356c879a1286eaabcb5e68326cb2490317984280fbc62f456d506a
nokogiri-1.19.3-x64-mingw-ucrt.gem
77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d
nokogiri-1.19.3-x86_64-darwin.gem
2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976
nokogiri-1.19.3-x86_64-linux-gnu.gem
248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f
nokogiri-1.19.3-x86_64-linux-musl.gem
78312cbac32a40c812780d9678221b79d51288eec00054c1a8d15f7ce05960e8
nokogiri-1.19.3.gem
</code></pre>
<h2>v1.19.2 / 2026-03-19</h2>
<h3>Dependencies</h3>
<ul>
<li>[JRuby] Saxon-HE is updated to 12.7, from 9.6.0-4. Saxon-HE is a
transitive dependency of nu.validator:jing, and this update addresses
CVEs in Saxon-HE's own transitive dependencies JDOM and dom4j. We don't
think this warrants a security release, however we're cutting a patch
release to help users whose security scanners are flagging this. <a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3611">#3611</a>
<a
href="https://github.com/flavorjones"><code>@​flavorjones</code></a></li>
</ul>
<h3>SHA256 Checksums</h3>

<pre><code>c34d5c8208025587554608e98fd88ab125b29c80f9352b821964e9a5d5cfbd19
nokogiri-1.19.2-aarch64-linux-gnu.gem
7f6b4b0202d507326841a4f790294bf75098aef50c7173443812e3ac5cb06515
nokogiri-1.19.2-aarch64-linux-musl.gem
b7fa1139016f3dc850bda1260988f0d749934a939d04ef2da13bec060d7d5081
nokogiri-1.19.2-arm-linux-gnu.gem
61114d44f6742ff72194a1b3020967201e2eb982814778d130f6471c11f9828c
nokogiri-1.19.2-arm-linux-musl.gem
58d8ea2e31a967b843b70487a44c14c8ba1866daa1b9da9be9dbdf1b43dee205
nokogiri-1.19.2-arm64-darwin.gem
e9d67034bc80ca71043040beea8a91be5dc99b662daa38a2bfb361b7a2cc8717
nokogiri-1.19.2-java.gem
8ccf25eea3363a2c7b3f2e173a3400582c633cfead27f805df9a9c56d4852d1a
nokogiri-1.19.2-x64-mingw-ucrt.gem
7d9af11fda72dfaa2961d8c4d5380ca0b51bc389dc5f8d4b859b9644f195e7a4
nokogiri-1.19.2-x86_64-darwin.gem
fa8feca882b73e871a9845f3817a72e9734c8e974bdc4fbad6e4bc6e8076b94f
nokogiri-1.19.2-x86_64-linux-gnu.gem
93128448e61a9383a30baef041bf1f5817e22f297a1d400521e90294445069a8
nokogiri-1.19.2-x86_64-linux-musl.gem
38fdd8b59db3d5ea9e7dfb14702e882b9bf819198d5bf976f17ebce12c481756
nokogiri-1.19.2.gem
</code></pre>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/sparklemotion/nokogiri/compare/v1.19.1...v1.19.2">https://github.com/sparklemotion/nokogiri/compare/v1.19.1...v1.19.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md">nokogiri's
changelog</a>.</em></p>
<blockquote>
<h2>v1.19.3 / 2026-04-27</h2>
<h3>Fixed / Security</h3>
<ul>
<li>Address exponential regex backtracking in CSS selector tokenizer.
See <a
href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-c4rq-3m3g-8wgx">GHSA-c4rq-3m3g-8wgx</a>
for more information.</li>
<li>[CRuby] Address memory leak in
<code>XSLT::Stylesheet#transform</code>. See <a
href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-v2fc-qm4h-8hqv">GHSA-v2fc-qm4h-8hqv</a>
for more information.</li>
</ul>
<h2>v1.19.2 / 2026-03-19</h2>
<h3>Dependencies</h3>
<ul>
<li>[JRuby] Saxon-HE is updated to 12.7, from 9.6.0-4. Saxon-HE is a
transitive dependency of nu.validator:jing, and this update addresses
CVEs in Saxon-HE's own transitive dependencies JDOM and dom4j. We don't
think this warrants a security release, however we're cutting a patch
release to help users whose security scanners are flagging this. <a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3611">#3611</a>
<a
href="https://github.com/flavorjones"><code>@​flavorjones</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/c139a3da0fe0cae7499a0bafa20f2875877c585b"><code>c139a3d</code></a>
version bump to v1.19.3</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/7501a63b9f4246d12516e35b91fed8be34f854c0"><code>7501a63</code></a>
fix: backtracking in CSS tokenizer rules (v1.19.x backport) (<a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3627">#3627</a>)</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/03e7968a730a6544ab56a8d6c3e82dd630ad4339"><code>03e7968</code></a>
test: skip CSS tokenizer benchmarks on JRuby</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/b984b7e47f622d1aa97d54c16d5cd596c3eb9538"><code>b984b7e</code></a>
fix: ReDoS in CSS tokenizer ident rule</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/00926231e28d5a20e5b4873efba36099aea0d5c6"><code>0092623</code></a>
fix: ReDoS in CSS tokenizer STRING rule</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/ee17d33aff3adb30c14e71d3d4c8163465acaccf"><code>ee17d33</code></a>
fix: memory leak in XSLT transform (backport to v1.19.x) (<a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3624">#3624</a>)</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/ce188a395192e3757d8701949afb643dc025084c"><code>ce188a3</code></a>
doc: update CHANGELOG</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/caeaac41f874f0944f9397c78bf6c1bfac2cb472"><code>caeaac4</code></a>
fix: memory leak in XSLT transform</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/25220bf268c9808e28415563ed7f8ea8d5c332bf"><code>25220bf</code></a>
dep(test): test against libxml-ruby v6 (<a
href="https://redirect.github.com/sparklemotion/nokogiri/issues/3618">#3618</a>)</li>
<li><a
href="https://github.com/sparklemotion/nokogiri/commit/0caeb21a5c5e9ff45bbede88fb53655f6753bb0e"><code>0caeb21</code></a>
doc: add security warnings for untrusted XSLT stylesheets</li>
<li>Additional commits viewable in <a
href="https://github.com/sparklemotion/nokogiri/compare/v1.19.1...v1.19.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nokogiri&package-manager=bundler&previous-version=1.19.1&new-version=1.19.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-11 18:49:31 -07:00
Sivin VargheseandGitHub 85ddc68834 fix: prevent bulk action checkbox reset in team view (#14432) 2026-05-12 07:13:48 +05:30
Sojan JoseandGitHub 086aa36ffe feat(companies): add notes and history to company details (#14401) 2026-05-11 23:15:25 +05:30
f6be0d80ef feat: UI changes for document auto sync [AI-153] (#14258)
# Pull Request Template

## Description

FE code for document sync

Adds:
- UI to show counts (stats) of available web pages, stale and synced
documents and last synced at
- Bulk action and manual ways to sync web documents
- index to stats related columns

## Type of change

Please delete options that are not relevant.

- [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.

https://linear.app/chatwoot/issue/AI-153/fe-document-auto-sync

Documents dashboard:
<img width="2160" height="986" alt="CleanShot 2026-05-11 at 17 57 09@2x"
src="https://github.com/user-attachments/assets/6d934764-964c-4656-b005-1b4f0329e553"
/>

Filters:
<img width="1138" height="564" alt="CleanShot 2026-05-11 at 17 58 13@2x"
src="https://github.com/user-attachments/assets/cee780e6-eb8f-4aed-8cc5-b674244a821b"
/>

Needs update:
<img width="2222" height="966" alt="CleanShot 2026-05-11 at 17 57 53@2x"
src="https://github.com/user-attachments/assets/70c85ddd-7eb1-4328-ba14-7929e67e7b36"
/>

pdfs:
<img width="2180" height="558" alt="CleanShot 2026-05-11 at 17 58 30@2x"
src="https://github.com/user-attachments/assets/975b5c9f-bd1c-4979-9870-8f926d7f6e11"
/>

bulk actions:
<img width="2244" height="992" alt="CleanShot 2026-05-11 at 17 58 57@2x"
src="https://github.com/user-attachments/assets/bdb3c63f-d2de-41dc-a6d5-8821d3303be0"
/>

single url sync:
<img width="2264" height="722" alt="CleanShot 2026-05-11 at 17 59 19@2x"
src="https://github.com/user-attachments/assets/7d7323a5-0fcb-4be9-8635-55e56964999b"
/>



## 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: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
2026-05-11 20:13:29 +05:30
Shivam MishraandGitHub 3489298726 feat: add WidgetCreationService for onboarding web widget setup (#14314)
When a new account finishes onboarding we want to land them on a
dashboard with a working web widget already configured, branded, named,
and assigned to them, instead of an empty inbox list. This PR adds the
services that produce that widget. **No user-visible change yet:** the
services are dormant until the trigger and background job are wired up
in the follow-up PR.

## Context

Milestone 1 added `Account::BrandingEnrichmentJob`, which calls
context.dev during signup and stores brand data on
`account.custom_attributes['brand_info']`, plus the new onboarding form
that captures `domain`, `name`, `industry`, etc. Milestone 2 starts
using that data, and the first thing we want is a web widget
materialized automatically. Splitting the service layer from the
orchestration plumbing (Redis key, `onboarding_step` extension,
controller wiring, ActionCable) keeps this diff focused and lets the
LLM/widget logic merge independently.

## How to test

Run against an existing account that already has `brand_info` populated.

```ruby
account = Account.find(<account_id>)
user    = account.administrators.first
inbox   = WidgetCreationService.new(account, user).perform

inbox.channel.widget_color     # color from brand_info, or '#1f93ff'
inbox.channel.welcome_title    # brand_info[:title], or account.name
inbox.channel.welcome_tagline  # LLM tagline (Enterprise + system key set),
                               # else brand_info[:slogan]/[:description]/nil
inbox.inbox_members.pluck(:user_id)
```

Toggle `InstallationConfig['CAPTAIN_OPEN_AI_API_KEY']` to flip between
LLM and brand-text tagline paths. To verify failure isolation, raise
inside `Captain::Llm::WidgetTaglineService#perform` and confirm widget
creation still succeeds with the fallback tagline.
2026-05-11 16:10:48 +05:30
Shivam MishraandGitHub 2e13f69fdf chore: log errors from context.dev (#14310)
This PR updates the way we log errors and results from context.dev to
have better visibility on the enrichment process for onboarding
2026-05-11 16:09:45 +05:30
Shivam MishraandGitHub bc768bf04f chore: verbosely log errors for leadsquare activity failure (#14407) 2026-05-11 10:58:23 +05:30
Shivam MishraandGitHub cc612e755b fix: SafeFetch dependency loading (#14408)
The SafeFetch spec suite was failing in CI with `NameError:
uninitialized constant SafeFetch::Fetcher` across every example that
exercised `SafeFetch.fetch`. From a product perspective, this made the
external-file fetch path look unreliable even though the failure
happened before any network validation, SSRF protection, content-type
checks, or tempfile handling could run.

The symptom pointed to a load-order issue rather than an actual fetch
behavior regression. `SafeFetch.fetch` referenced `Fetcher` from the
top-level module, but that nested class was not guaranteed to be loaded
in every test execution path before the method was invoked.

This change keeps the existing SafeFetch split between the public API
and the implementation classes, but makes the public entry point
responsible for loading the implementation it needs before use. That is
intentionally smaller than folding all of the fetcher logic into
`lib/safe_fetch.rb`; the separate files still keep the request option
parsing and streaming implementation readable, while the public API no
longer depends on Rails or the test runner having loaded nested
constants in a particular order.

The file also now uses a single `SafeFetch` module declaration. That
removes the awkward reopen pattern and makes the dependency boundary
easier to see: constants and errors are defined first, then the public
`fetch` method loads and delegates to the implementation classes.
2026-05-08 21:18:50 +05:30
Aakash BakhleandGitHub aa10d42237 chore: bump RubyLLM version [AI-152] (#14387)
# Pull Request Template

## Description

Bump RubyLLM version and update model registry

## Type of change

Version bump on package

## 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
2026-05-08 18:44:15 +05:30
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>
2026-05-08 16:40:15 +05:30
9c1d1c4070 feat(labels): remove label associations asynchronously on delete (#13531)
## Summary
- Remove label deletion dependency on association cleanup by deleting
immediately and enqueueing a background job.
- Add `Labels::RemoveAssociationsJob` to strip deleted label references
from tagged conversations and contacts.
- Keep this version simple by removing the label count/prompt
requirement requested.

## Implementation notes
- Enqueue job from `Api::V1::Accounts::LabelsController#destroy` with
label title + account id.
- Background work performed in `Labels::DestroyService`.

## References
- Linear issue:
https://linear.app/chatwoot/issue/CW-4765/cw-2857-enhancement-removing-labels-is-inconsistent
- GitHub issue: https://github.com/chatwoot/chatwoot/issues/1249

## Testing
- `bundle exec rspec
spec/controllers/api/v1/accounts/labels_controller_spec.rb
spec/services/labels/destroy_service_spec.rb
spec/jobs/labels/remove_associations_job_spec.rb
spec/services/labels/update_service_spec.rb`
- `bundle exec rubocop
app/controllers/api/v1/accounts/labels_controller.rb
app/jobs/labels/remove_associations_job.rb
spec/controllers/api/v1/accounts/labels_controller_spec.rb
spec/jobs/labels/remove_associations_job_spec.rb
spec/services/labels/destroy_service_spec.rb`

---------

Co-authored-by: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-05-08 13:40:36 +05:30
5c6ea78ce6 fix(security): Enforce admin authorization on custom attribute definitions API (#14392)
Custom attribute definitions can now only be created, edited, or deleted
by administrators, matching the existing settings UI restriction.
Previously, an agent could call the `custom_attribute_definitions` API
directly and modify account configuration that they couldn't reach
through the dashboard — a Broken Access Control vulnerability reported
externally.

Fixes
https://linear.app/chatwoot/issue/CW-7038/broken-access-control-on-custom-attribute-definitions-api

## How to test

1. Sign in as an agent.
2. Try to create a custom attribute by calling `POST
/api/v1/accounts/<id>/custom_attribute_definitions` directly (the
settings page is hidden for agents — use curl with the agent's
`api_access_token`).
3. Expect `401 Unauthorized` with body `{"error":"You are not authorized
to do this action"}`. Repeat for `PATCH` and `DELETE`.
4. Sign in as an administrator and confirm create/edit/delete still work
from Settings → Custom Attributes.
5. As either role, the listing endpoint (`GET
.../custom_attribute_definitions`) should still succeed — agents need
this to render attributes in conversation and contact panels.

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-05-08 11:42:23 +04:00
Tanmay Deep Sharma 0bc890302b Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-08 12:20:12 +05:30
Tanmay Deep Sharma 68973c3b7b Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-08 12:20:02 +05:30
Tanmay Deep Sharma 0875ae719e fix(voice): only reuse open conversations for Twilio outbound calls
Message#reopen_conversation bails on outgoing messages, so reusing a
resolved/snoozed/pending conversation hint left the call bubble inside
a thread that stays in its prior status. Restrict the existing-conversation
hint to open conversations and fall through to creating a fresh one
otherwise.
2026-05-08 12:19:52 +05:30
Tanmay Deep Sharma e7d9165945 fix(voice): only reuse open conversations for Twilio outbound calls
Message#reopen_conversation bails on outgoing messages, so reusing a
resolved/snoozed/pending conversation hint left the call bubble inside
a thread that stays in its prior status. Restrict the existing-conversation
hint to open conversations and fall through to creating a fresh one
otherwise.
2026-05-08 12:19:26 +05:30
Tanmay Deep SharmaandGitHub e6b8f48b3b fix: settle captain credits on subscription cancellation (#14089)
## Linear Ticket
-
https://linear.app/chatwoot/issue/CW-6875/captain-credits-3-bugs-in-stripe-subscription-lifecycle-cancel-ratchet

## Description

Fixes Captain credit settlement on subscription cancellation. Previously
`limits['captain_responses']` and `captain_responses_usage` were left in
their pre-cancellation state, which caused incorrect credit totals when
a customer re-subscribed. Cancellation now settles the monthly allotment
(preserving any remaining topup) and resets the usage counter.

## Type of change

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

## How Has This Been Tested?

1. Set up an account subscribed to a paid plan (e.g. Startups) so
`limits['captain_responses']` reflects the plan allotment.
2. Fire `customer.subscription.deleted` for that account's Stripe
customer. Confirm the limits.
3. Fire `customer.subscription.updated` re-subscribing to the paid plan.
Confirm the limits.
4. Repeat cancel → re-subscribe several times;


## 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
2026-05-08 12:18:53 +05:30
Sojan JoseandGitHub 10597863d7 feat(companies): add company creation flow (#14402) 2026-05-08 11:46:33 +05:30
53b2a517d7 fix: resolve SendReplyJob flaky specs (#14394)
`SendReplyJob` was caching reloadable service class objects in
`CHANNEL_SERVICES`. In test, a request spec can trigger Rails constant
reloading after `SendReplyJob` has already been loaded, leaving the job
with stale class objects while later specs stub the reloaded constants.
This resolves the channel service at perform time so the job follows the
current Rails constant table.

How to reproduce
Run the CircleCI shard that contains send_reply_job_spec, or the
minimized order-dependent reproduction:
```sh
bundle exec rspec --format progress spec/builders/v2/reports/label_summary_builder_spec.rb spec/controllers/api/v1/accounts/bulk_actions_controller_spec.rb spec/jobs/send_reply_job_spec.rb:32
```

What changed
- Store service class names in `SendReplyJob::CHANNEL_SERVICES` instead
of class objects.
- Resolve the service with constantize inside perform so reloads do not
leave stale cached classes.

Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
2026-05-07 22:47:09 +05:30
Tanmay Deep Sharma 2bb358a787 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 20:11:37 +07:00
Tanmay Deep Sharma 872f09f720 Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 20:11:31 +07:00
Tanmay Deep Sharma 9e56a024c5 fix(voice): honor Meta failure reasons on terminate
Call::TERMINAL_STATUSES include 'failed' but handle_terminate only ever
chose between 'completed' and 'no_answer'. A network drop or any other
failure reason landing after status=ACCEPTED was being recorded as
'completed' purely because the call was already in_progress, surfacing
failed conversations as successful in the dashboard. Map known failure
reasons to 'failed' before falling back to the answered/no_answer
heuristic.
2026-05-07 20:11:17 +07:00
Tanmay Deep Sharma 1032097978 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 20:01:03 +07:00
Tanmay Deep Sharma 66b0f51279 Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 20:00:57 +07:00
Tanmay Deep Sharma 7a7de71c39 Revert "test(telegram): fix business-chat send-attachments spec"
This reverts commit 461292fb2b.
2026-05-07 20:00:54 +07:00
Tanmay Deep Sharma 62e763ecc2 Revert "test(telegram): fix business-chat send-attachments spec"
This reverts commit 461292fb2b.
2026-05-07 20:00:41 +07:00
Tanmay Deep Sharma 8866071029 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 19:58:58 +07:00
Tanmay Deep Sharma 006d7afa44 Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 19:58:52 +07:00
Tanmay Deep Sharma 461292fb2b test(telegram): fix business-chat send-attachments spec
The let(:channel) and the service's message.inbox.channel were different
object instances, so the chat_id / business_connection_id stubs on the
test instance never applied to the service. Other examples masked it
because they only counted requests; the business-chat example matches
on body and tripped. Set the values via conversation.additional_attributes
(where the channel methods actually read from) instead of stubbing.
2026-05-07 19:58:41 +07:00
Tanmay Deep Sharma 3f65838155 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 19:57:42 +07:00
Tanmay Deep Sharma 12f29d0916 Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 19:57:36 +07:00
Tanmay Deep Sharma 3c33d912df fix(voice): persist late outbound SDP answers when ACCEPTED races connect
If status=ACCEPTED is processed before Meta's connect webhook, the call
is already in_progress when accept_outbound_call runs, and the previous
guard dropped the SDP answer — leaving the browser without the data it
needs to complete the WebRTC handshake. Gate on a stored sdp_answer
instead of in_progress so out-of-order delivery still persists and
broadcasts the answer (and connect retries stay idempotent).
2026-05-07 19:57:27 +07:00
Tanmay Deep SharmaandGitHub 9d889f691b Merge branch 'develop' into feat/whatsapp-call-incoming-pipeline 2026-05-07 19:38:02 +07:00
Tanmay Deep Sharma 801e1e8ec5 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 17:12:58 +07:00
Tanmay Deep Sharma 7795bc9f1d Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 17:12:51 +07:00
Tanmay Deep SharmaandClaude Opus 4.7 f6a88d7f12 test(voice): align WhatsApp call specs with feature gate and v22 calls path
- call_permission_reply specs: enable channel_voice and set
  provider_config['source'] = 'embedded_signup' so voice_enabled? returns
  true; without it the service short-circuits before any handling.
- whatsapp_cloud_service specs: stub /v22.0/<phone_id>/calls (and
  /messages) to match calls_phone_id_path, which uses
  WHATSAPP_API_VERSION (fallback v22.0). The OSS v13.0 path is locked
  for legacy /messages compatibility only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:12:39 +07:00
Tanmay Deep SharmaandClaude Opus 4.7 6b45b97d9c chore(voice): drop non-UI changes from feat/whatsapp-call-ui
Reset BE/migration/spec files to base so this PR diffs as a pure UI change.
The deleted migration and the BE-side comment/edits belong on the BE PR
chain (feat/whatsapp-call-incoming-pipeline) rather than the UI PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:06:46 +07:00
Tanmay Deep Sharma 25609d88b2 Merge branch 'feat/whatsapp-call-meta-bridge' into feat/whatsapp-call-ui 2026-05-07 17:00:43 +07:00
Tanmay Deep Sharma 5471e52335 Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge 2026-05-07 17:00:21 +07:00
Tanmay Deep SharmaandClaude Opus 4.7 f0119b3f2b fix(voice): preserve duration when agents hang up active WhatsApp calls
finalize_call wasn't writing duration_seconds or end_reason, and the Meta
terminate webhook that would have filled them in is suppressed by the
terminal-state idempotency guard in IncomingCallService#handle_terminate.
The result was completed-call bubbles with no duration. Compute the
duration from started_at at agent-hangup time and stamp end_reason
locally instead of waiting for a webhook that won't apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:00:11 +07:00