feat: Add force legacy auto-resolve flag (#13804)

# Pull Request Template

## Description

Add account setting and store_accessor for
`captain_force_legacy_auto_resolve`.
Enterprise job now skips LLM evaluation when this flag is true and falls
back to legacy time-based resolution. Add spec to cover the fallback.


## Type of change

We recently rolled out Captain deciding if a conversation is resolved or
not. While it is an improvement for majority of customers, some still
prefer the old way of auto-resolving based on inactivity. This PR adds a
check.

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

legacy_auto_resolve = true

<img width="1282" height="848" alt="CleanShot 2026-03-13 at 19 55 55@2x"
src="https://github.com/user-attachments/assets/dfdcc5d5-6d21-462b-87a6-a5e1b1290a8b"
/>


legacy_auto_resolve = false
<img width="1268" height="864" alt="CleanShot 2026-03-13 at 20 00 50@2x"
src="https://github.com/user-attachments/assets/f4719ec6-922a-4c3b-bc45-7b29eaced565"
/>



## 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
This commit is contained in:
Aakash Bakhle
2026-03-14 03:34:58 +05:30
committed by GitHub
parent 412b72db7c
commit a90ffe6264
9 changed files with 121 additions and 10 deletions

View File

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

View File

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

View File

@@ -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')

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