Merge branch 'develop' into feat/rollup/1-model-write-path

This commit is contained in:
Shivam Mishra
2026-03-16 11:35:49 +05:30
committed by GitHub
16 changed files with 151 additions and 48 deletions
@@ -19,7 +19,7 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
contact = @contact
end
@contact_inbox.update(hmac_verified: true) if should_verify_hmac? && valid_hmac?
@contact_inbox.update(hmac_verified: true) if should_verify_hmac?
identify_contact(contact)
end
@@ -26,6 +26,7 @@ const onPortalCreate = ({ slug: portalSlug, locale }) => {
<EmptyStateLayout
:title="$t('HELP_CENTER.TITLE')"
:subtitle="$t('HELP_CENTER.NEW_PAGE.DESCRIPTION')"
class="bg-n-surface-1"
>
<template #empty-state-item>
<div class="grid grid-cols-2 gap-4 p-px">
+3 -2
View File
@@ -41,7 +41,7 @@ class Account < ApplicationRecord
'audio_transcriptions': { 'type': %w[boolean null] },
'auto_resolve_label': { 'type': %w[string null] },
'keep_pending_on_bot_failure': { 'type': %w[boolean null] },
'captain_disable_auto_resolve': { 'type': %w[boolean null] },
'captain_auto_resolve_mode': { 'type': %w[string null], 'enum': ['evaluated', 'legacy', 'disabled', nil] },
'conversation_required_attributes': {
'type': %w[array null],
'items': { 'type': 'string' }
@@ -93,7 +93,8 @@ class Account < ApplicationRecord
store_accessor :settings, :captain_models, :captain_features
store_accessor :settings, :reporting_timezone
store_accessor :settings, :keep_pending_on_bot_failure
store_accessor :settings, :captain_disable_auto_resolve
store_accessor :settings, :captain_auto_resolve_mode
include AccountCaptainAutoResolve
has_many :account_users, dependent: :destroy_async
has_many :agent_bot_inboxes, dependent: :destroy_async
@@ -0,0 +1,21 @@
module AccountCaptainAutoResolve
extend ActiveSupport::Concern
VALID_CAPTAIN_AUTO_RESOLVE_MODES = %w[evaluated legacy disabled].freeze
included do
VALID_CAPTAIN_AUTO_RESOLVE_MODES.each do |mode|
define_method("captain_auto_resolve_#{mode}?") do
captain_auto_resolve_mode == mode
end
end
end
def captain_auto_resolve_mode
mode = settings&.[]('captain_auto_resolve_mode')
return mode if VALID_CAPTAIN_AUTO_RESOLVE_MODES.include?(mode)
return 'disabled' if settings&.[]('captain_disable_auto_resolve') == true
feature_enabled?('captain_tasks') ? 'evaluated' : 'legacy'
end
end
+2 -2
View File
@@ -104,11 +104,11 @@ wistia:
</div>
bunny:
regex: 'https?://iframe\.mediadelivery\.net/play/(?<library_id>\d+)/(?<video_id>[^&/?]+)'
regex: 'https?://(?:iframe|player)\.mediadelivery\.net/(?:play|embed)/(?<library_id>\d+)/(?<video_id>[^&/?]+)'
template: |
<div style="position: relative; padding-top: 56.25%;">
<iframe
src="https://iframe.mediadelivery.net/embed/%{library_id}/%{video_id}?autoplay=false&loop=false&muted=false&preload=true&responsive=true"
src="https://player.mediadelivery.net/embed/%{library_id}/%{video_id}?autoplay=false&loop=false&muted=false&preload=true&responsive=true"
title="Bunny video player"
loading="lazy"
style="border: 0; position: absolute; top: 0; height: 100%; width: 100%;"
@@ -5,9 +5,9 @@ class Captain::InboxPendingConversationsResolutionJob < ApplicationJob
queue_as :low
def perform(inbox)
return if inbox.account.captain_disable_auto_resolve
return if inbox.account.captain_auto_resolve_disabled?
if inbox.account.feature_enabled?('captain_tasks')
if evaluate_conversation_completion?(inbox.account)
perform_with_evaluation(inbox)
else
perform_time_based(inbox)
@@ -18,6 +18,10 @@ class Captain::InboxPendingConversationsResolutionJob < ApplicationJob
private
def evaluate_conversation_completion?(account)
account.feature_enabled?('captain_tasks') && account.captain_auto_resolve_evaluated?
end
def perform_time_based(inbox)
Current.executed_by = inbox.captain_assistant
@@ -12,7 +12,7 @@ module Enterprise::Account::ConversationsResolutionSchedulerJob
inbox = captain_inbox.inbox
next if inbox.email?
next if inbox.account.captain_disable_auto_resolve
next if inbox.account.captain_auto_resolve_disabled?
Captain::InboxPendingConversationsResolutionJob.perform_later(
inbox
@@ -6,7 +6,7 @@ class Captain::Tools::ResolveConversationTool < Captain::Tools::BasePublicTool
conversation = find_conversation(tool_context.state)
return 'Conversation not found' unless conversation
return "Conversation ##{conversation.display_id} is already resolved" if conversation.resolved?
return 'Auto-resolve is disabled for this account' if conversation.account.captain_disable_auto_resolve
return 'Auto-resolve is disabled for this account' if conversation.account.captain_auto_resolve_disabled?
log_tool_usage('resolve_conversation', { conversation_id: conversation.id, reason: reason })
+1 -1
View File
@@ -79,7 +79,7 @@
"json-logic-js": "^2.0.5",
"lettersanitizer": "^1.0.6",
"libphonenumber-js": "^1.11.9",
"markdown-it": "^13.0.2",
"markdown-it": "^14.1.1",
"markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0",
"mitt": "^3.0.1",
+6 -31
View File
@@ -160,8 +160,8 @@ importers:
specifier: ^1.11.9
version: 1.11.9
markdown-it:
specifier: ^13.0.2
version: 13.0.2
specifier: ^14.1.1
version: 14.1.1
markdown-it-link-attributes:
specifier: ^4.0.1
version: 4.0.1
@@ -2206,10 +2206,6 @@ packages:
entities@2.1.0:
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
entities@3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
@@ -3087,9 +3083,6 @@ packages:
linkify-it@3.0.3:
resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==}
linkify-it@4.0.1:
resolution: {integrity: sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
@@ -3210,12 +3203,8 @@ packages:
resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
hasBin: true
markdown-it@13.0.2:
resolution: {integrity: sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==}
hasBin: true
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
markdown-it@14.1.1:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
math-intrinsics@1.1.0:
@@ -6841,8 +6830,6 @@ snapshots:
entities@2.1.0: {}
entities@3.0.1: {}
entities@4.5.0: {}
entities@6.0.1: {}
@@ -7950,10 +7937,6 @@ snapshots:
dependencies:
uc.micro: 1.0.6
linkify-it@4.0.1:
dependencies:
uc.micro: 1.0.6
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
@@ -8091,15 +8074,7 @@ snapshots:
mdurl: 1.0.1
uc.micro: 1.0.6
markdown-it@13.0.2:
dependencies:
argparse: 2.0.1
entities: 3.0.1
linkify-it: 4.0.1
mdurl: 1.0.1
uc.micro: 1.0.6
markdown-it@14.1.0:
markdown-it@14.1.1:
dependencies:
argparse: 2.0.1
entities: 4.5.0
@@ -8738,7 +8713,7 @@ snapshots:
prosemirror-markdown@1.13.0:
dependencies:
markdown-it: 14.1.0
markdown-it: 14.1.1
prosemirror-model: 1.22.3
prosemirror-menu@1.2.4:
+5 -1
View File
@@ -63,7 +63,11 @@ describe 'Markdown Embeds Configuration' do
'bunny' => [
{ url: 'https://iframe.mediadelivery.net/play/431789/1f105841-cad9-46fe-a70e-b7623c60797c',
expected: { 'library_id' => '431789', 'video_id' => '1f105841-cad9-46fe-a70e-b7623c60797c' } },
{ url: 'https://iframe.mediadelivery.net/play/12345/abcdef-ghijkl', expected: { 'library_id' => '12345', 'video_id' => 'abcdef-ghijkl' } }
{ url: 'https://iframe.mediadelivery.net/play/12345/abcdef-ghijkl', expected: { 'library_id' => '12345', 'video_id' => 'abcdef-ghijkl' } },
{ url: 'https://player.mediadelivery.net/play/431789/1f105841-cad9-46fe-a70e-b7623c60797c',
expected: { 'library_id' => '431789', 'video_id' => '1f105841-cad9-46fe-a70e-b7623c60797c' } },
{ url: 'https://iframe.mediadelivery.net/embed/256380/d9d9ab1f-fc9f-4488-9c26-4ffc653c0024',
expected: { 'library_id' => '256380', 'video_id' => 'd9d9ab1f-fc9f-4488-9c26-4ffc653c0024' } }
],
'codepen' => [
{ url: 'https://codepen.io/username/pen/abcdef', expected: { 'user' => 'username', 'pen_id' => 'abcdef' } },
@@ -84,6 +84,16 @@ RSpec.describe Captain::InboxPendingConversationsResolutionJob, type: :job do
expect(resolvable_pending_conversation.reload.status).to eq('pending')
expect(resolvable_pending_conversation.messages.outgoing).to be_empty
end
it 'falls back to legacy time-based resolve when legacy auto-resolve is forced' do
inbox.account.update!(captain_auto_resolve_mode: 'legacy')
allow(Captain::ConversationCompletionService).to receive(:new)
described_class.perform_now(inbox)
expect(Captain::ConversationCompletionService).not_to have_received(:new)
expect(resolvable_pending_conversation.reload.status).to eq('resolved')
end
end
context 'when LLM evaluation returns complete' do
@@ -322,7 +332,7 @@ RSpec.describe Captain::InboxPendingConversationsResolutionJob, type: :job do
end
it 'does not resolve conversations when auto-resolve is disabled at execution time' do
inbox.account.update!(captain_disable_auto_resolve: true)
inbox.account.update!(captain_auto_resolve_mode: 'disabled')
expect do
described_class.perform_now(inbox)
@@ -331,4 +341,14 @@ RSpec.describe Captain::InboxPendingConversationsResolutionJob, type: :job do
expect(resolvable_pending_conversation.reload.status).to eq('pending')
expect(resolvable_pending_conversation.messages.outgoing).to be_empty
end
it 'falls back to disabled mode from legacy settings key' do
inbox.account.update!(settings: inbox.account.settings.merge('captain_disable_auto_resolve' => true))
expect do
described_class.perform_now(inbox)
end.not_to(change { resolvable_pending_conversation.reload.status })
expect(resolvable_pending_conversation.reload.status).to eq('pending')
end
end
@@ -30,12 +30,28 @@ RSpec.describe Account::ConversationsResolutionSchedulerJob, type: :job do
end
end
context 'when account has captain_disable_auto_resolve enabled' do
context 'when account has captain auto resolve disabled' do
let!(:regular_inbox) { create(:inbox, account: account) }
before do
create(:captain_inbox, captain_assistant: assistant, inbox: regular_inbox)
account.update!(captain_disable_auto_resolve: true)
account.update!(captain_auto_resolve_mode: 'disabled')
end
it 'does not enqueue resolution jobs' do
expect do
described_class.perform_now
end.not_to have_enqueued_job(Captain::InboxPendingConversationsResolutionJob)
.with(regular_inbox)
end
end
context 'when account uses legacy disabled settings key' do
let!(:regular_inbox) { create(:inbox, account: account) }
before do
create(:captain_inbox, captain_assistant: assistant, inbox: regular_inbox)
account.update!(settings: account.settings.merge('captain_disable_auto_resolve' => true))
end
it 'does not enqueue resolution jobs' do
@@ -41,7 +41,18 @@ RSpec.describe Captain::Tools::ResolveConversationTool do
end
describe 'when auto-resolve is disabled for the account' do
before { account.update!(captain_disable_auto_resolve: true) }
before { account.update!(captain_auto_resolve_mode: 'disabled') }
it 'does not resolve and returns a disabled message' do
result = tool.perform(tool_context, reason: 'Possible spam')
expect(result).to eq('Auto-resolve is disabled for this account')
expect(conversation.reload).not_to be_resolved
end
end
describe 'when auto-resolve is disabled via legacy settings key' do
before { account.update!(settings: account.settings.merge('captain_disable_auto_resolve' => true)) }
it 'does not resolve and returns a disabled message' do
result = tool.perform(tool_context, reason: 'Possible spam')
+14 -2
View File
@@ -184,12 +184,12 @@ describe CustomMarkdownRenderer do
end
end
context 'when link is a Bunny.net URL' do
context 'when link is a Bunny.net iframe URL' do
let(:bunny_url) { 'https://iframe.mediadelivery.net/play/431789/1f105841-cad9-46fe-a70e-b7623c60797c' }
it 'renders an iframe with Bunny embed code' do
output = render_markdown_link(bunny_url)
expect(output).to include('src="https://iframe.mediadelivery.net/embed/431789/1f105841-cad9-46fe-a70e-b7623c60797c?autoplay=false&loop=false&muted=false&preload=true&responsive=true"')
expect(output).to include('src="https://player.mediadelivery.net/embed/431789/1f105841-cad9-46fe-a70e-b7623c60797c?autoplay=false&loop=false&muted=false&preload=true&responsive=true"')
expect(output).to include('allowfullscreen')
expect(output).to include('allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"')
end
@@ -200,5 +200,17 @@ describe CustomMarkdownRenderer do
expect(output).to include('position: absolute; top: 0; height: 100%; width: 100%;')
end
end
context 'when link is a Bunny.net player URL' do
let(:bunny_url) { 'https://player.mediadelivery.net/play/431789/1f105841-cad9-46fe-a70e-b7623c60797c' }
it 'renders an iframe with Bunny embed code' do
output = render_markdown_link(bunny_url)
expect(output).to include('embed/431789/1f105841-cad9-46fe-a70e-b7623c60797c')
expect(output).to include('autoplay=false&loop=false&muted=false&preload=true&responsive=true')
expect(output).to include('allowfullscreen')
expect(output).to include('allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"')
end
end
end
end
+38
View File
@@ -198,6 +198,44 @@ RSpec.describe Account do
expect(account.settings['auto_resolve_message']).to eq(message)
end
it 'defaults captain_auto_resolve_mode to legacy when captain_tasks is disabled' do
allow(account).to receive(:feature_enabled?).with('captain_tasks').and_return(false)
expect(account.captain_auto_resolve_mode).to eq('legacy')
expect(account).to be_captain_auto_resolve_legacy
end
it 'defaults captain_auto_resolve_mode to evaluated when captain_tasks is enabled' do
allow(account).to receive(:feature_enabled?).with('captain_tasks').and_return(true)
expect(account.captain_auto_resolve_mode).to eq('evaluated')
expect(account).to be_captain_auto_resolve_evaluated
end
it 'correctly gets and sets captain_auto_resolve_mode' do
account.captain_auto_resolve_mode = 'legacy'
expect(account.captain_auto_resolve_mode).to eq('legacy')
expect(account.settings['captain_auto_resolve_mode']).to eq('legacy')
expect(account).to be_captain_auto_resolve_legacy
end
it 'allows clearing captain_auto_resolve_mode to fall back to feature defaults' do
allow(account).to receive(:feature_enabled?).with('captain_tasks').and_return(false)
account.captain_auto_resolve_mode = nil
expect(account).to be_valid
expect(account.captain_auto_resolve_mode).to eq('legacy')
expect(account.settings['captain_auto_resolve_mode']).to be_nil
end
it 'falls back to disabled mode from legacy settings key' do
account.settings = { 'captain_disable_auto_resolve' => true }
expect(account.captain_auto_resolve_mode).to eq('disabled')
expect(account).to be_captain_auto_resolve_disabled
end
it 'handles nil values correctly' do
account.auto_resolve_after = nil
account.auto_resolve_message = nil