d154edf8ea
1709 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d3ce2a4cf9 | Merge branch 'main' into chore/merge-upstream-4.12.0 | ||
|
|
c6bfd1eed3
|
feat: schedule messages recurrence (#240)
* feat(scheduled-messages): add recurring scheduled messages Implements the recurring scheduled messages feature allowing agents to configure recurrence rules when scheduling messages, with automatic creation of subsequent scheduled messages after each send. Backend: - RecurringScheduledMessage model with JSONB recurrence_rule validation - RecurrenceCalculatorService for next occurrence date calculation - RecurrenceDescriptionService for human-readable rule descriptions - CreateNextOccurrenceService for auto-creating child ScheduledMessages - RecurringScheduledMessagesController with CRUD operations - RecurringScheduledMessagePolicy for authorization - Modified SendScheduledMessageJob to handle recurrence after send - Updated due_for_sending scope to exclude resolved conversations - ActionCable events for real-time updates - Activity message i18n (en + pt-BR) Frontend: - RecurrenceDropdown.vue with contextual shortcut options - RecurrenceCustomModal.vue for custom recurrence configuration - RecurringScheduledMessageItem.vue for sidebar display - Integration into ScheduledMessageModal.vue - Updated ScheduledMessages.vue with recurrence section and filtering - Vuex store module for recurring scheduled messages - API client for CRUD operations - WebSocket handlers in actionCable.js - recurrenceHelpers.js utility functions - i18n keys for en and pt-BR Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): fix creation and edit visibility - Fix API payload key mismatch (snake_case vs camelCase) in modal submit - Add status: 'active' to recurring creation payload - Fix strong params to permit recurrence_rule array fields (week_days) - Cast string values from strong params to integers for JSONB validation - Show RecurrenceDropdown when editing (remove isEditing gate) - Populate recurrenceRule from scheduled message's recurring parent - Include recurring_scheduled_message_id and recurrence_rule in scheduled message jbuilder response Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): fix dropdown toggle and locale tag errors - Use DropdownItem :click prop instead of @click to use the injected closeMenu from DropdownContainer context (default slot doesn't expose toggle) - Normalize locale from pt_BR to pt-BR for Intl.DateTimeFormat compatibility in RecurringScheduledMessageItem Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(recurring-messages): add separator line and expandable history - Add border separator between recurring messages section and pending/draft messages, matching the history section separator - Replace static 'N enviadas' counter with clickable toggle that expands to show individual sent/failed child messages with status badges and formatted timestamps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(recurring-messages): add click-to-navigate on sent children Make sent child messages in recurring message history clickable. Clicking navigates to the actual message in the conversation using the messageId query param, same pattern as ScheduledMessageItem. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(recurring-messages): allow editing recurring messages - Add edit button to RecurringScheduledMessageItem (active only) - Transform recurring message into scheduledMessage-compatible shape with recurring_scheduled_message_id set, so the modal reuses the existing update path - Handle edge case of removing recurrence from a recurring message (cancels series without trying to update a non-existent standalone) - Sent history is preserved by the backend update action Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): show recurrence field without date selection Remove v-if="scheduledDate" gate so the recurrence dropdown is always visible in the modal. Falls back to today's date for contextual shortcut labels when no date is selected yet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): ensure recurrence visible on edit and add pending_scheduled_message to API - Add pending_scheduled_message to recurring_scheduled_message jbuilder so REST API data matches WebSocket push_event_data - Add fallback in openEditRecurringModal to find pending child from scheduled_messages array when pending_scheduled_message is absent - Add same fallback in nextSendLabel computed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scheduled-messages): add sections for Drafts and Pending messages in the UI * refactor(scheduled-messages): merge RecurringScheduledMessageItem into ScheduledMessageItem Consolidate the recurring message card into the existing ScheduledMessageItem component instead of maintaining a separate component. The unified component detects recurring messages via recurrence_rule and conditionally shows: - Recurrence description header with repeat icon - Next send time label - Expandable sent/failed children history with click-to-navigate - Stop button (replaces delete) with confirmation modal - Active/completed/cancelled status badges Delete the now-unused RecurringScheduledMessageItem.vue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scheduled-messages): use blue badge for active, keep green for sent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scheduled-messages): remove draft and pending sections from UI and update recurrence title * fix(recurring-messages): use Teleport for recurrence dropdown Replace DropdownContainer with Teleport-based floating dropdown so options render outside the modal. Fixes: - Dropdown no longer enlarges the modal or causes scrolling - Dropdown closes before Custom modal opens (no overlap) - Auto-detects available space and opens above/below trigger Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): recalculate next date when recurrence rule changes When editing a recurring message and changing the recurrence rule, the pending occurrence date is now validated against the new rule. If the user-provided date doesn't match (e.g. Thursday removed from weekly days), the system computes the next valid date using RecurrenceCalculatorService instead of blindly using the old date. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scheduled-messages): resolve deprecated onClose and disabled type warnings - Replace :on-close prop with @close event on woot-modal components - Cast hasTemplate computed to boolean to fix disabled prop type check Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(scheduled-messages): replace remaining deprecated on-close props - ScheduledMessages.vue delete confirm modal - RecurrenceCustomModal.vue modal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): address review feedback on PR #240 - Fix v-if/v-else bug hiding message list behind resolved warning - Fix occurrences_sent incrementing on failed sends (skip_increment flag) - Fix compute_next_valid_date using .min instead of .max - Fix Vuex delete action to update state on cancel (not remove) - Use atomic update_counters for occurrences_sent increment - Add safe Date.iso8601 parsing with rescue in should_complete? - Add null: false to occurrences_sent migration column - Fix pt-BR accent: Recorrencia → Recorrências - Use I18n.with_locale(account.locale) for all activity messages - Fix N+1 in jbuilder partials (Ruby filtering + eager loading) - Add interval >= 1 validation to RecurrenceCustomModal isValid - Validate recurrence_rule presence when status is active - Add ISO8601 date format validation for end_date - Add unknown_agent i18n key for fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): wrap create/update in transactions, clean pending on deactivation - Wrap create and update flows in ActiveRecord transactions - Move attachment purge after save! to prevent data loss on validation failure - Destroy pending children when status transitions to non-active - Fixes critical bug where stopping recurrence could leave armed pending messages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): prevent monthly/yearly day-of-month drift Store the original intended day in recurrence_rule JSONB as month_day (for monthly) and year_day/year_month (for yearly). The calculator now uses these stored values instead of @last_date.day, preventing drift after short months cap the day (e.g., Jan 31 → Feb 28 → all subsequent months stuck on 28). Backend: - RecurrenceCalculatorService: use rule[:month_day] for monthly and rule[:year_day]/rule[:year_month] for yearly calculations - Controller: permit and cast the new integer keys Frontend: - recurrenceHelpers: yearly shortcuts include year_day/year_month - RecurrenceCustomModal: emit month_day for monthly day_of_month rules and year_day/year_month for yearly rules Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): i18n description services, align due_for_sending scope Backend: - RecurrenceDescriptionService: replace hardcoded English with I18n.t() calls; accept locale parameter and use I18n.with_locale - RecurringScheduledMessage model: pass account locale to description service - ScheduledMessage: align due_for_sending? instance method with scope by checking conversation status (open/pending) Frontend: - buildRecurrenceDescription: use t() i18n function instead of manual isPt locale branching - Add DESCRIPTION i18n keys to en/conversation.json and pt_BR/conversation.json - Update RecurrenceDropdown and ScheduledMessageItem callers to pass t i18n: - Add recurring_scheduled_messages.description.* keys to en.yml and pt_BR.yml Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): keep pending child when deactivating recurrence When editing a recurring message to disable recurrence while setting a future send date, the pending child message is now preserved instead of being destroyed. This allows a 'final send' without creating new recurrences (the send job already guards with recurring&.active?). Backend: - Add update_pending_on_deactivation: updates pending child's scheduled_at or creates a final pending occurrence - Replace destroy_all in update's non-active branch Frontend: - activeRecurringMessages now includes non-active recurring messages that still have a pending child (pending_scheduled_message) - Stop button hidden for already-cancelled recurring messages - inactiveRecurringMessages excludes messages with pending children Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): prevent removing recurrence from existing recurring message Once a scheduled message has recurrence, the 'Does not repeat' option is hidden from the RecurrenceDropdown when editing. This avoids edge cases where deactivating recurrence leaves the message in an ambiguous display state. - RecurrenceDropdown: add hideNoRepeat prop, filter NO_REPEAT from shortcuts - ScheduledMessageModal: pass hideNoRepeat when isEditingRecurring - Revert update_pending_on_deactivation (no longer reachable from UI) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): address CodeRabbit review feedback - ScheduledMessage#push_event_data: expose recurring_scheduled_message_id in ActionCable payloads so frontend correctly classifies children - RecurringScheduledMessagePolicy: add agent_bot? check for parity with ScheduledMessagePolicy - RecurrenceCalculatorService: guard against nil/empty week_days - Factory: bind inbox and account to conversation to prevent cross-account flakiness in specs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(recurring-messages): update schema to enforce non-null constraint on occurrences_sent --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com> |
||
|
|
a996b920e8
|
feat: group conversations (#228)
* feat: add group and conversation types to contacts and conversations, and implement conversation group membership model * chore: add factory and specs for conversation group member model * chore: add group type checks and associations for contacts and conversations * refactor: remove scopes from ConversationGroupMember model * refactor: remove scopes from ConversationGroupMember model specs * refactor: enhance conversation type migration with concurrent indexing * feat: add is_active index and scopes to ConversationGroupMember model * feat: implement GroupConversationHandler for managing group conversations * feat: add group_type attribute to contact creation * fix: update WHATSAPP_CHANNEL_REGEX to allow up to 20 digits to handle group jid * feat: handle group JID format in remote_jid method * chore: update group contact info when finding or creating group contact * chore: refactor and implement contact message handling and message creation logic for baileys single contact conversation * feat: implement group message handling and metadata fetching in WhatsApp service * chore: add spec for group type handling in contact creation for individual and group contacts * chore: add specs for test scopes in conversation group members * chore: update documentation for sender phone extraction in group conversation handler * chore: move GroupConversationHandler concern to correct dir * chore: implement specs for recipient_id handling to individual and group contacts * chore: add group message handling specs for incoming messages * chore: enhance group message handling to prevent race conditions * chore: add group_metadata method to with error handling * chore: add test for sending messages to group recipients in WhatsappBaileysService * chore: raise error for unsuccessful response in group_metadata method * chore: adds tests for group metadata retrieval and error handling * chore: refactor build_sender_contact_attributes to avoid double call methods * chore: update error handling for attachment download failure in message creation * chore: optimize update_contact_info method to use compact hash for updates * chore: simplify find_or_create_sender_contact method return values * chore: rename group and individual contact message handlers * chore: remove pointless comments from group contact message handler methods * chore: refine sender JID extraction logic to remove unnecessary checks * chore: remove phone number in spec for group contact attributes * chore: implement sync_group route * chore: implement get group_members route * fix: sync_group participants creation handling * chore: update contact avatar handling in group message processing * chore: move sync_group functionality for conversation model * feat: add sync_group action to ConversationsController and route * fix: set contact name to phone in group message processing * chore: refine group member retrieval logic in sync_group service and view * feat: implement group participants update handling * feat: implement group updates handling and localization for group activities * chore: add handling for group membership requests and icon changes * chore: add authorization for sync_group action in ContactsController * chore: add sync_group endpoint specs for contact management * chore: add authorization for sync_group action in ConversationsController * chore: add specs for sync_group endpoint in ConversationsController * chore: refactor index action in GroupMembersController for improved conversation filtering * chore: add request specs for group_members endpoint in ContactsController * chore: add specs for sync_group method in Conversation model * chore: add specs for sync_group method in Channel::Whatsapp model * chore: remove comments in find_or_create_group_conversation method * chore: add specs for Contacts::SyncGroupService to validate group contact behavior * chore: add specs for Whatsapp::BaileysHandlers::GroupsUpdate to validate group updates * chore: add specs for Whatsapp::BaileysHandlers::GroupParticipantsUpdate to handle group participant actions * chore: add fallback for identifier when contact has no phone_number in SendOnWhatsappService * chore: add specs for group membership request and icon change handling in MessagesUpsert * chore: add specs for sync_group method to handle group metadata and participant updates * chore: update sync_group method to retrieve group members and adjust JSON response * chore: update conversation query to filter by group type in GroupMembersController * chore: update conversation creation in group_members_controller_spec to specify conversation_type as group * chore: update find_or_create_group_conversation to include pending conversations * chore: refactor sync_group method and enhance specs for group conversation handling * feat: add GroupEventHelper module for managing group activities and contacts * chore: refactor group contact inbox and conversation creation methods in group handlers * chore: remove unnecessary check for blank participant contacts in sync_group_members method * feat: implement message receipt update handling for WhatsApp integration * chore: resolve rubocop rule for update_last_seen_at method * chore: update swagger with endpoints for syncing group information and listing group members * chore: integrate Contacts::SyncGroupService in group members controller, enhance error handling and update swagger * chore: include participant information in reaction and quoted message keys for send message in group conversations * chore: enhance whatsapp_baileys_service with participant handling for message keys * feat: add skill for writing RSpec tests in the project * fix: update recipient_id logic to directly use contact identifier for group contacts * chore: implement group stub message handling for membership requests and icon changes * fix: update whatsapp inbox source_id validation regex spec * chore: fix spec for contact syncing group * chore: remove readTimestamp handling and related tests for message read updates in group * Cayo oliveira/cu 86af01932/4 backend gerenciamento dos grupos (#221) * feat: add is_active index and scopes to ConversationGroupMember model * feat: implement GroupConversationHandler for managing group conversations * feat: add group_type attribute to contact creation * fix: update WHATSAPP_CHANNEL_REGEX to allow up to 20 digits to handle group jid * feat: handle group JID format in remote_jid method * chore: update group contact info when finding or creating group contact * chore: refactor and implement contact message handling and message creation logic for baileys single contact conversation * feat: implement group message handling and metadata fetching in WhatsApp service * chore: add spec for group type handling in contact creation for individual and group contacts * chore: add specs for test scopes in conversation group members * chore: update documentation for sender phone extraction in group conversation handler * chore: move GroupConversationHandler concern to correct dir * chore: implement specs for recipient_id handling to individual and group contacts * chore: add group message handling specs for incoming messages * chore: enhance group message handling to prevent race conditions * chore: add group_metadata method to with error handling * chore: add test for sending messages to group recipients in WhatsappBaileysService * chore: raise error for unsuccessful response in group_metadata method * chore: adds tests for group metadata retrieval and error handling * chore: refactor build_sender_contact_attributes to avoid double call methods * chore: update error handling for attachment download failure in message creation * chore: optimize update_contact_info method to use compact hash for updates * chore: simplify find_or_create_sender_contact method return values * chore: rename group and individual contact message handlers * chore: remove pointless comments from group contact message handler methods * chore: refine sender JID extraction logic to remove unnecessary checks * chore: remove phone number in spec for group contact attributes * chore: implement sync_group route * chore: implement get group_members route * fix: sync_group participants creation handling * chore: update contact avatar handling in group message processing * chore: move sync_group functionality for conversation model * feat: add sync_group action to ConversationsController and route * fix: set contact name to phone in group message processing * chore: refine group member retrieval logic in sync_group service and view * feat: implement group participants update handling * feat: implement group updates handling and localization for group activities * chore: add handling for group membership requests and icon changes * chore: add authorization for sync_group action in ContactsController * chore: add sync_group endpoint specs for contact management * chore: add authorization for sync_group action in ConversationsController * chore: add specs for sync_group endpoint in ConversationsController * chore: refactor index action in GroupMembersController for improved conversation filtering * chore: add request specs for group_members endpoint in ContactsController * chore: add specs for sync_group method in Conversation model * chore: add specs for sync_group method in Channel::Whatsapp model * chore: remove comments in find_or_create_group_conversation method * chore: add specs for Contacts::SyncGroupService to validate group contact behavior * chore: add specs for Whatsapp::BaileysHandlers::GroupsUpdate to validate group updates * chore: add specs for Whatsapp::BaileysHandlers::GroupParticipantsUpdate to handle group participant actions * chore: add fallback for identifier when contact has no phone_number in SendOnWhatsappService * chore: add specs for group membership request and icon change handling in MessagesUpsert * chore: add specs for sync_group method to handle group metadata and participant updates * chore: update sync_group method to retrieve group members and adjust JSON response * chore: update conversation query to filter by group type in GroupMembersController * chore: update conversation creation in group_members_controller_spec to specify conversation_type as group * chore: update find_or_create_group_conversation to include pending conversations * chore: refactor sync_group method and enhance specs for group conversation handling * feat: add GroupEventHelper module for managing group activities and contacts * chore: refactor group contact inbox and conversation creation methods in group handlers * chore: remove unnecessary check for blank participant contacts in sync_group_members method * chore: update swagger with endpoints for syncing group information and listing group members * chore: integrate Contacts::SyncGroupService in group members controller, enhance error handling and update swagger * fix: update recipient_id logic to directly use contact identifier for group contacts * chore: implement group stub message handling for membership requests and icon changes * fix: update whatsapp inbox source_id validation regex spec * chore: fix spec for contact syncing group * fix: optimize update_last_seen_at method to use update_columns * feat: Implement full frontend and backend support for group conversations - Added PRD for group conversations detailing frontend and backend requirements. - Created new Baileys TypeScript definitions for group-related functions. - Renamed `conversation_type` to `group_type` in the database and updated all references. - Implemented API serialization for `group_type` in conversation and contact responses. - Developed Vuex store module for managing group members. - Created UI components for group management, including group creation, member management, and metadata editing. - Integrated @mention functionality for group conversations and real-time updates via ActionCable. * feat: [US-001] - Rename conversation_type to group_type on conversations - Add migration to rename column and indexes - Update Conversation model enum to group_type - Update GroupConversationHandler concern - Update controllers (contacts, group_members) - Update all backend specs * chore: mark US-001 complete, update progress log, fix rubocop annotation * feat: [US-002] - Serialize group_type fields in API responses * feat: [US-003] - Add group_type filter to conversations index * feat: [US-004] - Add group_type to filter_keys.yml and FilterService * feat: US-005 - Backend group creation endpoint - Add POST /api/v1/accounts/:account_id/groups endpoint - Add Groups::CreateService to orchestrate Baileys group creation - Extend WhatsappBaileysService and BaseService with group management methods - Add routes for group members, metadata, invite, and join requests - Returns 403 when agent lacks inbox access, 422 when provider is unavailable * feat: US-006 - Backend add/remove members and role management endpoints - Add create/destroy/update actions to GroupMembersController - Delegate group management methods from Channel::Whatsapp to provider_service - create adds members via Baileys and creates ConversationGroupMember records - destroy removes a member by ID and sets is_active false - update promotes/demotes a member and updates their role * feat: US-007 - Backend group metadata update endpoint - Add PATCH /contacts/:id/group_metadata endpoint - Updates group subject via Baileys and syncs contact name - Updates group description via Baileys and syncs additional_attributes.description - Returns 422 when provider is unavailable * feat: US-008 - Backend invite link management endpoints - Add GET /contacts/:id/group_invite to retrieve current invite code/url - Add POST /contacts/:id/group_invite/revoke to revoke and get new invite code/url - Returns 422 when provider is unavailable * feat: US-009 - Backend join request management endpoints - Add GET /contacts/:id/group_join_requests to list pending join requests - Add POST /contacts/:id/group_join_requests/handle to approve/reject requests - Uses request_action param to avoid conflict with Rails reserved params[:action] - Returns 422 when provider is unavailable * feat: US-010 - Extend MentionService for contact mentions - Extract mention://contact/ID/Name URIs from message content - Store mentioned contact IDs in message.content_attributes[mentioned_contacts] - Existing user/team mention handling unchanged * feat: US-011 - Frontend API clients for all group endpoints - Add app/javascript/dashboard/api/groupMembers.js - Exports 11 methods: getGroupMembers, syncGroup, createGroup, updateGroupMetadata, addMembers, removeMembers, updateMemberRole, getInviteLink, revokeInviteLink, getPendingRequests, handleJoinRequest * feat: US-012 - Frontend Vuex store module groupMembers - Add groupMembers store module with fetch, sync, addMembers, removeMembers, updateMemberRole actions - Add SET_GROUP_MEMBERS and SET_GROUP_MEMBERS_UI_FLAG mutation types - Register module in store index * feat: US-013 - Frontend i18n keys for group features - Add groups.json with keys for group info, filter, creation modal, metadata editing, invite link, member management, join requests, and mention dropdown - Register groups.json in i18n locale en/index.js Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: US-014 - Frontend group_type filter in ConversationBasicFilter - Add chatGroupTypeFilter state, getter, mutation, and action to conversations store - Add getChatGroupTypeFilter getter - Add group_type param to ConversationApi.get() - Add Type filter section to ConversationBasicFilter with All/Individual/Group options - Persist group_type to UI settings under conversations_filter_by.group_type - Restore group_type from UI settings on page load - Include groupType in conversationFilters and pass as group_type param to API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: US-013 - Frontend — i18n keys for group features (en + pt-BR) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: [US-014] - Frontend — add group_type filter to ConversationBasicFilter All implementation was already in place from prior work: - ConversationBasicFilter.vue has Type section with All/Individual/Group options - ChatList.vue handles group_type in conversationFilters and restores from UI settings - Store has setChatGroupTypeFilter action, getChatGroupTypeFilter getter - API maps groupType → group_type query param Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-015 - Frontend — add group_type to advanced filter system - Add GROUP_TYPE to CONVERSATION_ATTRIBUTES in filterHelper.js - Add group_type filter definition in provider.js (components-next) - Add group_type to legacy advancedFilterItems/index.js and filterAttributeGroups - Add group_type to automationHelper conditionFilterMaps - Add group_type to customViewsHelper getValuesForFilter - Add group_type options to ChatList setParamsForEditFolderModal - Add GROUP_TYPE i18n key in en and pt_BR advancedFilters.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-016 - Frontend — GroupContactInfo basic display Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-017 - Frontend — GroupContactInfo sync button Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-018 - integrate GroupContactInfo in ContactPanel - Import GroupContactInfo component - Conditionally render GroupContactInfo when group_type === 'group' - Keep ContactInfo for individual conversations (no regression) - Dynamic sidebar title: 'Group' for groups, 'Contact' for individual - contact_notes and contact_attributes accordion sections unchanged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-019 - Frontend — group creation UI modal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-020 - Frontend — member management UI in GroupContactInfo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-021 - Frontend — group metadata editing UI Add inline editing for group name, description, and avatar in GroupContactInfo: - Click group name to edit inline, save on Enter/blur - Click description to edit inline with textarea, save on blur - Click avatar to open file picker for upload via contacts/update - Loading states on all fields during save - Success/error alerts for all operations - updateGroupMetadata action added to groupMembers store Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-022 - Frontend — invite link management UI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-023 - Frontend — join request management UI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-024 - Frontend — group message bubbles: sender name with color - Add sender name display above incoming message bubbles in group conversations - Deterministic color per sender using AVATAR_COLORS palette (name.length % 6) - Sender name hidden for consecutive messages from the same sender - Individual conversation bubbles unchanged - Pass groupWithPrevious and isGroupConversation props through MessageList → Message Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-025 - Frontend — group message bubbles: sender avatar Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Add Ralph Wiggum AI agent script for managing tool execution and progress tracking * feat: US-026 - Frontend — @mention dropdown for group conversations - Create TagGroupMembers.vue component for group member mention suggestions - Modify Editor.vue: add isGroupConversation/groupContactId props, render TagGroupMembers for group non-private context - Modify ReplyBox.vue: compute isGroupConversation and groupContactId from currentChat, pass to WootMessageEditor - @ mention plugin isAllowed now triggers for group conversations - In individual conversations or private notes, existing behavior unchanged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-027 - Frontend — mention rendering in group message bubbles Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-028 - Frontend ActionCable handler for contact.group_synced event - Backend: Include group_members data in contact.group_synced ActionCable payload - Frontend: Register contact.group_synced handler in ActionCableConnector - Frontend: Add setGroupMembers action to groupMembers store for direct commits - Tests: ActionCable handler spec + groupMembers store spec for new action Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: Update progress tracking for group conversations feature - mark tests as passing * fix: sender click case mismatch and filter dropdown spacing - Message.vue: use case-insensitive comparison for sender type check (Contact.push_event_data returns 'contact' but SENDER_TYPES.CONTACT is 'Contact') - ConversationBasicFilter.vue: replace last:mt-4 with flex-col gap-4 for consistent spacing between all three filter sections * fix: four bugs found during manual testing review - ContactPanel.vue: fix i18n key GROUP.INFO.SIDEBAR_TITLE → GROUP.SIDEBAR_TITLE - groupMembers.js API: fix syncGroup HTTP method GET → POST to match backend route - group_members_controller.rb: remove SyncGroupService from index action - filterHelpers.js: add missing group_type case to getValueFromConversation * docs: update progress with bug fix learnings * chore: implement group creation functionality in UI components * chore: add copy invite link functionality and update UI components * feat: US-041 - Backend — ensure group_type is set on existing contacts and conversations GroupConversationHandler#update_group_contact_info now sets group_type: :group on contacts that are incorrectly typed as individual. GroupConversationHandler#find_or_create_group_conversation updates existing conversation's group_type to :group if it is currently :individual. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: mark US-041 as complete Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-029 - i18n keys for You badge and group settings (en + pt-BR) All i18n keys already existed from prior iterations. Verified presence and updated PRD status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-030 - fix Baileys API route/method mismatches Fix on_whatsapp to dig('data') before accessing first element. Update spec stubs to match { data: [...] } response envelope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-031 - group_leave, group_setting_update, group_join_approval_mode methods All methods, delegates, and error handling already implemented. Verified specs pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-032 - persist group settings, invite code, and profile picture during sync Add try_update_group_avatar to fetch and attach group profile picture during sync_group. Update spec stubs for profile-picture-url endpoint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-033 - GroupSettingsController with leave, update, toggle Controller and routes already implemented. Verified rubocop passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-034 - remove inbox_contact_id from provider_config and jbuilder Already removed in prior iterations. Verified no references remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-035 - refactor TagGroupMembers to phone_number matching Already implemented. Verified excludePhoneNumber prop and filtering. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-036 - remove InboxContact.vue and settings tab Already removed in prior iterations. Verified no references remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-037 - add You badge in GroupContactInfo member list Already implemented with isOwnMember check and blue badge styling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-038 - fix inline edit for group name and description Already implemented with phone number normalization. Verified code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-039 - group settings section UI with toggles Already implemented. Settings toggles, API calls, and i18n verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-040 - leave group UI with confirmation and auto-resolve Already implemented. Leave button, confirmation, and API call verified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-050 - Create GroupMember model and migration New group_members table with group_contact_id, contact_id, role, is_active. Unique index on (group_contact_id, contact_id). Associations added to Contact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-064 - Helper method to find channel from group contact Add Contact#group_channel to decouple channel lookup from conversations. Update GroupMembersController and GroupSettingsController to use it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-052 - Update GroupConversationHandler to use GroupMember Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-057 - Update GroupMembersController to query GroupMember Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-058 - Update GroupSettingsController to not depend on conversations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-060 - Update group_members jbuilder views Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-059 - Remove group_members association from Conversation model Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: US-051 - Remove ConversationGroupMember model and table Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: mark all stories complete, update progress Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(groups): real-time group panel, avatar refresh on icon change, editable name/description - Add group_type to Conversations::EventDataPresenter#push_data and Contact#push_event_data so WebSocket events carry the field, enabling the frontend to switch to GroupContactInfo in real-time - Update handle_icon_change_stub to call try_update_group_avatar with force: true, purging the cached avatar and fetching the new one - Add force parameter to try_update_group_avatar to support re-fetching - Remove isInboxAdmin gate from name/description editing in GroupContactInfo so any user can click to edit (server validates) * fix(groups): rewrite SyncGroupService and simplify group metadata channel lookup - Rewrite SyncGroupService to use contact.group_channel directly instead of iterating conversations; find or create a conversation for sync - Simplify GroupMetadataController to use @contact.group_channel instead of querying conversations; remove local contact/attribute updates since the Baileys API handles persistence via webhook events * feat(groups): resolve conversations when inbox phone leaves or is removed - Add resolve_conversations_if_inbox_left to GroupParticipantsUpdate - Resolves all open/pending conversations when the inbox phone number is removed from or leaves a group * feat(groups): add paginated member list with infinite scroll - Backend: add pagination to GroupMembersController (page/per_page, default 10, ordered admins first); add meta with pagination info to the jbuilder response - Frontend: update groupMembers API to accept page param; add APPEND_GROUP_MEMBERS and SET_GROUP_MEMBERS_META mutations; implement paginated fetch with append and isFetchingMore flag in store * feat(groups): support Ctrl+Click on group message sender to open in new tab - navigateToGroupSender now accepts the event and checks for Ctrl/Cmd+Click to open the sender contact in a new tab * chore(i18n): update leave group confirmation text in en and pt_BR * fix(groups): handle phone format differences in You badge and admin detection - Extract phonesMatch helper that compares last 8 digits as fallback, handling Brazilian 9th digit discrepancy (e.g. +5587988465072 vs +558788465072) - Apply to both isOwnMember and isInboxAdmin computed properties * feat(groups): auto-sync members on mount, show existing members immediately - On mount, fetch existing DB members first so they display instantly - Then silently attempt a background sync to refresh from WhatsApp - If sync fails (e.g. WhatsApp disconnected), existing members remain displayed without any user-facing error * fix(groups): pin own member on first page and return inbox phone in meta The "You" badge was not appearing because the inbox's own member could be missing from the first paginated page in large groups (admins sorted first). Backend: - Pin the inbox's own member at the top of page 1 regardless of sort order - Return inbox_phone_number in the group members meta response - Use last-8-digit SQL fallback for Brazilian 9th-digit phone mismatches Frontend: - Use meta.inbox_phone_number for the inboxPhone computed - Fix declaration order to satisfy no-use-before-define lint rule * fix(groups): fix member action dropdown clipped by overflow container The promote/demote/remove dropdown menu was invisible because the member list had `overflow-y-auto max-h-80`, clipping any absolutely-positioned dropdown rendered inside it. - Remove overflow container from member list; let the sidebar scroll - Replace scroll-based infinite loading with IntersectionObserver on a sentinel element for cleaner pagination trigger - All member action logic (promote, demote, remove) was already wired; the dropdown is now visible on hover * fix(groups): keep member action dropdown visible when menu is open The opacity-0/group-hover classes on the action menu wrapper caused the DropdownMenu to become invisible as soon as the mouse left the row. Now the wrapper stays fully opaque while the menu is active. * fix(groups): move clickaway to member list wrapper to prevent instant close v-on-clickaway was bound to every member's action div individually. Clicking the three-dot button on one member fired closeMemberMenu from all other members' clickaway handlers, closing the menu instantly. Moved the directive to the single member list container instead. * feat: add WhatsApp mention conversion (incoming + outgoing) - New MentionConverterService for bidirectional mention handling - Incoming: converts @phone/mentionedJid to mention://contact/ URIs - Outgoing: extracts mention://contact/ URIs into WhatsApp mentions array - Supports @everyone/todos group mentions - WhatsApp renderer preserves mention display text instead of raw URI * fix: preserve mention display text in WhatsApp renderer mention:// URIs now render as display name text instead of the raw URL when converting markdown to WhatsApp format * feat: add @everyone mention option in group conversations - Everyone item shown at top of mention dropdown - Searchable by 'all', 'todos', 'everyone' keywords - i18n keys added for en and pt-BR * refactor: use Switch component for group settings toggles - Add disabled prop to Switch component - Replace custom toggle buttons in GroupContactInfo with Switch - Loading spinner shown alongside toggle while toggling * feat(whatsapp): add group sync status tracking (group_left, group_last_synced_at) * feat(whatsapp): hide group management UI when group_left is true * fix(groupMembers): include inbox phone number in group members state and sync event * feat(whatsapp): wrap group settings and leave in Accordion component * feat(groupMembers): handle group creator modification errors and update error messages * feat(groupMembers): enhance invite link functionality and clean up UI state after copying * refactor: remove sync_group functionality from conversations and related specs * feat(GroupContactInfo): implement scroll-based loading for group members * docs(swagger): add group API endpoints and remove conversation sync_group - Remove dead conversation/{id}/sync_group swagger entry and file - Update group_members.yml with pagination params, POST operation, and $ref schema - Add swagger for: group_members_member (PATCH/DELETE), group_metadata, group_invite, group_invite_revoke, group_join_requests, group_join_requests_handle, group_settings, group_settings_leave, group_settings_toggle_join_approval, groups/create - Add group_member schema definition - Add Groups tag to application tag_groups - Register all 12 group endpoints in paths/index.yml * feat(WhatsappBaileysService): enhance mention handling by replacing @DisplayName with @lid/@phone in outgoing text * feat(groups): move group sync to background job with 15-min cooldown - Create Contacts::SyncGroupJob that checks group_last_synced_at before calling SyncGroupService (skips if < 15 min) - Controller sync_group now enqueues the job and returns 202 Accepted - Delete sync_group.json.jbuilder (no longer needed) - Frontend sync action is fire-and-forget; results via ActionCable - Auto-trigger sync on conversation select and panel mount - Remove manual sync button from GroupContactInfo * fix: show group members list even after leaving group\n\nKeep the members section visible in read-only mode when\ngroup_left is true. Admin actions (add member, promote,\ndemote, remove) remain hidden. Pending Join Requests and\nAdvanced Options also stay hidden. * fix: disable group name/description/avatar editing when group_left is true * fix: remove @all mention and fix Enter key in group mention dropdown\n\n- Remove the @all/everyone special mention from TagGroupMembers since\n no channel provider currently supports mentioning all participants\n- Fix Enter key sending message instead of inserting selected mention\n in group conversations. The root cause was Editor.vue only emitting\n toggleUserMention=true for private notes (isPrivate), leaving\n ReplyBox unaware the group mention dropdown was open. Now also\n emits for isGroupConversation.\n- Add TagGroupMembers spec covering filtering, exclusion, and emission" * fix: address PR review feedback for group conversations - Fix nil safety in group_invites and group_join_requests controllers by replacing group_conversation.inbox.channel with @contact.group_channel - Add before_action guard in group_members_controller to validate contact is a group with identifier before create/update/destroy - Persist metadata locally in group_metadata_controller after provider calls (subject -> name, description -> additional_attributes) - Add server-side allow_group_creation? check in groups_controller - Add word boundary to mention regex to prevent matching inside words - Remove useless catch clauses in groupMembers store (try/finally only) - Default groupType to [] in customViewsHelper to prevent crash - Fix swagger parameter name mismatch (contact_id -> id) across all group endpoint YML files for consistency * fix: address PR #228 review feedback - strong params, guards, and safety fixes * fix: dispatch real-time events for Baileys group participant and metadata updates Both group-participants.update and groups.update handlers were updating backend data (GroupMember records, Contact attributes) but never dispatching ActionCable events, leaving the frontend member list and group metadata stale until manual sync. Changes: - Add dispatch_group_synced_event helper to GroupEventHelper concern - Dispatch CONTACT_GROUP_SYNCED after participant add/remove/promote/demote - Dispatch CONTACT_GROUP_SYNCED after group subject/description/settings changes - Frontend: onContactGroupSynced also dispatches contacts/updateContact to refresh group name, description and settings in the sidebar * fix: enhance member menu positioning and close behavior on sidebar scroll * feat: implement group property updates and enhance toast notifications * fix: update WhatsApp channel regex to allow optional hyphenated numbers * feat: implement group admin functionalities including leave, update properties, and toggle join approval * refactor: simplify group message handling by removing metadata fetching and syncing methods * chore: remove raph files * feat: update Portuguese translations for 'Read More' and 'Insert Read More' phrases * feat: enhance group admin functionalities with join approval and member add modes * feat: enhance group join request handling by adding removal of handled requests and updating pending join requests * feat: restrict message sending in announcement mode groups When a Baileys WhatsApp group has announcement mode enabled (announce=true), only admin members can send messages. This adds: - Frontend: disabled editor + banner for non-admin inbox in announcement groups - Backend: validation in SendOnWhatsappService to reject messages - Shared phone helper utility extracted from GroupContactInfo - i18n keys for en and pt_BR * feat: add group sync job enqueueing and improve avatar update handling * feat: add functionality to reset invite link and confirm member addition restrictions * feat: update group name extraction logic to handle nil values * feat: add inbox admin status handling and update related components * feat: remove group conversation resolution on leave action * feat: enhance group sender avatar interaction with tooltip and cursor pointer * feat: add force option to SyncGroupJob and update related specs * feat: enhance invite link handling and avatar update logic in group conversations * chore: remove prd.json * fix: change group sender name display from block to inline-block for better layout * feat: add group members loading check and fetch logic in MessagesView and ReplyBox components * feat: allow id and firstUnreadId props to accept both Number and String types feat: add vOnClickOutside import to Editor component feat: enhance Portuguese translations for integrations and settings fix: change button color in GroupContactInfo component from green to teal * feat: soft-disabled group conversations with activity tracking Groups start in a soft-disabled state by default when using Baileys. Chatwoot still creates group conversations but does not process every incoming message. Instead, Baileys accumulates group messages and sends periodic groups.activity webhook events to update last_activity_at. Backend: - Add WHATSAPP_GROUPS_ENABLED env var and groups_enabled? class method - Send groupsEnabled in Baileys connection setup - Create groups.activity handler to update conversation last_activity_at - Gate group message processing behind groups_enabled? check - Expose groups_enabled via inbox API Frontend: - Add warning banner with CTA to app.fazer.ai on disabled group conversations - Disable reply editor for non-private-note mode when groups disabled - Add i18n strings for en and pt_BR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use method for groups disabled banner action to avoid window scope issue Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: broadcast conversation update after groups.activity event update_columns bypasses ActiveRecord callbacks, so the ActionCable broadcast was never triggered when last_activity_at changed. Dispatch a CONVERSATION_UPDATED event explicitly so the sidebar updates in real-time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: show unread dot for soft-disabled group conversations with activity Since soft-disabled groups don't create messages, unread_count is always 0 and the standard badge won't show. Detect unread state by comparing last_activity_at > agent_last_seen_at for these groups and display a teal dot indicator instead of a count badge. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: clear unread dot when agent opens soft-disabled group conversation The update_last_seen endpoint skipped updating agent_last_seen_at when there were no unread messages (the throttle path). For soft-disabled groups that never create messages, this meant the dot indicator could never be cleared. Add an unseen_activity? check that bypasses the throttle when last_activity_at > agent_last_seen_at. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: group avatar upload to provider and fix icon change sync - Route avatar upload through GroupMetadataController to push to WhatsApp provider before saving locally - Add update_group_picture to baileys service and base service - Fix buildContactFormData crash when social_profiles is undefined - Make try_update_group_avatar public so GROUP_CHANGE_ICON stub handler can call it from outside the service class Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update specs for group conversations feature changes - Add groupsEnabled param to setup_channel_provider and handle_channel_error WebMock stubs - Add group-request-participants-list stub for sync_group tests - Add group_type to push_event_data expected hash - Set last_activity_at in throttle tests to prevent unseen_activity? bypass - Update sync_group delegation expectation to include soft: false - Stub groups_enabled? in group message handling tests - Update WhatsApp source_id regex expectation for group contact IDs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: add settings file for additional directories configuration * chore: undo unrelated changes * chore: remove planning doc, fix migration version, fix swagger param consistency - Remove planejamento-chat-interno.md (unrelated planning document) - Fix CreateGroupMembers migration API version from 7.0 to 7.1 - Fix swagger.json: normalize group endpoint paths from {contact_id} to {id} to match YAML sources and existing contact sub-resource conventions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CayoPOliveira <cayoproliveira@gmail.com> Co-authored-by: Cayo P. R. Oliveira <cayo@fazer.ai> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
521ce90e79
|
fix(whatsapp): consolidate fragmented phone/LID contacts to prevent lost messages (#241)
* fix(whatsapp): consolidate fragmented phone/LID contacts to prevent lost messages When a WhatsApp user had two separate contacts (one by phone, one by LID) with different contact_ids, the consolidation service did nothing. This caused update_contact_info to crash with a phone_number uniqueness violation, silently dropping incoming messages. Now properly merges the two contacts by treating the phone contact as canonical, moving conversations from the LID contact, and cleaning up duplicates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(whatsapp): address CodeRabbit review feedback - Destroy orphaned LID contact when it has no remaining contact_inboxes - Clarify spec comment to past-tense (no longer an active bug) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(whatsapp): route legacy-source contact_inbox into merge path When a phone contact_inbox has a non-standard source_id (legacy format) and a separate LID contact_inbox exists for a different contact, the consolidation now merges them instead of early-returning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: assert orphaned LID contact is destroyed in legacy-source spec Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
195713cbfe
|
chore: general improvements (#232) | ||
|
|
88c2688553
|
feat: implement event dispatching for incoming WhatsApp messages (#230)
* feat: implement event dispatching for incoming WhatsApp messages * refactor: remove unused dispatcher instance variable in event dispatching context |
||
|
|
56c5609ca0
|
feat: add per-inbox signature management (#226)
* feat: add per-inbox signature management - Introduced `InboxSignature` model to manage signatures specific to each inbox. - Added API endpoints for fetching, creating, updating, and deleting inbox signatures. - Updated UI components to support inbox-specific signatures, including overrides for signature position and separator. - Implemented a new composable `useInboxSignatures` for managing inbox signatures in the frontend. - Enhanced existing components to utilize inbox signatures, including the reply box and message signature settings. - Added tests for the new inbox signatures functionality, ensuring proper behavior of the API and model validations. - Updated translations for new UI elements related to inbox signatures. * feat: implement inbox access validation and add related tests * feat: enhance inbox signatures fetching and management logic |
||
|
|
21007bd20b
|
feat: add show author option to portal settings and update related views (#225)
* feat: add show author option to portal settings and update related views * fix: update portal reference to use local variable for show author condition * feat: enhance show_author handling in portal config and add related tests |
||
|
|
efe49f7da4
|
fix: captain liquid render file system (#13647) | ||
|
|
b98c614669
|
feat: add campaign context to Captain v2 prompts (#13644)
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> |
||
|
|
a44cb2c738
|
feat(inbox): Enable conversation continuity for social channels (#11079)
## Summary This PR enables and surfaces **conversation workflow** for social-style channels that should support either: - `Create new conversations` after resolve, or - `Reopen same conversation` ## What is included - Adds the conversation workflow setting UI as card-based options in Inbox Settings. - Expands channel availability in settings to include channels like: - Telegram - TikTok - Instagram - Line - WhatsApp - Facebook - Updates conversation selection behavior for Line incoming messages to respect the workflow (reopen vs create-new-after-resolved). - Updates TikTok conversation selection behavior to respect the workflow (reopen vs create-new-after-resolved). - Keeps email behavior unchanged (always starts a new thread). Fixes: https://github.com/chatwoot/chatwoot/issues/8426 ## Screenshot <img width="1400" height="900" alt="pr11079-workflow-sender-clear-tight" src="https://github.com/user-attachments/assets/9456821f-8d83-4924-8dcf-7503c811a7b1" /> ## How To Reproduce 1. Open `Settings -> Inboxes -> <Telegram/TikTok/Instagram/Line/Facebook/WhatsApp inbox> -> Settings`. 2. Verify **Conversation workflow** is visible with the two card options. 3. Toggle between both options and save. 4. For Line and TikTok, verify resolved-conversation behavior follows the selected workflow. ## Testing - `RAILS_ENV=test bundle exec rspec spec/builders/messages/instagram/message_builder_spec.rb:213 spec/builders/messages/instagram/message_builder_spec.rb:255 spec/builders/messages/instagram/messenger/message_builder_spec.rb:228 spec/builders/messages/instagram/messenger/message_builder_spec.rb:293 spec/services/tiktok/message_service_spec.rb` - Result: `16 examples, 0 failures` ## Follow-up - Migrate Website Live Chat workflow settings into this same conversation-workflow settings model. - Add Voice channel support for this workflow setting. --------- 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> |
||
|
|
55f6257313
|
chore(hub): clean up legacy Captain hub flow (#13640)
## Summary This PR cleans up legacy Hub/Captain integration paths and simplifies hub URL behavior coverage in tests. ## Changes - remove legacy Captain account endpoint flow from `ChatwootHub` - remove obsolete spec coverage tied to that retired flow - keep hub URL handling centralized in `base_url` with enterprise overlay precedence - simplify hub URL specs to assert explicit static URL expectations where applicable ## Reproduce Run the focused hub specs from this branch: - `bundle exec rspec spec/lib/chatwoot_hub_spec.rb spec/enterprise/lib/chatwoot_hub_spec.rb` ## Testing Validated locally with: - `bundle exec rspec spec/lib/chatwoot_hub_spec.rb spec/enterprise/lib/chatwoot_hub_spec.rb` - `bundle exec rubocop lib/chatwoot_hub.rb spec/lib/chatwoot_hub_spec.rb enterprise/lib/enterprise/chatwoot_hub.rb spec/enterprise/lib/chatwoot_hub_spec.rb` |
||
|
|
72354a4459
|
fix: normalize audio/opus content type to audio/ogg for WhatsApp attachments (#223) | ||
|
|
bce4e9b3a7
|
fix: clear source_id when retrying message to prevent skipping (#222)
* fix: clear source_id when retrying message to prevent skipping * fix: validate message status and type before retrying to ensure proper handling |
||
|
|
7cec4ebaae
|
feat: support multimodal user messages in captain v2 (#13581)
Extract and pass image attachments from the latest user message to the runner, excluding the last user message from the context for processing. Fixes #13588 # Pull Request Template ## Description Adds image support to captain v2 ## Type of change Please delete options that are not relevant. - [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 local testing <img width="754" height="1008" alt="image" src="https://github.com/user-attachments/assets/914cbc2c-9d30-42d0-87d4-9e5430845c87" /> langfuse also shows media correctly with the instrumentation code: <img width="1800" height="1260" alt="image" src="https://github.com/user-attachments/assets/ce0f5fa6-b1a5-42ec-a213-9a82b1751037" /> ## 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: Shivam Mishra <scm.mymail@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
6be95e79f8
|
feat(csat): Add WhatsApp utility template analyzer with rewrite guidance (#13575)
CSAT templates for WhatsApp are submitted as Utility, but Meta may reclassify them as Marketing based on content, which can significantly increase messaging costs. This PR introduces a Captain-powered CSAT template analyzer for WhatsApp/Twilio WhatsApp that predicts utility fit, explains likely risks, and suggests safer rewrites before submission. The flow is manual (button-triggered), Captain-gated, and applies rewrites only on explicit user action. It also updates UX copy to clearly set expectations: the system submits as Utility, Meta makes the final categorization decision. Fixes https://linear.app/chatwoot/issue/CW-6424/ai-powered-whatsapp-template-classifier-for-csat-submissions https://github.com/user-attachments/assets/8fd1d6db-2f91-447c-9771-3de271b16fd9 |
||
|
|
b220663785
|
fix: Skip notifications for private notes (#13617)
When agents or integrations create private notes on a conversation, every note was sending a notification to the assigned agent and all conversation participants. The fix ensures that private notes no longer trigger new message notifications. If someone explicitly mentions a teammate in a private note, that person will still get notified as expected. |
||
|
|
40da358dc2
|
feat: better errors for SMTP (#13401)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> |
||
|
|
957a1b17c9
|
perf: add default configs for assignment V2 (#13577)
## Description AutoAssignment::RateLimiter#within_limit? returned true early for inboxes without an AssignmentPolicy, bypassing fair distribution entirely and allowing unlimited conversation assignment. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## Script - Script to enable users with V2 assignment: https://www.notion.so/chatwoot/Script-to-migrate-account-to-assignment-V2-30ca5f274c9280f5b8ecfd15e28eeb9c?source=copy_link ## 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: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
9dd13b9a2b
|
fix: topup checkout flaky test (#13616) | ||
|
|
ce39e54308
|
feat: add audio transcoding support for WhatsApp Cloud API (#220)
* feat: add audio transcoding support for WhatsApp Cloud API - Introduced `Audio::TranscodeService` to handle audio transcoding to OGG/Opus format. - Updated `Messages::MessageBuilder` to transcode audio attachments based on `transcode_audio` parameter. - Enhanced `WhatsappCloudService` to normalize audio content types and send voice flag for recorded audio in OGG format. - Added utility functions for audio conversion in JavaScript. - Updated Dockerfile to include FFmpeg for audio processing. - Added tests for audio transcoding and WhatsApp Cloud service interactions. * feat: enhance audio handling with transcoding support and error management * feat: improve audio transcoding error handling and enhance audio recording features * feat: enhance audio transcoding process and error handling for better reliability * feat: update recorded audio handling to support boolean and array formats |
||
|
|
2441487a76
|
perf: skip conversation loading in /meta endpoint (#13564)
# Pull Request Template
## Summary
- Adds `perform_meta_only` method to `ConversationFinder` that runs
setup and counts without loading the paginated conversation list
- Updates `/api/v1/conversations/meta` to use `perform_meta_only`
instead of `perform`
## Problem
The `/meta` endpoint calls `ConversationFinder#perform` which:
1. Runs all filters and setup (`set_up`)
2. Computes 3 COUNT queries (`set_count_for_all_conversations`)
3. Filters by assignee type
4. **Builds the full paginated conversation list** with
`.includes(:taggings, :inbox, {assignee: {avatar_attachment: [:blob]}},
{contact: {avatar_attachment: [:blob]}}, :team, :contact_inbox)` +
sorting + pagination
The controller then **discards the conversations** and only uses the
counts:
```ruby
def meta
result = conversation_finder.perform
@conversations_count = result[:count] # conversations thrown away
end
```
## Type of change
- [x] Performance fix
## How Has This Been Tested?
- [ ] Verify /meta returns correct mine/unassigned/assigned/all counts
- [ ] Verify counts update when switching inbox, team, or status filters
- [ ] Verify conversation list still loads correctly (uses perform, not
affected)
- [ ] Monitor response time reduction for /meta in NewRelic after deploy
## 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: Pranav <pranav@chatwoot.com>
|
||
|
|
d8f4bb940e
|
feat: add resolve_conversation tool for Captain V2 scenarios (#13597)
# Pull Request Template ## Description Adds a new built-in tool that allows Captain scenarios to resolve conversations programmatically. This enables automated workflows like the misdirected contact deflector to close conversations after handling them, while still allowing human review via label filtering. ## 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. tested by mentioning it to be used in captain v2 scenario <img width="1180" height="828" alt="image" src="https://github.com/user-attachments/assets/e70baf96-0c70-407e-af2c-328500ac5434" /> ## 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: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com> |
||
|
|
db7e02b93b
|
feat: captain channel type langfuse metadata (#13574)
# Pull Request Template ## Description Adds channel type to Captain assistant traces in Langfuse ## 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. <img width="906" height="672" alt="image" src="https://github.com/user-attachments/assets/224cee95-56aa-4672-8f74-0c0052251db9" /> <img width="908" height="611" alt="image" src="https://github.com/user-attachments/assets/ddd8ef0d-47c1-450c-a09f-27e82a34d04d" /> ## 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: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
2ab117e8eb
|
feat(cloud-billing): cancel subscriptions at period end on deletion mark (#13580)
## How to reproduce In Chatwoot Cloud, mark an account for deletion from account settings while the account has an active Stripe subscription. Before this change, deletion marking did not explicitly mark subscriptions to stop renewing at period end. ## What changed This PR adds `Enterprise::Billing::CancelCloudSubscriptionsService` and calls it from the delete action path in `Enterprise::Api::V1::AccountsController`. The service lists only active Stripe subscriptions for the customer and sets `cancel_at_period_end: true` when needed. The account deletion schedule remains unchanged (existing static 7-day behavior), and Stripe deleted-event fallback behavior remains unchanged. ## How this was tested Added and updated specs: - `spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb` - `spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb` Executed: - `bundle exec rspec spec/enterprise/services/enterprise/billing/cancel_cloud_subscriptions_service_spec.rb` - `bundle exec rspec spec/enterprise/controllers/enterprise/api/v1/accounts_controller_spec.rb:363` |
||
|
|
3b8a38b153
|
feat: Implement existing template linking for CSAT surveys (#218)
* feat: Implement existing template linking for CSAT surveys - Added functionality to link existing CSAT templates for WhatsApp channels. - Introduced a new component for selecting existing templates. - Updated the dashboard settings page to support template mode switching between creating new and using existing templates. - Enhanced the CSAT template management service to handle linking existing templates and fetching available templates. - Updated API routes to include linking and fetching available templates. - Added tests for the new linking functionality and template availability checks. * feat: Enhance CSAT template handling and validation across services and components * feat: Refactor body variable extraction for CSAT templates and update related validations * feat: Add linked_at field to CSAT template responses and update related handling * feat: Add tests for ConversationDrop date formatting and CSAT template body variable handling |
||
|
|
f2635a69ed
|
fix: email delivery in Email::SendOnEmailService (#217)
* fix: email delivery in Email::SendOnEmailService * fix: handle nil response from email_reply in Email::SendOnEmailService |
||
|
|
70f7f5c486 | chore: rubocop | ||
|
|
248d6c23b3 | feat: add external_created_at to message creation and update specs for provider callback | ||
|
|
9a4c5058f3 | Merge branch 'main' into chore/merge-upstream-4.11.0 | ||
|
|
138840a23f
|
fix: typo in metadata key in captain v2 (#13558)
# Pull Request Template ## Description ## Type of change typo fix ## 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 - [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 |
||
|
|
39243b9e71
|
fix: duplicate message_created webhooks for WhatsApp messages (#13523)
Some customers using WhatsApp inboxes with account-level webhooks were
reporting receiving duplicate `message_created` webhook deliveries for
every incoming message. Upon inspection, here's what we found
- Both payloads are identical.
- No errors appear in the application logs
- Webhook URL is only configured in one place.
This meant, the system was sending the webhooks twice. For some context,
there's a know related issue... Meta's WhatsApp Business API can deliver
the same webhook notification multiple times for a single message. The
codebase already acknowledges this — there's a comment in
`IncomingMessageBaseService#process_messages` noting that "multiple
webhook events can be received against the same message due to
misconfigurations in the Meta business manager account." A deduplication
guard exists, but it doesn't actually work under concurrency.
### Rationale
The existing dedup was a three-step sequence: check Redis (`GET`), check
the database, then set a Redis flag (`SETEX`). Two Sidekiq workers
processing duplicate Meta webhooks simultaneously would both complete
the `GET` before either executed the `SETEX`, so both would proceed to
create a message. The `source_id` column has a non-unique index, so the
database wouldn't catch the duplicate either. Each message then
independently fires `after_create_commit`, dispatching two
`message_created` webhook events to the customer.
```
Worker A Worker B
│ │
▼ ▼
Redis GET key ──► nil Redis GET key ──► nil
│ │
│ ◄── both pass guard ──► │
│ │
▼ ▼
Redis SETEX key Redis SETEX key
│ │
▼ ▼
BEGIN transaction BEGIN transaction
INSERT message INSERT message
DELETE Redis key ◄─┐ │
COMMIT │ DELETE Redis key
│ COMMIT
│ │
└── key gone before ───┘
B's commit lands
▼ ▼
after_create_commit after_create_commit
dispatch MESSAGE_CREATED dispatch MESSAGE_CREATED
│ │
▼ ▼
WebhookJob ──► n8n WebhookJob ──► n8n
(duplicate!)
```
There was a second, subtler problem visible in the diagram: the Redis
key was cleared *inside* the database transaction, before the
transaction committed. This opened a window where neither the Redis
check nor the database check would see the in-flight message.
The fix collapses the check-and-set into a single `SET NX EX` call,
which is atomic in Redis. The key is no longer eagerly cleared — it
expires naturally after 24 hours. The database lookup
(`find_message_by_source_id`) remains as a fallback for messages that
were created before the lock expired.
```
Worker A Worker B
│ │
▼ ▼
Redis SET NX ──► OK Redis SET NX ──► nil
│ │
▼ ▼
proceeds to create returns early
message normally (lock already held)
```
### Implementation Notes
The lock logic is extracted into `Whatsapp::MessageDedupLock`, a small
class that wraps a single `Redis SET NX EX` call. This makes the
concurrency guarantee testable in isolation — the spec uses a
`CyclicBarrier` to race two threads against the same key and asserts
exactly one wins, without needing database writes,
`use_transactional_tests = false`, or monkey-patching.
Because the Redis lock now persists (instead of being cleared
mid-transaction), existing WhatsApp specs needed an `after` hook to
clean up `MESSAGE_SOURCE_KEY::*` keys between examples. Transactional
fixtures only roll back the database, not Redis.
|
||
|
|
3874383698
|
feat: insrument captain v2 (#13439)
# Pull Request Template ## Description Instruments captain v2 ## 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. Local testing: <img width="864" height="510" alt="image" src="https://github.com/user-attachments/assets/855ebce5-e8b8-4d22-b0bb-0d413769a6ab" /> ## 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: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
61eaa098ae
|
fix(messages): reduce audio transcription 400 retry noise (#13487)
## Summary This PR reduces duplicate failure noise for audio transcription jobs that fail with permanent HTTP 400 responses, and fixes a file-format edge case causing intermittent 400s. Sentry issue: [CHATWOOT-99E / 6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/) ## Confirmed root cause For some attachments, the stored filename had no extension (example: `speech`, content type `audio/mpeg`). When the temporary transcription upload file was created without an extension, OpenAI returned: `Unrecognized file format` (HTTP 400). ## Scope of changes 1. `Messages::AudioTranscriptionJob` - Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on permanent request errors. - Adds explicit Rails warning logs for discarded jobs with attachment/job/status context. 2. `Messages::AudioTranscriptionService` - Keeps guaranteed temp file cleanup via `ensure`. - Ensures temp upload files include an extension when the original filename has none, derived from blob `content_type`. - This addresses intermittent failures like extensionless `audio/mpeg` files. ## Reproduction Enable audio transcription for an account and process an audio attachment whose stored filename has no extension (for example `speech`) but valid audio content type (`audio/mpeg`). Before this fix, OpenAI transcription could return HTTP 400 `Unrecognized file format` for that attachment while similar attachments with extensions succeeded. ## Testing Ran: `bundle exec rubocop enterprise/app/jobs/messages/audio_transcription_job.rb enterprise/app/services/messages/audio_transcription_service.rb` Result: both modified files pass lint with no offenses. |
||
|
|
f4538ae2c5
|
fix: Enforce team boundaries to prevent cross-team assignments (#13353)
## Description Fixes a critical bug where conversations assigned to a team could be auto-assigned to agents outside that team when all team members were at capacity. ## 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 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core assignment selection for both legacy and v2 flows; misconfiguration of `allow_auto_assign` or team membership could cause conversations to remain unassigned. > > **Overview** > Prevents auto-assignment from crossing team boundaries by filtering eligible agents to the conversation’s `team` members (and requiring `team.allow_auto_assign`) in both the legacy `AutoAssignmentHandler` path and the v2 `AutoAssignment::AssignmentService` (including the Enterprise override). > > Adds test coverage to ensure team-scoped conversations only assign to team members, and are skipped when team auto-assign is disabled or no team members are available; also updates the conversations controller spec setup to include team membership. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 67ed2bda0cd8ffd56c7e0253b86369dead2e6155. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> |
||
|
|
2c2f0547f7
|
fix: Captain not responding to campaign conversations (#13489)
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> |
||
|
|
d272a64ff7
|
fix(mailbox): handle malformed sender address headers (#13486)
## How to reproduce When an inbound email has malformed sender headers (for example `From: McDonald <info@example.com` without a closing `>`), mailbox processing can raise `Mail::Field::IncompleteParseError` while resolving sender data in `MailPresenter`. ## What changed This PR hardens sender parsing in `MailPresenter` with a small, readable implementation: - Added/used a safe parser (`parse_mail_address`) that rescues `Mail::Field::ParseError` and `Mail::Field::IncompleteParseError`. - `sender_name` now uses the same safe parser path. - `original_sender` now resolves candidates in order via a compact `filter_map` flow: - `Reply-To` - `X-Original-Sender` - `From` - All three candidates are parsed as email addresses before use (including `X-Original-Sender`), and invalid values are ignored. - `notification_email_from_chatwoot?` now compares sender addresses case-insensitively (`casecmp?`) to avoid case-only mismatches. ## Test coverage Added focused presenter specs for: - malformed `From` header returns nil sender values and does not classify as notification sender - malformed `Reply-To` falls back to valid `From` - valid `X-Original-Sender` is used when present - invalid `X-Original-Sender` falls back to valid `From` - mixed-case sender address still matches configured `MAILER_SENDER_EMAIL` ## How this was tested Ran: - `bundle exec rspec spec/presenters/mail_presenter_spec.rb` - `bundle exec rubocop app/presenters/mail_presenter.rb spec/presenters/mail_presenter_spec.rb` Sentry issue: [CHATWOOT-B9Y](https://chatwoot-p3.sentry.io/issues/7005483640/) |
||
|
|
7b512bd00e
|
fix: V2 Assignment service enhancements (#13036)
## Linear Ticket: https://linear.app/chatwoot/issue/CW-6081/review-feedback ## Description Assignment V2 Service Enhancements - Enable Assignment V2 on plan upgrade - Fix UI issue with fair distribution policy display - Add advanced assignment feature flag and enhance Assignment V2 capabilities ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? This has been tested using the 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 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes auto-assignment execution paths, rate limiting defaults, and feature-flag gating (including premium plan behavior), which could affect which conversations get assigned and when. UI rewires inbox settings and policy flows, so regressions are possible around navigation/linking and feature visibility. > > **Overview** > **Adds a new premium `advanced_assignment` feature flag** and uses it to gate capacity/balanced assignment features in the UI (sidebar entry, settings routes, assignment-policy landing cards) and backend (Enterprise balanced selector + capacity filtering). `advanced_assignment` is marked premium, included in Business plan entitlements, and auto-synced in Enterprise accounts when `assignment_v2` is toggled. > > **Improves Assignment V2 policy UX** by adding an inbox-level “Conversation Assignment” section (behind `assignment_v2`) that can link/unlink an assignment policy, navigate to create/edit policy flows with `inboxId` query context, and show an inbox-link prompt after creating a policy. The policy form now defaults to enabled, disables the `balanced` option with a premium badge/message when unavailable, and inbox lists support click-to-navigate. > > **Tightens/adjusts auto-assignment behavior**: bulk assignment now requires `inbox.enable_auto_assignment?`, conversation ordering uses the attached `assignment_policy` priority, and rate limiting uses `assignment_policy` config with an infinite default limit while still tracking assignments. Tests and i18n strings are updated accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 23bc03bf75ee4376071e4d7fc7cd564c601d33d7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Pranav <pranav@chatwoot.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> |
||
|
|
8f95fafff4
|
feat: Add a setting to keep conversations pending on bot failures (#13512)
Adds an account-level setting `keep_pending_on_bot_failure` to control whether conversations should move from pending to open when agent bot webhooks fail. Some users experience occasional message drops and don't want conversations to automatically reopen due to transient bot failures. This setting gives accounts control over that behavior. This is a temporary setting which will be removed in future once a proper fix for it is done, so it is not added in the UI. |
||
|
|
6e397c7571
|
fix: default model for captain assistant (#13496) | ||
|
|
67112647e8
|
fix: escape special characters in Linear GraphQL queries (#13490)
Creating a Linear issue from Chatwoot fails with a GraphQL parse error
when the title, description, or search term contains double quotes. For
example, a description like `the sender is "Bot"` produces this broken
query:
```graphql
issueCreate(input: { description: "the sender is "Bot"" })
```
Linear's API rejects this with `Syntax Error: Expected ":", found
String`. This affects issue creation, issue linking, and issue search —
any flow where user-provided text is interpolated into a GraphQL query.
The `graphql_value` helper was only escaping newlines (`\n`) but not
quotes, backslashes, or other characters that are meaningful inside a
GraphQL string literal. On top of that, `issue_link` and `search_issue`
bypassed `graphql_value` entirely, using raw string interpolation
instead.
The fix replaces the manual `gsub` escaping with Ruby's `to_json`, which
produces a properly escaped, double-quoted string that handles all
special characters. This is a minimal, well-understood substitution —
`to_json` on a Ruby string returns a valid JSON string literal, which is
also a valid GraphQL string literal since GraphQL uses the same escaping
rules. The `issue_link` mutation and `search_issue` query are updated to
route their parameters through `graphql_value` instead of raw
interpolation.
The `team_entities_query` and `linked_issues` methods in `queries.rb`
also use raw interpolation, but their inputs are system-generated IDs
and URLs rather than user-provided text, so they're left as-is to keep
this change focused.
|
||
|
|
f83415f299
|
fix(account-deletion): normalize deleted email suffix and handle collisions safely (#13472)
## Summary This PR fixes account deletion failures by changing how orphaned user emails are rewritten during `AccountDeletionService`. Ref: https://chatwoot-p3.sentry.io/issues/6715254765/events/e228a5d045ad47348d6c32448bc33b7a/ ## Changes (develop -> this branch) - Updated soft-delete email rewrite from: - `#{original_email}-deleted.com` - To deterministic value: - `#{user.id}@chatwoot-deleted.invalid` - Added reserved non-deliverable domain constant: - `@chatwoot-deleted.invalid` - Replaced the "other accounts" check from `count.zero?` to `exists?` (same behavior, cheaper query). - Updated service spec expectation to match deterministic email value and assert it differs from original email. ## Files changed - `app/services/account_deletion_service.rb` - `spec/services/account_deletion_service_spec.rb` ## How to verify - Run: `bundle exec rspec spec/services/account_deletion_service_spec.rb` - Run: `bundle exec rubocop app/services/account_deletion_service.rb spec/services/account_deletion_service_spec.rb` |
||
|
|
6a7cbcf5ba
|
fix: Fixes reply-to in WhatsApp Cloud API (#13467)
This change https://github.com/chatwoot/chatwoot/pull/13371 broke the functionality. When a user replies to a WhatsApp message, the reply context wasn't being properly stored in Chatwoot due to #13371 WhatsApp sends reply messages with a `context` field containing the original message ID: ```json { "messages": [{ "context": { "from": "phone_number", "id": "wamid.ORIGINAL_MESSAGE_ID" }, "from": "phone_number", "id": "wamid.REPLY_MESSAGE_ID", "text": { "body": "This is a reply" } }] } ``` However, the in_reply_to_external_id was being overridden when building the message because content_attributes was explicitly set to either { external_echo: true } or {}, which discarded the reply-to information. |
||
|
|
3c47ea3d43
|
fix: prevent deletion of scheduled messages that have been sent or failed (#212)
* fix: prevent deletion of scheduled messages that have been sent or failed * fix: update error message for deletion of processed scheduled messages |
||
|
|
c24eba9180
|
fix: resolve race condition on slow networks by re-checking message source_id after acquiring contact lock (#210) | ||
|
|
8eaea7c72e
|
feat: Add standalone outgoing messages count API endpoint (#13419)
This PR adds a new standalone `GET /api/v2/accounts/:id/reports/outgoing_messages_count` endpoint that returns outgoing message counts grouped by agent, team, inbox, or label. |
||
|
|
9eb3ee44a8 |
Revert "chore: Upgrade Rails to 7.2.2 and update Gemfile dependencies (#11037)"
This reverts commit
|
||
|
|
ef6ba8aabd
|
chore: Upgrade Rails to 7.2.2 and update Gemfile dependencies (#11037)
Upgrade rails to 7.2.2 so that we can proceed with the rails 8 upgrade afterwards # Changelog - `.circleci/config.yml` — align CI DB setup with GitHub Actions (`db:create` + `db:schema:load`) to avoid trigger-dependent prep steps. - `.rubocop.yml` — add `rubocop-rspec_rails` and disable new cops that don't match existing spec style. - `AGENTS.md` — document that specs should run without `.env` (rename temporarily when present). - `Gemfile` — upgrade to Rails 7.2, switch Azure storage gem, pin `commonmarker`, bump `sidekiq-cron`, add `rubocop-rspec_rails`, and relax some gem pins. - `Gemfile.lock` — dependency lockfile updates from the Rails 7.2 and gem changes. - `app/controllers/api/v1/accounts/integrations/linear_controller.rb` — stringify params before passing to the Linear service to keep key types stable. - `app/controllers/super_admin/instance_statuses_controller.rb` — use `MigrationContext` API for migration status in Rails 7.2. - `app/models/installation_config.rb` — add commentary on YAML serialization and future JSONB migration (no behavior change). - `app/models/integrations/hook.rb` — ensure hook type is set on create only and guard against missing app. - `app/models/user.rb` — update enum syntax for Rails 7.2 deprecation, serialize OTP backup codes with JSON, and use Ruby `alias`. - `app/services/crm/leadsquared/setup_service.rb` — stringify hook settings keys before merge to keep JSON shape consistent. - `app/services/macros/execution_service.rb` — remove macro-specific assignee activity workaround; rely on standard assignment handlers. - `config/application.rb` — load Rails 7.2 defaults. - `config/storage.yml` — update Azure Active Storage service name to `AzureBlob`. - `db/migrate/20230515051424_update_article_image_keys.rb` — use credentials `secret_key_base` with fallback to legacy secrets. - `docker/Dockerfile` — add `yaml-dev` and `pkgconf` packages for native extensions (Ruby 3.4 / psych). - `lib/seeders/reports/message_creator.rb` — add parentheses for clarity in range calculation. - `package.json` — pin Vite version and bump `vite-plugin-ruby`. - `pnpm-lock.yaml` — lockfile changes from JS dependency updates. - `spec/builders/v2/report_builder_spec.rb` — disable transactional fixtures; truncate tables per example via Rails `truncate_tables` so after_commit callbacks run with clean isolation; keep builder spec metadata minimal. - `spec/builders/v2/reports/label_summary_builder_spec.rb` — disable transactional fixtures + truncate tables via Rails `truncate_tables`; revert to real `resolved!`/`open!`/`resolved!` flow for multiple resolution events; align date range to `Time.zone` to avoid offset gaps; keep builder spec metadata minimal. - `spec/controllers/api/v1/accounts/macros_controller_spec.rb` — assert `assignee_id` instead of activity message to avoid transaction-timing flakes. - `spec/services/telegram/incoming_message_service_spec.rb` — reference the contact tied to the created conversation instead of `Contact.all.first` to avoid order-dependent failures when other specs leave data behind. - `spec/mailers/administrator_notifications/shared/smtp_config_shared.rb` — use `with_modified_env` instead of stubbing mailer internals. - `spec/services/account/sign_up_email_validation_service_spec.rb` — compare error `class.name` for parallel/reload-safe assertions. |
||
|
|
614b887110
|
feat: enhance scheduled message + improved automations (#208)
* feat: enhance scheduled message delay handling * fix: update scheduled message delay validation * feat: message templates + liquid variables * feat: enhance validation and handling for scheduled messages and attachments * test: add validation for scheduled message creation and enhance Liquid variable processing * chore: define constants for scheduled message delay limits and update validations * chore: correct scheduled message delay constants for consistency * feat: enhance scheduled message validation and processing for templates |
||
|
|
c884cdefde
|
feat: add per-account daily rate limit for outbound emails (#13411)
Introduce a daily cap on non-channel outbound emails to prevent abuse. Fixes https://linear.app/chatwoot/issue/CW-6418/ses-incident-jan-28 ## Type of change - [x] New feature (non-breaking change which adds functionality) - [x] Breaking change (fix or feature that would cause existing functionality not to work as expected) ## Summary - Adds a Redis-based daily counter to rate limit outbound emails per account, preventing email abuse - Covers continuity emails (WebWidget/API), conversation transcripts, and agent notifications - Email channel replies are excluded (paid feature, not abusable) - Adds account suspension check in `ConversationReplyMailer` to block already-queued emails for suspended accounts ## Limit Resolution Hierarchy 1. Per-account override (`account.limits['emails']`) — SuperAdmin configurable 2. Enterprise plan-based (`ACCOUNT_EMAILS_PLAN_LIMITS` InstallationConfig) 3. Global default (`ACCOUNT_EMAILS_LIMIT` InstallationConfig, default: 100) 4. Fallback (`ChatwootApp.max_limit` — effectively unlimited) ## Enforcement Points | Path | Where | Behavior | |------|-------|----------| | WebWidget/API continuity | `SendEmailNotificationService#should_send_email_notification?` | Silently skipped | | Widget transcript | `Widget::ConversationsController#transcript` | Returns 429 | | API transcript | `ConversationsController#transcript` | Returns 429 | | Agent notifications | `Notification::EmailNotificationService#perform` | Silently skipped | | Email channel replies | Not rate limited | Paid feature | | Suspended accounts | `ConversationReplyMailer` | Blocked at mailer level | |