feat(model): Add manager takeover for Chat and manager role for Messa…#168
Open
feat(model): Add manager takeover for Chat and manager role for Messa…#168
Conversation
dapi
commented
Dec 31, 2025
dapi
commented
Dec 31, 2025
| return unless tenant_configured_for_private_chat? | ||
| end | ||
|
|
||
| current_tenant.touch(:last_message_at) |
Owner
Author
There was a problem hiding this comment.
мы можем это делать через touch:true в ассоциациях?
| # @param timeout_minutes [Integer] таймаут | ||
| # @param notify_client [Boolean] уведомлять ли клиента | ||
| def initialize(chat:, user:, timeout_minutes: nil, notify_client: true) | ||
| @chat = chat |
| def call | ||
| # Валидация nil-аргументов до with_lock | ||
| raise ValidationError, 'Chat is required' if chat.nil? | ||
| raise ValidationError, 'User is required' if user.nil? |
Owner
Author
There was a problem hiding this comment.
Убери эти валидации. Эти проверки должны происходить при инициализации объекта и достаточно raise без отлова
| # | ||
| # @return [Result] результат с telegram_message_id или ошибкой | ||
| def call | ||
| validate! |
| def call | ||
| validate! | ||
| send_message | ||
| rescue ArgumentError => e |
Owner
Author
There was a problem hiding this comment.
Удали это и прекрати делать rescue ArgumentErorr и тп. Если такая ошибка случается это ошибка кодирования и она должна просто упасть
| # @param text [String] текст сообщения | ||
| # @param parse_mode [String] режим парсинга | ||
| def initialize(chat:, text:, parse_mode: 'HTML') | ||
| @chat = chat |
| # @param parse_mode [String] режим парсинга | ||
| def initialize(chat:, text:, parse_mode: 'HTML') | ||
| @chat = chat | ||
| @text = text |
| def safe_context | ||
| { | ||
| service: self.class.name, | ||
| chat_id: chat&.id, |
Owner
Author
There was a problem hiding this comment.
chat.id, так как chat всегда есть
…ge (#163) * feat(model): Add manager takeover for Chat and manager role for Message (#154) Model layer implementation for manager takeover feature: Chat model: - Add manager_active, manager_active_at, manager_active_until fields - Add manager_user association (belongs_to User) - Add takeover_by_manager!, release_to_bot!, extend_manager_timeout! methods - Add manager_mode?, bot_mode?, time_until_auto_release helper methods - Add manager_controlled, bot_controlled scopes - Auto-release to bot when timeout expires Message model: - Add 'manager' role to ROLES constant - Add sent_by_user association for tracking who sent manager messages - Add from_manager?, from_bot?, from_client? helper methods - Add from_manager, from_bot, from_client scopes - Validate sent_by_user presence when role is 'manager' This implements issue #154 (Model data for manager takeover). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(config): Move manager takeover timeout to ApplicationConfig - Remove MANAGER_TAKEOVER_TIMEOUT constant from Chat model - Add manager_takeover_timeout_minutes to ApplicationConfig with default 30 - Add coerce_types for proper integer conversion from ENV - Add CLAUDE.md rule: configurable values go to ApplicationConfig, not constants This allows changing timeout via MANAGER_TAKEOVER_TIMEOUT_MINUTES env var without code changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(services): Add manager takeover services (#155) Implements services for manager takeover functionality: - Manager::TakeoverService - takes control of chat from bot - Manager::MessageService - sends manager messages to client - Manager::ReleaseService - releases chat back to bot - Manager::TelegramMessageSender - low-level Telegram API wrapper All services include: - Comprehensive error handling with ErrorLogger - Full test coverage (31 tests passing) - Russian localization for client notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(test): Remove unmockable ApplicationConfig integration test Remove test for max_chat_messages_display config limit because: - Anyway_config singleton cannot be mocked in integration tests - Testing .limit() in integration tests is testing Rails internals - The actual limit functionality is already covered by Rails 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add Tenants::Chats::ManagerController with: - POST /chats/:chat_id/manager/takeover - manager takes control of chat - POST /chats/:chat_id/manager/release - return chat to bot - POST /chats/:chat_id/manager/messages - send message as manager Features: - Full authorization (owner/member of tenant) - Boolean param parsing for notify_client - JSON responses with chat and message data - 20 comprehensive tests covering all endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add rescue_from handlers for RecordNotFound (404 JSON) and ParameterMissing (400 JSON) - Fix notify_client_param nil handling for edge cases - Add tests: viewer role access, release authorization, timeout expiry, JSON error responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add include ErrorLogger to ManagerController - Add logging with context to all rescue_from blocks - Add StandardError fallback handler for JSON 500 responses - Add error_context helper with controller, action, chat_id, user_id, tenant_id 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add FATAL_ERRORS constant for infrastructure errors - Re-raise ConnectionNotEstablished, QueryCanceled, PG::ConnectionBad - These errors should trigger 500 + Bugsnag, not be caught silently 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The view was calling chat.messages.order(:created_at) which triggered a new DB query ignoring the preloaded/limited messages from controller. Changes: - Remove .order(:created_at) from _chat_messages partial (controller already orders messages correctly via load_chat_with_messages) - Add explicit created_at timestamps to message fixtures for consistent ordering in tests - Rewrite flaky test that relied on Mocha stub not working with Anyway::Config singleton pattern in integration tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add validation: release requires chat to be in manager mode - Fix notify_client_param to return true for unrecognized values - Add controller test for release on bot-controlled chat - Update service tests for new release validation behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Verifies that unrecognized values for notify_client parameter default to true (notification sent) instead of nil. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements bot integration for manager takeover functionality: - WebhookController now checks manager_mode before invoking AI - Messages from clients in manager mode are saved without AI response - Turbo Stream broadcast notifies dashboard of new messages - ChatTakeoverTimeoutJob auto-releases chat after 30 minutes - TakeoverService schedules timeout job on takeover - New analytics events: MESSAGE_RECEIVED_IN_MANAGER_MODE, CHAT_TAKEOVER_STARTED, CHAT_TAKEOVER_ENDED When chat is in manager mode: - AI assistant stays silent - Client messages are saved to history - Dashboard receives real-time updates - Timeout job ensures chat returns to bot after inactivity Closes #158 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add error handling with analytics tracking in handle_message_in_manager_mode - Add turbo_stream_from subscription for real-time message updates in dashboard - Add informational logging when timeout job skips already-released chats 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change broadcast_prepend_to to broadcast_append_to (messages should appear at bottom) - Add error handling for broadcast (non-blocking, Redis failures won't affect message saving) - Add I18n key telegram.errors.message_save_failed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Реализован функционал takeover чатов менеджером: - Кнопка "Перехватить диалог" для перевода чата в manager_mode - Форма отправки сообщений менеджером клиенту в Telegram - Кнопка "Вернуть боту" для возврата чата в ai_mode - Таймер автоматического возврата (30 минут) - Уведомления клиенту о переключении режима - ChatTakeoverService: перехват/возврат диалогов с авторизацией - ManagerMessageService: отправка сообщений с rate limiting (60/час) - ChatTakeoverTimeoutJob: автоматический таймаут с retry/discard - Stimulus контроллеры: countdown_controller, chat_message_form_controller - Turbo Stream обновления для real-time UI - Chat: mode enum (ai_mode/manager_mode), taken_by, taken_at - Message: sender_type enum (user/assistant/manager/system) - 17 тестов контроллера ChatsController - 14 тестов модели Chat - 20 тестов ChatTakeoverService - 6 тестов ChatTakeoverTimeoutJob Closes #157 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Critical fixes: - Fix message.sender → message.sent_by_user in _message.html.slim - Fix takeover_time_remaining to use manager_active_until field (was incorrectly using taken_at + timeout, breaking extend_timeout) - Add error logging for notification failures in TakeoverService - Add error logging for notification failures in ReleaseService Important improvements: - Wrap takeover operations in transaction for atomicity - Add test for concurrent takeover prevention - Update @example in ChatTakeoverTimeoutJob documentation Also includes cleanup: - Remove deprecated ChatTakeoverService (replaced by Manager::TakeoverService) - Remove deprecated ManagerMessageService (replaced by Manager::MessageService) - Update tests for new takeover_time_remaining logic 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add analytics tracking for manual release in ReleaseService (CHAT_TAKEOVER_ENDED event) - Handle media messages (photo, document, video, etc.) in manager mode - Add if_exists: true to migration remove_column calls for safety - Add warn logging for ArgumentError in all Manager services 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove duplicate test 'returns error when chat already in bot mode' - Add test for analytics tracking on manual release - Include ActiveJob::TestHelper for assert_enqueued_with 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The test was flaky because it created messages at 14:15 local time,
but if the test ran before 14:15, those messages would be filtered
out by the period_range (which ends at Time.current).
Fix: Use yesterday's date for test messages to ensure they are
always in the past regardless of when the test runs.
Also wrap the test in Time.use_zone('Europe/Moscow') to ensure
consistent timezone behavior across different CI environments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add rescue block to track_takeover_started in TakeoverService Analytics failures should not break the takeover operation - Add released_by_id to CHAT_TAKEOVER_ENDED event properties Ensures consistency with ReleaseService which passes this property 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Add MANAGER_MESSAGE_SENT analytics event - Added to EventConstants with manager_id, message_length properties - Added to AnalyticsService::Events module - MessageService now tracks sent messages 2. Add analytics failure resilience tests - TakeoverService, ReleaseService, MessageService all tested - Verify operations succeed even when analytics fails 3. Add message length validation (4096 chars max) - Telegram limit enforced in MessageService - Tests for too-long and max-length content 4. Add pessimistic locking to prevent race conditions - TakeoverService uses with_lock for atomic check-and-takeover - Prevents two managers from taking over simultaneously 5. Improve test reliability - Switch from bot_client mocks to TelegramMessageSender stubs - More robust against with_lock behavior - Clarified custom timeout test with variable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix @return documentation for extend_manager_timeout! (true/false, not Chat) - Add rescue StandardError in track_timeout_release (ChatTakeoverTimeoutJob) - Change bare `rescue => e` to `rescue StandardError => e` in all analytics tracking methods to avoid catching system exceptions (SystemExit, etc.) Files changed: - app/models/chat.rb: Fix YARD @return documentation - app/jobs/chat_takeover_timeout_job.rb: Add error handling for analytics - app/services/manager/*.rb: Use StandardError instead of bare rescue 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Manager messages not displayed in chat view - Added separate `when 'manager'` case in _message.html.slim - Messages with role='manager' were not matching any case 2. Zero timeout bug in takeover controller - Changed `params[:timeout_minutes]&.to_i` to `.presence&.to_i` - Empty string "" would convert to 0, causing immediate timeout 3. Broadcast errors not logged via ErrorLogger - Replaced Rails.logger.warn with log_error() in webhook_controller - Per CLAUDE.md: use ErrorLogger instead of direct logging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Auto-fixed SpaceInsideArrayLiteralBrackets and alignment issues in chat_topic.rb, demo_data.rb and test files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Issue 1: Added comment explaining MAX_MESSAGE_LENGTH is Telegram API limit - Not configurable, documented with @see link Issue 2: Added manager_active? check in MessageService validation - Prevents sending messages when manager session expired - Added test for expired session scenario Issue 5: Replaced ArgumentError with custom ValidationError - TakeoverService, ReleaseService, MessageService now use ValidationError - Prevents catching unrelated ArgumentError from stdlib Issue 6: Added logging for ArgumentError in TelegramMessageSender - Now logs validation failures for debugging Issue 7: Fixed @return documentation in chat.rb - takeover_by_manager! and release_to_bot! return true, not Chat 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When HOST is set (e.g., '3012.brandymint.ru'), automatically allow: - The exact host - All subdomains (demo.*, admin.*, etc.) Uses Rails dot-prefix convention: '.host' matches host and all subdomains. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The form was sending `text` param but controller expects `message[content]`. Added `scope: :message` and changed field from `:text` to `:content`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes based on @dapi's code review: 1. Remove redundant llm_chat.update! - acts_as_message touch_chat handles timestamp updates automatically 2. Move broadcast_to_dashboard from controller to Message model: - Added after_create_commit :broadcast_to_dashboard callback - Only broadcastable roles (user, assistant, manager) trigger broadcast 3. Remove rescue blocks from handle_message_in_manager_mode: - ActiveRecord errors should propagate to controller-level rescue_from 4. Add else clause to detect_media_type: - Returns 'Неизвестный тип' for unhandled message types 5. Remove redundant rescue StandardError from Manager services: - AnalyticsService.track already has internal error handling (line 59-66) - Removed obsolete tests that verified this redundant behavior Result: -58 lines of code, simpler architecture, single responsibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace custom channel naming with proper Turbo Streams authorization: - Add ActionCable connection with user authentication - Create TenantChatsChannel inheriting from Turbo::StreamsChannel - Authorize subscriptions based on tenant access - Simplify Message broadcast to use chat object directly - Add channel authorization tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace 5 incremental migrations with 2 clean ones: - add_manager_takeover_to_chats: mode, taken_by_id, taken_at, manager_active_until - add_sent_by_user_to_messages: sent_by_user_id Removed intermediate migrations that added and then removed columns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove BROADCASTABLE_ROLES filter - broadcast all messages including system and tool messages for complete visibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace custom broadcast callbacks with broadcasts_refreshes - Message: broadcasts_refreshes_to :chat (page refresh on new messages) - Chat: broadcasts_refreshes (page refresh on mode/takeover changes) - Add turbo-refresh-method=morph and turbo-refresh-scroll=preserve meta tags - Add Turbo Broadcastable documentation link to CLAUDE.md This provides smoother real-time updates using Turbo 8's morphing feature, which intelligently updates the DOM without full page reloads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix Telegram API documentation link (MTProto → Bot API) - Remove @author AI Assistant tags (tracked in git history) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move required argument validation to initialize with `|| raise` - These are programming errors that should crash, not return Result - Business validations remain in separate validate_state! method - Update tests to expect RuntimeError for nil arguments - Add controller-level validation for user-friendly error on blank content - Document pattern in CLAUDE.md for future development 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ArgumentError, TypeError, NameError, NoMethodError are programming errors that should crash and be tracked in Bugsnag, not caught. Only expected runtime errors (network, API, data validation) should be handled gracefully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove rescue from AnalyticsService.track - let errors propagate - Remove rescue from ChatTakeoverTimeoutJob#track_timeout_release - Remove silent failure rescue from Chat#handle_booking_creator_persisted - Add CLAUDE.md rule: do not wrap AnalyticsService.track in rescue Analytics uses background jobs (SolidQueue) - if job fails to enqueue, it's an infrastructure error that should crash and go to Bugsnag. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ApplicationConfig.manager_takeover_enabled (default: true) to control whether manager takeover functionality is available. When disabled: - All manager endpoints return 404 with JSON error - Chat controls UI is completely hidden Changes: - Add manager_takeover_enabled config option with boolean coercion - Add before_action guard in ManagerController - Wrap chat controls view in conditional - Add 3 tests for feature toggle behavior - Fix RuboCop style issues in manager service tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create comprehensive Turbo/Stimulus reference in docs/development/hotwire-guide.md - Cover Turbo Drive, Frames, Streams, Broadcasts - Include Stimulus controllers, targets, actions, values - Add project-specific examples from Super Valera codebase - Link guide from CLAUDE.md and docs/development/README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract calculate_takeover_duration into TakeoverDurationCalculator concern - Use concern in ReleaseService and ChatTakeoverTimeoutJob - Add separate index on chats.mode for queries without tenant_id 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…173) * feat: Replace pagination with infinite scroll in chat sidebar - Replace Kaminari pagination with "Load more" button - Add Stimulus infinite_scroll_controller for dynamic loading - Create _chat_list_items partial for AJAX responses - Reduce excessive padding on chats page (p-8 → p-4) - Add tests for infinite scroll functionality Closes #172 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Add error handling and config improvements to infinite scroll - Add user-visible error state in infinite scroll controller - Handle 401/403 responses with redirect to login - Add CSRF token to AJAX requests - Move chats_per_page to ApplicationConfig (was hardcoded PER_PAGE) - Add error message translations (en/ru) - Add tests for pagination with page parameter - Add test for sort parameter preservation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: Use request.xhr? instead of chat_list_only parameter Replace custom parameter-based detection with Rails-native request.xhr? for cleaner AJAX request handling in infinite scroll. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: Fix chat takeover buttons not responding to clicks - Refactor ManagerController to return Turbo Streams instead of JSON - Move turbo_stream templates to manager/ subdirectory (Rails convention) - Rename send_message.turbo_stream.slim to create_message.turbo_stream.slim - Add data-turbo-action="advance" to chat list links for URL updates - Add Russian translations for controller error messages - Rewrite controller tests to use Turbo Stream assertions Fixes #171 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: Fix tenant subdomain in CLAUDE.md (dev -> demo) The demo tenant is created by seeds, not "dev". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
343cfcf to
be347d1
Compare
Owner
Author
|
@dependabot rebase |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…ge (#163)
Model layer implementation for manager takeover feature:
Chat model:
Message model:
This implements issue #154 (Model data for manager takeover).
🤖 Generated with Claude Code
This allows changing timeout via MANAGER_TAKEOVER_TIMEOUT_MINUTES env var without code changes.
🤖 Generated with Claude Code