<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Niwa_AI_Admin_Autonomy {
    /**
     * Default worker time limit (seconds). Used on Apache/PHP-FPM where
     * set_time_limit() is honored.
     */
    const TURN_WORKER_TIME_LIMIT = 1800;

    /**
     * Worker time limit on LiteSpeed/LSAPI. LSAPI ignores set_time_limit()
     * pa hard-kill ne moze da se odlozi iz PHP-a; ali in-process soft_deadline
     * jeste cisto PHP cekanje koje ne zavisi od SAPI-a. Drzimo budget ispod
     * uocenog Hostinger kill prozora (~300s) kako bi turn engine uhvatio
     * soft_deadline i snimio continuation pre nego sto LSAPI prekine proces.
     */
    const TURN_WORKER_TIME_LIMIT_LITESPEED = 300;

    const RUNNER_LIVENESS_TTL = 30;
    const TURN_EXECUTION_SOFT_DEADLINE_BUFFER = 45;
    const TURN_PROGRESS_STATE_TTL = 900;
    const POST_REPLY_MEMORY_QUEUE_OPTION = 'niwa_ai_admin_autonomy_post_reply_memory_queue';
    const POST_REPLY_MEMORY_HOOK = 'niwa_ai_admin_autonomy_process_post_reply_memory';
    const BACKGROUND_TICK_HOOK = 'niwa_ai_admin_autonomy_background_tick';
    const WHATSAPP_GATEWAY_WATCHDOG_HOOK = 'niwa_ai_admin_autonomy_whatsapp_gateway_watchdog';
    const WHATSAPP_TEXT_BATCH_FLUSH_HOOK = 'niwa_ai_admin_autonomy_flush_whatsapp_text_batch';
    const VENDOR_INTEGRITY_HOOK = 'niwa_ai_admin_autonomy_check_vendor_integrity';
    const TIMED_AUTOMATION_FALLBACK_STATE_OPTION = 'niwa_ai_admin_autonomy_timed_automation_fallback';
    const TIMED_AUTOMATION_FALLBACK_MIN_INTERVAL = 30;
    const MAX_POST_REPLY_MEMORY_QUEUE = 30;
    const POST_REPLY_MEMORY_MAX_AGE = DAY_IN_SECONDS;
    const TURN_CHAT_LOCALE_TTL = 31536000;
    const TURN_PARTIAL_STREAM_INITIAL_HARD_CHARS = 3000;
    const TURN_PARTIAL_STREAM_MIN_CHARS = 4000;
    const TURN_PARTIAL_STREAM_FOLLOWUP_MIN_CHARS = 2000;
    const TURN_PARTIAL_STREAM_FOLLOWUP_DELAY = 120;
    const TURN_PARTIAL_STREAM_FOLLOWUP_DELAY_MIN_CHARS = 1500;
    const TURN_PARTIAL_STREAM_MAX_BUFFER_CHARS = 8000;
    const TURN_PARTIAL_STREAM_INITIAL_DELAY = 60;
    const TURN_TOOL_STATUS_MIN_VISIBLE_SECONDS = 3;

    private $store;
    private $executor;
    private $telegram;
    private $whatsapp;
    private $chat_agent;
    private $conversation_store;
    private $scheduler;
    /** @var Niwa_AI_Admin_Autonomy_Typing_Feedback|null */
    private $typing_feedback = null;
    /** @var Niwa_AI_Admin_Autonomy_Session_State|null */
    private $session_state;
    /** @var Niwa_AI_Admin_Autonomy_Session_Actor_Message_Runner|null */
    private $session_actor_message_runner = null;
    /** @var Niwa_AI_Admin_Autonomy_Control_Plane|null */
    private $control_plane;
    /** @var Niwa_AI_Admin_Autonomy_Actor_Runtime|null */
    private $actor_runtime;
    /** @var Niwa_AI_Admin_Autonomy_Actor_Runtime_Recovery|null */
    private $actor_runtime_recovery = null;
    /** @var Niwa_AI_Admin_Autonomy_Actor_Runtime_Lease_Keeper|null */
    private $actor_runtime_lease_keeper = null;
    /** @var Niwa_AI_Admin_Autonomy_Pending_Runtime_State|null */
    private $pending_runtime_state = null;
    /** @var Niwa_AI_Admin_Autonomy_Admin_Request_Helper|null */
    private $admin_request_helper = null;
    /** @var Niwa_AI_Admin_Autonomy_Outgoing_Reply_Formatter|null */
    private $outgoing_reply_formatter = null;
    /** @var Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger|null */
    private $runtime_trace_logger = null;
    private $last_founder_chat_result = array();

    /** @var Niwa_AI_Admin_Autonomy_Browser_Message_Context|null */
    private $browser_message_context = null;
    /** @var Niwa_AI_Admin_Autonomy_Browser_Session|null */
    private $browser_session = null;
    private $active_loopback_runner_id = '';
    /** @var Niwa_AI_Admin_Autonomy_Loopback_Runner_Executor|null */
    private $loopback_runner_executor = null;
    /** @var Niwa_AI_Admin_Autonomy_Runner_Dispatcher|null */
    private $runner_dispatcher = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Ingress|null */
    private $telegram_ingress = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Reactions|null */
    private $telegram_reactions = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Authorization|null */
    private $telegram_authorization = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Message_Preprocessor|null */
    private $telegram_message_preprocessor = null;
    /** @var Niwa_AI_Admin_Autonomy_Actor_Intake|null */
    private $actor_intake = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Message_Gates|null */
    private $telegram_message_gates = null;
    /** @var Niwa_AI_Admin_Autonomy_Message_Handler|null */
    private $message_handler = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Poll_Runner|null */
    private $telegram_poll_runner = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Buffer_Event_Logger|null */
    private $telegram_buffer_event_logger = null;
    /** @var Niwa_AI_Admin_Autonomy_WhatsApp_Text_Batcher|null */
    private $whatsapp_text_batcher = null;
    /** @var Niwa_AI_Admin_Autonomy_WhatsApp_Command_Access|null */
    private $whatsapp_command_access = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Voice_Transcriber|null */
    private $telegram_voice_transcriber = null;
    /** @var Niwa_AI_Admin_Autonomy_Telegram_Message_Context_Builder|null */
    private $telegram_message_context_builder = null;
    /** @var Niwa_AI_Admin_Autonomy_Media_Context_Builder|null */
    private $media_context_builder = null;
    /** @var Niwa_AI_Admin_Autonomy_Manual_Diagnostic_Runner|null */
    private $manual_diagnostic_runner = null;
    /** @var Niwa_AI_Admin_Autonomy_Context_Compactor|null */
    private $context_compactor = null;
    /** @var Niwa_AI_Admin_Autonomy_Status_Message_Builder|null */
    private $status_message_builder = null;
    /** @var Niwa_AI_Admin_Autonomy_Help_Message_Builder|null */
    private $help_message_builder = null;
    /** @var Niwa_AI_Admin_Autonomy_Plan_Preview_Builder|null */
    private $plan_preview_builder = null;
    /** @var Niwa_AI_Admin_Autonomy_Runtime_Command_Handler|null */
    private $runtime_command_handler = null;
    /** @var Niwa_AI_Admin_Autonomy_Command_Utils|null */
    private $command_utils = null;
    /** @var Niwa_AI_Admin_Autonomy_Runtime_Utils|null */
    private $runtime_utils = null;
    /** @var Niwa_AI_Admin_Autonomy_Runtime_Stopper|null */
    private $runtime_stopper = null;
    /** @var Niwa_AI_Admin_Autonomy_Rollback_Runner|null */
    private $rollback_runner = null;
    /** @var Niwa_AI_Admin_Autonomy_Runtime_Locks|null */
    private $runtime_locks = null;
    /** @var Niwa_AI_Admin_Autonomy_Vendor_Integrity_Monitor|null */
    private $vendor_integrity_monitor = null;
    /** @var Niwa_AI_Admin_Autonomy_Scheduled_Task_Runner|null */
    private $scheduled_task_runner = null;
    /** @var Niwa_AI_Admin_Autonomy_Timed_Automation_Fallback|null */
    private $timed_automation_fallback = null;
    /** @var Niwa_AI_Admin_Autonomy_Background_Wake_Loop|null */
    private $background_wake_loop = null;
    /** @var Niwa_AI_Admin_Autonomy_Turn_Tool_Progress_Translator|null */
    private $turn_tool_progress_translator = null;
    /** @var Niwa_AI_Admin_Autonomy_Turn_Stream_Buffer|null */
    private $turn_stream_buffer = null;
    /** @var Niwa_AI_Admin_Autonomy_Turn_Stream_Lifecycle|null */
    private $turn_stream_lifecycle = null;
    /** @var Niwa_AI_Admin_Autonomy_Turn_Tool_Status_Messenger|null */
    private $turn_tool_status_messenger = null;

    /** @var Niwa_AI_Admin_Autonomy_Turn_Progress_Callback|null */
    private $turn_progress_callback = null;

    /** @var Niwa_AI_Admin_Autonomy_Post_Reply_Memory_Queue|null */
    private $post_reply_memory_queue = null;

    /** @var Niwa_AI_Admin_Autonomy_Background_Tick_Runner|null */
    private $background_tick_runner = null;

    /** @var Niwa_AI_Admin_Autonomy_Actor_Message_Preparer|null */
    private $actor_message_preparer = null;

    /** @var Niwa_AI_Admin_Autonomy_Founder_Message_Executor|null */
    private $founder_message_executor = null;
    /** @var Niwa_AI_Admin_Autonomy_Founder_Identity_Helper|null */
    private $founder_identity_helper = null;

    /**
     * Holds the last soft-deadline cancellation result from a turn.
     *
     * Set by chat_with_founder() when the turn engine returns a soft_deadline
     * cancellation with a context snapshot, so the actor runtime can
     * re-enqueue a continuation turn inside the same session mailbox.
     *
     * @var array|null
     */
    private $last_soft_deadline_result = null;

    /**
     * Lazy-instantiated album & text-fragment buffer (Phase 4).
     *
     * @var Niwa_AI_Admin_Autonomy_Telegram_Album_Buffer|null
     */
    private $album_buffer = null;

    private function get_album_buffer() {
        if ( null === $this->album_buffer && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Album_Buffer' ) ) {
            $this->album_buffer = new Niwa_AI_Admin_Autonomy_Telegram_Album_Buffer();
        }
        return $this->album_buffer;
    }

    private function get_whatsapp_text_batcher() {
        if ( null === $this->whatsapp_text_batcher && class_exists( 'Niwa_AI_Admin_Autonomy_WhatsApp_Text_Batcher' ) ) {
            $this->whatsapp_text_batcher = new Niwa_AI_Admin_Autonomy_WhatsApp_Text_Batcher();
        }
        return $this->whatsapp_text_batcher;
    }

    private function get_whatsapp_command_access() {
        if ( null === $this->whatsapp_command_access && class_exists( 'Niwa_AI_Admin_Autonomy_WhatsApp_Command_Access' ) ) {
            $this->whatsapp_command_access = new Niwa_AI_Admin_Autonomy_WhatsApp_Command_Access();
        }
        return $this->whatsapp_command_access;
    }

    private function get_typing_feedback() {
        if ( null === $this->typing_feedback && class_exists( 'Niwa_AI_Admin_Autonomy_Typing_Feedback' ) ) {
            $this->typing_feedback = new Niwa_AI_Admin_Autonomy_Typing_Feedback(
                $this->telegram,
                array(
                    'send_typing_indicator'        => function ( $chat_id ) {
                        return $this->send_typing_indicator( $chat_id );
                    },
                    'should_use_local_cli_runner'   => function () {
                        return $this->should_use_local_cli_runner();
                    },
                    'build_loopback_signature_body' => function ( $prefix, $secret ) {
                        return $this->build_loopback_signature_body( $prefix, $secret );
                    },
                    'verify_loopback_signature'     => function ( $prefix, $secret ) {
                        return $this->verify_loopback_signature( $prefix, $secret );
                    },
                    'debug_logger'                  => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->typing_feedback;
    }

    private function get_runner_dispatcher() {
        if ( null === $this->runner_dispatcher && class_exists( 'Niwa_AI_Admin_Autonomy_Runner_Dispatcher' ) ) {
            $this->runner_dispatcher = new Niwa_AI_Admin_Autonomy_Runner_Dispatcher(
                $this->telegram,
                array(
                    'has_active_actor_messages'       => function () {
                        return $this->has_active_actor_messages();
                    },
                    'clear_stale_buffer_runner_state' => function ( $state, $reason ) {
                        return $this->clear_stale_buffer_runner_state( $state, $reason );
                    },
                    'build_loopback_signature_body'   => function ( $prefix, $secret ) {
                        return $this->build_loopback_signature_body( $prefix, $secret );
                    },
                    'debug_logger'                    => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->runner_dispatcher;
    }

    private function get_loopback_runner_executor() {
        if ( null === $this->loopback_runner_executor && class_exists( 'Niwa_AI_Admin_Autonomy_Loopback_Runner_Executor' ) ) {
            $this->loopback_runner_executor = new Niwa_AI_Admin_Autonomy_Loopback_Runner_Executor(
                $this->telegram,
                array(
                    'get_active_loopback_runner_id'       => function () {
                        return $this->active_loopback_runner_id;
                    },
                    'set_active_loopback_runner_id'       => function ( $runner_id ) {
                        $this->active_loopback_runner_id = (string) $runner_id;
                    },
                    'runner_state_is_owned_by_current_request' => function ( $state, $runner_id ) {
                        return $this->runner_state_is_owned_by_current_request( $state, $runner_id );
                    },
                    'poll_telegram_updates'              => function () {
                        return $this->poll_telegram_updates();
                    },
                    'run_background_wake_loop'           => function () {
                        return $this->run_background_wake_loop();
                    },
                    'format_runtime_exception_message'   => function ( Throwable $e ) {
                        return $this->format_runtime_exception_message( $e );
                    },
                    'has_pending_actor_messages'         => function () {
                        return $this->has_pending_actor_messages();
                    },
                    'has_pending_transport_buffers'      => function () {
                        return $this->has_pending_transport_buffers();
                    },
                    'is_telegram_webhook_active'         => function () {
                        return $this->is_telegram_webhook_active();
                    },
                    'has_due_scheduled_jobs_soon'        => function ( $window_seconds ) {
                        return $this->has_due_scheduled_jobs_soon( $window_seconds );
                    },
                    'dispatch_loopback_runner'           => function ( $reason, $active_ttl ) {
                        return $this->dispatch_loopback_runner( $reason, $active_ttl );
                    },
					'schedule_runner_retry'              => function () {
						if ( function_exists( 'wp_schedule_single_event' ) && ! wp_next_scheduled( self::BACKGROUND_TICK_HOOK ) ) {
							wp_schedule_single_event( time() + 30, self::BACKGROUND_TICK_HOOK );
						}
					},
                    'debug_logger'                       => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->loopback_runner_executor;
    }

    private function get_telegram_ingress() {
        if ( null === $this->telegram_ingress && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Ingress' ) ) {
            $this->telegram_ingress = new Niwa_AI_Admin_Autonomy_Telegram_Ingress(
                $this->telegram,
                array(
                    'process_extracted_message'  => function ( $message, $wake_runner ) {
                        return $this->process_extracted_telegram_message( $message, $wake_runner );
                    },
                    'dispatch_runner'            => function ( $reason, $active_ttl ) {
                        return $this->dispatch_loopback_runner( $reason, $active_ttl );
                    },
                    'runner_liveness_ttl'        => function () {
                        return $this->get_runner_liveness_ttl();
                    },
                    'should_use_local_cli_runner' => function () {
                        return $this->should_use_local_cli_runner();
                    },
                    'process_actor_runtime'      => function ( $limit ) {
                        return $this->process_actor_runtime( $limit );
                    },
                    'format_exception'           => function ( Throwable $e ) {
                        return $this->format_runtime_exception_message( $e );
                    },
                    'debug_logger'               => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->telegram_ingress;
    }

    private function get_telegram_reactions() {
        if ( null === $this->telegram_reactions && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Reactions' ) ) {
            $this->telegram_reactions = new Niwa_AI_Admin_Autonomy_Telegram_Reactions(
                $this->telegram,
                $this->conversation_store,
                array(
                    'get_founder_session_id' => function ( $chat_id ) {
                        return $this->get_founder_session_id( $chat_id );
                    },
                    'debug_logger'           => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->telegram_reactions;
    }

    private function get_telegram_authorization() {
        if ( null === $this->telegram_authorization && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Authorization' ) ) {
            $this->telegram_authorization = new Niwa_AI_Admin_Autonomy_Telegram_Authorization(
                $this->telegram,
                array(
                    'debug_logger' => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->telegram_authorization;
    }

    private function get_telegram_message_preprocessor() {
        if ( null === $this->telegram_message_preprocessor && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Message_Preprocessor' ) ) {
            $this->telegram_message_preprocessor = new Niwa_AI_Admin_Autonomy_Telegram_Message_Preprocessor();
        }

        return $this->telegram_message_preprocessor;
    }

    private function get_actor_intake() {
        if ( null === $this->actor_intake && class_exists( 'Niwa_AI_Admin_Autonomy_Actor_Intake' ) ) {
            $this->actor_intake = new Niwa_AI_Admin_Autonomy_Actor_Intake(
                $this->telegram,
                $this->actor_runtime,
                array(
                    'should_queue_message'  => function ( $message ) {
                        return $this->should_queue_founder_message( $message );
                    },
                    'begin_typing_feedback' => function ( $chat_id ) {
                        $this->begin_typing_feedback( $chat_id );
                    },
                    'dispatch_runner'       => function ( $reason, $active_ttl ) {
                        return $this->dispatch_loopback_runner( $reason, $active_ttl );
                    },
                    'runner_liveness_ttl'   => function () {
                        return $this->get_runner_liveness_ttl();
                    },
                    'debug_logger'          => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->actor_intake;
    }

    private function get_telegram_message_gates() {
        if ( null === $this->telegram_message_gates && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Message_Gates' ) ) {
            $this->telegram_message_gates = new Niwa_AI_Admin_Autonomy_Telegram_Message_Gates(
                $this->telegram,
                array(
                    'is_stop_command_text'      => function ( $text ) {
                        return $this->is_stop_command_text( $text );
                    },
                    'resolve_actor_session_id'  => function ( $message ) {
                        return $this->resolve_actor_session_id( $message );
                    },
                    'stop_runtime_for_session'  => function ( $session_id, $chat_id, $message_id, $source ) {
                        return $this->stop_runtime_for_session( $session_id, $chat_id, $message_id, $source );
                    },
                    'remember_exchange'         => function ( $chat_id, $text, $reply, $context = array() ) {
                        $this->remember_exchange( $chat_id, $text, $reply, $context );
                    },
                    'debug_logger'              => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->telegram_message_gates;
    }

    private function get_message_handler() {
        if ( null === $this->message_handler && class_exists( 'Niwa_AI_Admin_Autonomy_Message_Handler' ) ) {
            $this->message_handler = new Niwa_AI_Admin_Autonomy_Message_Handler(
                array(
                    'handle_reaction_message'          => function ( $message ) {
                        return $this->handle_telegram_reaction_message( $message );
                    },
                    'authorize_channel_message'         => function ( $message ) {
                        return $this->authorize_telegram_message( $message );
                    },
                    'remember_turn_progress_locale'    => function ( $message ) {
                        $this->remember_turn_progress_locale( $message );
                    },
                    'remember_admin_delivery_origin'    => function ( $message ) {
                        $this->remember_admin_delivery_origin( $message );
                    },
                    'get_message_preprocessor'         => function () {
                        return $this->get_telegram_message_preprocessor();
                    },
                    'get_message_gates'                => function () {
                        return $this->get_telegram_message_gates();
                    },
                    'get_actor_intake'                 => function () {
                        return $this->get_actor_intake();
                    },
                    'debug_logger'                     => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->message_handler;
    }

    private function get_telegram_poll_runner() {
        if ( null === $this->telegram_poll_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Poll_Runner' ) ) {
            $this->telegram_poll_runner = new Niwa_AI_Admin_Autonomy_Telegram_Poll_Runner(
                $this->telegram,
                $this->get_album_buffer(),
                array(
                    'is_telegram_webhook_active'        => function () {
                        return $this->is_telegram_webhook_active();
                    },
                    'acquire_lock'                      => function ( $key, $ttl ) {
                        return $this->acquire_lock( $key, $ttl );
                    },
                    'release_lock'                      => function ( $key ) {
                        $this->release_lock( $key );
                    },
                    'log_telegram_transport_buffer_event' => function ( $event, $message ) {
                        $this->log_telegram_transport_buffer_event( $event, $message );
                    },
                    'handle_incoming_message'           => function ( $message, $wake_runner ) {
                        return $this->handle_incoming_message( $message, $wake_runner );
                    },
                    'begin_typing_feedback'             => function ( $chat_id ) {
                        $this->begin_typing_feedback( $chat_id );
                    },
                    'process_extracted_telegram_message' => function ( $message, $wake_runner ) {
                        return $this->process_extracted_telegram_message( $message, $wake_runner );
                    },
                    'process_due_scheduled_jobs'        => function ( $limit ) {
                        return $this->process_due_scheduled_jobs( $limit );
                    },
                    'flush_whatsapp_text_batch'         => function () {
                        return $this->flush_whatsapp_text_batch();
                    },
                    'process_actor_runtime'             => function ( $limit ) {
                        return $this->process_actor_runtime( $limit );
                    },
                    'debug_logger'                      => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->telegram_poll_runner;
    }

    private function get_telegram_buffer_event_logger() {
        if ( null === $this->telegram_buffer_event_logger && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Buffer_Event_Logger' ) ) {
            $this->telegram_buffer_event_logger = new Niwa_AI_Admin_Autonomy_Telegram_Buffer_Event_Logger(
                array(
                    'text_length'  => function ( $text ) {
                        return $this->text_length( $text );
                    },
                    'text_substr'  => function ( $text, $start, $length = null ) {
                        return $this->text_substr( $text, $start, $length );
                    },
                    'debug_logger' => function ( $event, $payload ) {
                        $this->log_turn_debug( $event, $payload );
                    },
                )
            );
        }

        return $this->telegram_buffer_event_logger;
    }

    private function get_telegram_voice_transcriber() {
        if ( null === $this->telegram_voice_transcriber && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Voice_Transcriber' ) ) {
            $this->telegram_voice_transcriber = new Niwa_AI_Admin_Autonomy_Telegram_Voice_Transcriber( $this->telegram, $this->whatsapp, $this->chat_agent );
        }

        return $this->telegram_voice_transcriber;
    }

    private function get_telegram_message_context_builder() {
        if ( null === $this->telegram_message_context_builder && class_exists( 'Niwa_AI_Admin_Autonomy_Telegram_Message_Context_Builder' ) ) {
            $this->telegram_message_context_builder = new Niwa_AI_Admin_Autonomy_Telegram_Message_Context_Builder(
                $this->telegram,
                $this->chat_agent,
                $this->conversation_store,
                $this->session_state,
                array(
                    'get_founder_session_id'         => function ( $chat_id ) {
                        return $this->get_founder_session_id( $chat_id );
                    },
                    'build_media_attachments_context' => function ( $message ) {
                        return $this->build_media_attachments_context( $message );
                    },
                )
            );
        }

        return $this->telegram_message_context_builder;
    }

    private function get_media_context_builder() {
        if ( null === $this->media_context_builder && class_exists( 'Niwa_AI_Admin_Autonomy_Media_Context_Builder' ) ) {
            $this->media_context_builder = new Niwa_AI_Admin_Autonomy_Media_Context_Builder(
                $this->telegram,
                $this->chat_agent,
                array(
                    'transcribe_voice_message' => function ( $ref ) {
                        return $this->transcribe_voice_message( $ref );
                    },
					'resolve_whatsapp_local_media_path' => function ( $path ) {
						return $this->whatsapp && method_exists( $this->whatsapp, 'resolve_local_media_path' )
							? $this->whatsapp->resolve_local_media_path( $path )
							: '';
					},
                )
            );
        }

        return $this->media_context_builder;
    }

    private function get_manual_diagnostic_runner() {
        if ( null === $this->manual_diagnostic_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Manual_Diagnostic_Runner' ) ) {
            $this->manual_diagnostic_runner = new Niwa_AI_Admin_Autonomy_Manual_Diagnostic_Runner( $this->chat_agent );
        }

        return $this->manual_diagnostic_runner;
    }

    private function get_context_compactor() {
        if ( null === $this->context_compactor && class_exists( 'Niwa_AI_Admin_Autonomy_Context_Compactor' ) ) {
            $this->context_compactor = new Niwa_AI_Admin_Autonomy_Context_Compactor();
        }

        return $this->context_compactor;
    }

    private function get_status_message_builder() {
        if ( null === $this->status_message_builder && class_exists( 'Niwa_AI_Admin_Autonomy_Status_Message_Builder' ) ) {
            $this->status_message_builder = new Niwa_AI_Admin_Autonomy_Status_Message_Builder(
                $this->store,
                $this->telegram,
                $this->control_plane,
                $this->chat_agent,
                $this->scheduler,
                array(
                    'format_timestamp' => function ( $timestamp ) {
                        return $this->format_timestamp( $timestamp );
                    },
                    'truncate_text'    => function ( $text, $limit ) {
                        return $this->truncate_text( $text, $limit );
                    },
                )
            );
        }

        return $this->status_message_builder;
    }

    private function get_help_message_builder() {
        if ( null === $this->help_message_builder && class_exists( 'Niwa_AI_Admin_Autonomy_Help_Message_Builder' ) ) {
            $this->help_message_builder = new Niwa_AI_Admin_Autonomy_Help_Message_Builder();
        }

        return $this->help_message_builder;
    }

    private function get_plan_preview_builder() {
        if ( null === $this->plan_preview_builder && class_exists( 'Niwa_AI_Admin_Autonomy_Plan_Preview_Builder' ) ) {
            $this->plan_preview_builder = new Niwa_AI_Admin_Autonomy_Plan_Preview_Builder();
        }

        return $this->plan_preview_builder;
    }

    private function get_runtime_command_handler() {
        if ( null === $this->runtime_command_handler && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Command_Handler' ) ) {
            $this->runtime_command_handler = new Niwa_AI_Admin_Autonomy_Runtime_Command_Handler(
                $this->chat_agent,
                $this->control_plane,
                $this->conversation_store,
                array(
                    'build_status_message'            => function ( $context ) {
                        return $this->build_status_message( $context );
                    },
                    'rollback_last_successful_run'    => function () {
                        return $this->rollback_last_successful_run();
                    },
                    'build_remove_keyboard'           => function () {
                        return $this->build_remove_keyboard();
                    },
                    'build_help_message'              => function () {
                        return $this->build_help_message();
                    },
                    'build_stock_manager_help_message' => function () {
                        return $this->build_stock_manager_help_message();
                    },
                    'stop_runtime_for_session'        => function ( $session_id, $chat_id, $message_id, $source ) {
                        return $this->stop_runtime_for_session( $session_id, $chat_id, $message_id, $source );
                    },
                    'clear_turn_stream_state'         => function ( $chat_id, $message_id ) {
                        $this->clear_turn_stream_state( $chat_id, $message_id );
                    },
                )
            );
        }

        return $this->runtime_command_handler;
    }

    private function get_command_utils() {
        if ( null === $this->command_utils && class_exists( 'Niwa_AI_Admin_Autonomy_Command_Utils' ) ) {
            $this->command_utils = new Niwa_AI_Admin_Autonomy_Command_Utils();
        }

        return $this->command_utils;
    }

    private function get_runtime_utils() {
        if ( null === $this->runtime_utils && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Utils' ) ) {
            $this->runtime_utils = new Niwa_AI_Admin_Autonomy_Runtime_Utils(
                array(
                    'turn_worker_time_limit' => function () {
                        return self::get_turn_worker_time_limit();
                    },
                    'debug_logger'           => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->runtime_utils;
    }

    private function get_runtime_stopper() {
        if ( null === $this->runtime_stopper && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Stopper' ) ) {
            $this->runtime_stopper = new Niwa_AI_Admin_Autonomy_Runtime_Stopper(
                $this->chat_agent,
                $this->control_plane,
                $this->telegram,
                array(
                    'clear_turn_stream_state' => function ( $chat_id, $message_id ) {
                        $this->clear_turn_stream_state( $chat_id, $message_id );
                    },
                    'end_typing_feedback'     => function ( $chat_id ) {
                        $this->end_typing_feedback( $chat_id );
                    },
                    'log_turn_debug'          => function ( $event, $context ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->runtime_stopper;
    }

    private function get_rollback_runner() {
        if ( null === $this->rollback_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Rollback_Runner' ) ) {
            $this->rollback_runner = new Niwa_AI_Admin_Autonomy_Rollback_Runner(
                $this->store,
                $this->executor,
                array(
                    'acquire_lock'                   => function ( $name, $ttl ) {
                        return $this->acquire_lock( $name, $ttl );
                    },
                    'release_lock'                   => function ( $name ) {
                        $this->release_lock( $name );
                    },
                    'send_typing_indicator_from_run' => function ( $run ) {
                        $this->send_typing_indicator_from_run( $run );
                    },
                )
            );
        }

        return $this->rollback_runner;
    }

    private function get_runtime_locks() {
        if ( null === $this->runtime_locks && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Locks' ) ) {
            $this->runtime_locks = new Niwa_AI_Admin_Autonomy_Runtime_Locks( $this->telegram );
        }

        return $this->runtime_locks;
    }

    private function get_vendor_integrity_monitor() {
        if ( null === $this->vendor_integrity_monitor && class_exists( 'Niwa_AI_Admin_Autonomy_Vendor_Integrity_Monitor' ) ) {
            $this->vendor_integrity_monitor = new Niwa_AI_Admin_Autonomy_Vendor_Integrity_Monitor(
                array(
                    'acquire_lock'          => function ( $name, $ttl ) {
                        return $this->acquire_lock( $name, $ttl );
                    },
                    'release_lock'          => function ( $name ) {
                        $this->release_lock( $name );
                    },
                    'enqueue_actor_message' => function ( $message ) {
                        if ( ! $this->actor_runtime || ! method_exists( $this->actor_runtime, 'enqueue_message' ) ) {
                            return array(
                                'success' => false,
                                'error'   => 'actor_runtime_unavailable',
                            );
                        }

                        $entry = (array) $this->actor_runtime->enqueue_message( is_array( $message ) ? $message : array(), true );
                        if ( empty( $entry['id'] ) && empty( $entry['duplicate_existing'] ) ) {
                            return array(
                                'success' => false,
                                'error'   => 'vendor_integrity_actor_enqueue_failed',
                            );
                        }

                        $this->dispatch_loopback_runner( 'vendor-integrity', $this->get_runner_liveness_ttl() );

                        return array(
                            'success'  => true,
                            'queued'   => empty( $entry['duplicate_existing'] ),
                            'reason'   => ! empty( $entry['duplicate_existing'] ) ? 'duplicate_actor' : 'queued_actor',
                            'actor_id' => isset( $entry['id'] ) ? (string) $entry['id'] : '',
                        );
                    },
                )
            );
        }

        return $this->vendor_integrity_monitor;
    }

    private function get_scheduled_task_runner() {
        if ( null === $this->scheduled_task_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Scheduled_Task_Runner' ) ) {
            $this->scheduled_task_runner = new Niwa_AI_Admin_Autonomy_Scheduled_Task_Runner(
                $this->chat_agent,
                array(
                    'process_due_scheduled_jobs' => function ( $limit ) {
                        return $this->process_due_scheduled_jobs( $limit );
                    },
                    'process_actor_runtime'      => function ( $limit ) {
                        return $this->process_actor_runtime( $limit );
                    },
                    'process_post_reply_memory'  => function ( $limit ) {
                        return $this->process_post_reply_memory_queue( $limit );
                    },
                )
            );
        }

        return $this->scheduled_task_runner;
    }

    private function get_timed_automation_fallback() {
        if ( null === $this->timed_automation_fallback && class_exists( 'Niwa_AI_Admin_Autonomy_Timed_Automation_Fallback' ) ) {
            $this->timed_automation_fallback = new Niwa_AI_Admin_Autonomy_Timed_Automation_Fallback(
                self::TIMED_AUTOMATION_FALLBACK_STATE_OPTION,
                array( self::BACKGROUND_TICK_HOOK, 'niwa_ai_admin_autonomy_process_scheduled_tasks', 'niwa_ai_admin_autonomy_process_scheduled_job' ),
                self::TIMED_AUTOMATION_FALLBACK_MIN_INTERVAL,
                array(
                    'debug_logger' => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->timed_automation_fallback;
    }

    private function get_background_wake_loop() {
        if ( null === $this->background_wake_loop && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Wake_Loop' ) ) {
            $this->background_wake_loop = new Niwa_AI_Admin_Autonomy_Background_Wake_Loop(
                self::get_turn_worker_time_limit(),
                array(
                    'get_telegram'                    => function () {
                        return $this->telegram;
                    },
                    'run_timed_automation_tick'       => function () {
                        return $this->run_timed_automation_tick();
                    },
                    'has_enabled_operative_heartbeat' => function ( $continuous_only ) {
                        return $this->has_enabled_operative_heartbeat( $continuous_only );
                    },
                    'has_due_scheduled_jobs_soon'     => function ( $window_seconds ) {
                        return $this->has_due_scheduled_jobs_soon( $window_seconds );
                    },
                )
            );
        }

        return $this->background_wake_loop;
    }

    private function get_turn_tool_progress_translator() {
        if ( null === $this->turn_tool_progress_translator && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Tool_Progress_Translator' ) ) {
            $this->turn_tool_progress_translator = new Niwa_AI_Admin_Autonomy_Turn_Tool_Progress_Translator( self::TURN_CHAT_LOCALE_TTL );
        }

        return $this->turn_tool_progress_translator;
    }

    private function get_turn_stream_buffer() {
        if ( null === $this->turn_stream_buffer && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Stream_Buffer' ) ) {
            $this->turn_stream_buffer = new Niwa_AI_Admin_Autonomy_Turn_Stream_Buffer(
                $this->telegram,
                array(
                    'initial_delay'            => self::TURN_PARTIAL_STREAM_INITIAL_DELAY,
                    'initial_hard_chars'       => self::TURN_PARTIAL_STREAM_INITIAL_HARD_CHARS,
                    'max_buffer_chars'         => self::TURN_PARTIAL_STREAM_MAX_BUFFER_CHARS,
                    'followup_min_chars'       => self::TURN_PARTIAL_STREAM_FOLLOWUP_MIN_CHARS,
                    'followup_delay'           => self::TURN_PARTIAL_STREAM_FOLLOWUP_DELAY,
                    'followup_delay_min_chars' => self::TURN_PARTIAL_STREAM_FOLLOWUP_DELAY_MIN_CHARS,
                    'min_chars'                => self::TURN_PARTIAL_STREAM_MIN_CHARS,
                ),
                array(
                    'log'         => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                    'text_length' => function ( $text ) {
                        return $this->text_length( $text );
                    },
                    'text_substr' => function ( $text, $start, $length = null ) {
                        return $this->text_substr( $text, $start, $length );
                    },
                )
            );
        }

        return $this->turn_stream_buffer;
    }

    private function get_turn_stream_lifecycle() {
        if ( null === $this->turn_stream_lifecycle && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Stream_Lifecycle' ) ) {
            $this->turn_stream_lifecycle = new Niwa_AI_Admin_Autonomy_Turn_Stream_Lifecycle(
                $this->telegram,
                array(
                    'clear_turn_stream_state'       => function ( $chat_id, $message_id ) {
                        $this->clear_turn_stream_state( $chat_id, $message_id );
                    },
                    'update_turn_stream_state'      => function ( $chat_id, $message_id, $state ) {
                        $this->update_turn_stream_state( $chat_id, $message_id, $state );
                    },
                    'get_turn_stream_state'         => function ( $chat_id, $message_id ) {
                        return $this->get_turn_stream_state( $chat_id, $message_id );
                    },
                    'maybe_flush_turn_stream_buffer' => function ( $state, $force ) {
                        return $this->maybe_flush_turn_stream_buffer( $state, $force );
                    },
                    'text_length'                   => function ( $text ) {
                        return $this->text_length( $text );
                    },
                    'text_substr'                   => function ( $text, $start, $length = null ) {
                        return $this->text_substr( $text, $start, $length );
                    },
                    'debug_logger'                  => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->turn_stream_lifecycle;
    }

    private function get_turn_tool_status_messenger() {
        if ( null === $this->turn_tool_status_messenger && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Tool_Status_Messenger' ) ) {
            $this->turn_tool_status_messenger = new Niwa_AI_Admin_Autonomy_Turn_Tool_Status_Messenger(
                $this->telegram,
                self::TURN_TOOL_STATUS_MIN_VISIBLE_SECONDS,
                array(
                    'translate' => function ( $chat_id, $tool_name, $phase ) {
                        return $this->translate_turn_tool_progress( $chat_id, $tool_name, $phase );
                    },
                    'log'       => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->turn_tool_status_messenger;
    }

    private function get_actor_runtime_recovery() {
        if ( null === $this->actor_runtime_recovery && class_exists( 'Niwa_AI_Admin_Autonomy_Actor_Runtime_Recovery' ) ) {
            $this->actor_runtime_recovery = new Niwa_AI_Admin_Autonomy_Actor_Runtime_Recovery(
                $this->telegram,
                $this->control_plane,
                $this->session_state,
                array(
                    'log' => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->actor_runtime_recovery;
    }

    private function get_actor_runtime_lease_keeper() {
        if ( null === $this->actor_runtime_lease_keeper && class_exists( 'Niwa_AI_Admin_Autonomy_Actor_Runtime_Lease_Keeper' ) ) {
            $this->actor_runtime_lease_keeper = new Niwa_AI_Admin_Autonomy_Actor_Runtime_Lease_Keeper(
                $this->control_plane,
                Niwa_AI_Admin_Autonomy_Actor_Runtime::ACTOR_MESSAGE_LEASE_TTL,
                Niwa_AI_Admin_Autonomy_Actor_Runtime::ACTOR_MESSAGE_HEARTBEAT_INTERVAL,
                array(
                    'turn_worker_time_limit'                  => function () {
                        return self::get_turn_worker_time_limit();
                    },
                    'refresh_current_loopback_runner_liveness' => function ( $ttl ) {
                        $this->refresh_current_loopback_runner_liveness( $ttl );
                    },
                )
            );
        } elseif ( $this->actor_runtime_lease_keeper && method_exists( $this->actor_runtime_lease_keeper, 'set_control_plane' ) ) {
            $this->actor_runtime_lease_keeper->set_control_plane( $this->control_plane );
        }

        return $this->actor_runtime_lease_keeper;
    }

    private function get_pending_runtime_state() {
        if ( null === $this->pending_runtime_state && class_exists( 'Niwa_AI_Admin_Autonomy_Pending_Runtime_State' ) ) {
            $this->pending_runtime_state = new Niwa_AI_Admin_Autonomy_Pending_Runtime_State(
                $this->control_plane,
                $this->chat_agent,
                $this->telegram,
                Niwa_AI_Constants::OPT_AUTONOMY_TG_ALBUM_INDEX,
                Niwa_AI_Constants::OPT_AUTONOMY_TG_TEXT_FRAG_INDEX,
                Niwa_AI_Constants::OPT_AUTONOMY_WA_TEXT_BATCH_INDEX,
                array(
                    'reap_dead_actor_runtime_state' => function () {
                        $this->reap_dead_actor_runtime_state();
                    },
                )
            );
        } elseif ( $this->pending_runtime_state && method_exists( $this->pending_runtime_state, 'set_dependencies' ) ) {
            $this->pending_runtime_state->set_dependencies( $this->control_plane, $this->chat_agent, $this->telegram );
        }

        return $this->pending_runtime_state;
    }

    private function get_admin_request_helper() {
        if ( null === $this->admin_request_helper && class_exists( 'Niwa_AI_Admin_Autonomy_Admin_Request_Helper' ) ) {
            $this->admin_request_helper = new Niwa_AI_Admin_Autonomy_Admin_Request_Helper();
        }

        return $this->admin_request_helper;
    }

    private function get_outgoing_reply_formatter() {
        if ( null === $this->outgoing_reply_formatter && class_exists( 'Niwa_AI_Admin_Autonomy_Outgoing_Reply_Formatter' ) ) {
            $this->outgoing_reply_formatter = new Niwa_AI_Admin_Autonomy_Outgoing_Reply_Formatter( $this->chat_agent );
        } elseif ( $this->outgoing_reply_formatter && method_exists( $this->outgoing_reply_formatter, 'set_chat_agent' ) ) {
            $this->outgoing_reply_formatter->set_chat_agent( $this->chat_agent );
        }

        return $this->outgoing_reply_formatter;
    }

    private function get_browser_message_context_helper() {
        if ( null === $this->browser_message_context && class_exists( 'Niwa_AI_Admin_Autonomy_Browser_Message_Context' ) ) {
            $this->browser_message_context = new Niwa_AI_Admin_Autonomy_Browser_Message_Context();
        }

        return $this->browser_message_context;
    }

    private function get_browser_session_helper() {
        if ( null === $this->browser_session && class_exists( 'Niwa_AI_Admin_Autonomy_Browser_Session' ) ) {
            $this->browser_session = new Niwa_AI_Admin_Autonomy_Browser_Session(
                $this->telegram,
                $this->conversation_store,
                array(
                    'get_founder_session_id' => function ( $chat_id ) {
                        return $this->get_founder_session_id( $chat_id );
                    },
                )
            );
        }

        return $this->browser_session;
    }

    private function get_turn_progress_callback_builder() {
        if ( null === $this->turn_progress_callback && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Progress_Callback' ) ) {
            $this->turn_progress_callback = new Niwa_AI_Admin_Autonomy_Turn_Progress_Callback(
                array(
                    'keep_typing_feedback'                    => function ( $chat_id ) {
                        $this->keep_typing_feedback( $chat_id );
                    },
                    'get_turn_stream_state'                   => function ( $chat_id, $message_id ) {
                        return $this->get_turn_stream_state( $chat_id, $message_id );
                    },
                    'consume_telegram_quote_current_directive' => function ( $text ) {
                        return $this->consume_telegram_quote_current_directive( $text );
                    },
                    'text_length'                             => function ( $text ) {
                        return $this->text_length( $text );
                    },
                    'maybe_flush_turn_stream_buffer'          => function ( $state, $force ) {
                        return $this->maybe_flush_turn_stream_buffer( $state, $force );
                    },
                    'delete_all_turn_tool_status_messages'    => function ( $state, $reason ) {
                        return $this->delete_all_turn_tool_status_messages( $state, $reason );
                    },
                    'maybe_send_turn_tool_status'             => function ( $state, $phase, $payload ) {
                        return $this->maybe_send_turn_tool_status( $state, $phase, $payload );
                    },
                    'delete_turn_tool_status_message'         => function ( $state, $payload, $reason ) {
                        return $this->delete_turn_tool_status_message( $state, $payload, $reason );
                    },
                    'update_turn_stream_state'                => function ( $chat_id, $message_id, $state ) {
                        $this->update_turn_stream_state( $chat_id, $message_id, $state );
                    },
                    'debug_logger'                            => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->turn_progress_callback;
    }

    private function get_post_reply_memory_queue() {
        if ( null === $this->post_reply_memory_queue && class_exists( 'Niwa_AI_Admin_Autonomy_Post_Reply_Memory_Queue' ) ) {
            $this->post_reply_memory_queue = new Niwa_AI_Admin_Autonomy_Post_Reply_Memory_Queue(
                $this->chat_agent,
                array(
                    'compact_queued_context' => function ( $context ) {
                        return $this->compact_queued_context( $context );
                    },
                    'acquire_lock'           => function ( $name, $ttl ) {
                        return $this->acquire_lock( $name, $ttl );
                    },
                    'release_lock'           => function ( $name ) {
                        $this->release_lock( $name );
                    },
                    'debug_logger'           => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                ),
                array(
                    'option_name' => self::POST_REPLY_MEMORY_QUEUE_OPTION,
                    'max_queue'   => self::MAX_POST_REPLY_MEMORY_QUEUE,
                    'max_age'     => self::POST_REPLY_MEMORY_MAX_AGE,
                )
            );
        }

        return $this->post_reply_memory_queue;
    }

    private function get_background_tick_runner() {
        if ( null === $this->background_tick_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Background_Tick_Runner' ) ) {
            $this->background_tick_runner = new Niwa_AI_Admin_Autonomy_Background_Tick_Runner(
                array(
                    'format_exception' => function ( Throwable $e ) {
                        return $this->format_runtime_exception_message( $e );
                    },
                    'debug_logger'     => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->background_tick_runner;
    }

    private function get_actor_message_preparer() {
        if ( null === $this->actor_message_preparer && class_exists( 'Niwa_AI_Admin_Autonomy_Actor_Message_Preparer' ) ) {
            $this->actor_message_preparer = new Niwa_AI_Admin_Autonomy_Actor_Message_Preparer(
                $this->telegram,
                array(
                    'apply_stock_manager_media_policy' => function ( $message ) {
                        $preprocessor = $this->get_telegram_message_preprocessor();
                        return $preprocessor ? $preprocessor->apply_stock_manager_media_policy( $message ) : $message;
                    },
                    'find_first_skill_document_ref'    => function ( $message ) {
                        return $this->find_first_skill_document_ref( $message );
                    },
                    'find_first_spreadsheet_ref'       => function ( $message ) {
                        return $this->find_first_spreadsheet_ref( $message );
                    },
                    'find_first_voice_ref'             => function ( $message ) {
                        return $this->find_first_voice_ref( $message );
                    },
                    'transcribe_voice_message'         => function ( $ref ) {
                        return $this->transcribe_voice_message( $ref );
                    },
                    'send_typing_indicator'            => function ( $chat_id ) {
                        return $this->send_typing_indicator( $chat_id );
                    },
                    'send_channel_message'             => function ( $channel, $chat_id, $reply, $reply_to_id = 0, $reply_markup = array(), $context = array() ) {
                        return $this->send_channel_message( $channel, $chat_id, $reply, $reply_to_id, $reply_markup, $context );
                    },
                    'remember_exchange'                => function ( $chat_id, $user_text, $reply ) {
                        $this->remember_exchange( $chat_id, $user_text, $reply );
                    },
                )
            );
        }

        return $this->actor_message_preparer;
    }

    private function get_session_actor_message_runner() {
        if ( null === $this->session_actor_message_runner && class_exists( 'Niwa_AI_Admin_Autonomy_Session_Actor_Message_Runner' ) ) {
            $this->session_actor_message_runner = new Niwa_AI_Admin_Autonomy_Session_Actor_Message_Runner(
                $this->control_plane,
                array(
                    'begin_typing_feedback'               => function ( $chat_id ) {
                        $this->begin_typing_feedback( $chat_id );
                    },
					'recover_admin_ui_delivery'           => function ( $session_id, $entry ) {
						return $this->recover_admin_ui_actor_delivery( $session_id, $entry );
					},
					'refresh_admin_ui_job'                => function ( $job_id, $session_id, $actor_id ) {
						return $this->transition_admin_ui_chat_job(
							array(
								'type'       => 'progress',
								'job_id'     => $job_id,
								'session_id' => $session_id,
								'actor_id'   => $actor_id,
							)
						);
					},
                    'prepare_actor_message_for_execution' => function ( $message ) {
                        return $this->prepare_actor_message_for_execution( $message );
                    },
                    'build_message_context'               => function ( $message ) {
                        return $this->build_message_context( $message );
                    },
                    'is_media_only_message'               => function ( $message ) {
                        return $this->is_media_only_message( $message );
                    },
                    'prepare_media_only_message'          => function ( $message, $context ) {
                        return $this->prepare_media_only_message( $message, $context );
                    },
                    'compact_queued_context'              => function ( $context ) {
                        return $this->compact_queued_context( $context );
                    },
                    'turn_worker_time_limit'              => function () {
                        return self::get_turn_worker_time_limit();
                    },
                    'turn_execution_soft_deadline_buffer' => function () {
                        return self::TURN_EXECUTION_SOFT_DEADLINE_BUFFER;
                    },
                    'refresh_actor_message_lease'         => function ( $session_id, $message_id ) {
                        return $this->refresh_actor_message_lease( $session_id, $message_id );
                    },
                    'wrap_actor_message_lease_callback'   => function ( $session_id, $message_id, $progress_callback ) {
                        return $this->wrap_actor_message_lease_callback( $session_id, $message_id, $progress_callback );
                    },
                    'clear_last_soft_deadline_result'     => function () {
                        $this->last_soft_deadline_result = null;
                    },
                    'execute_founder_message'             => function ( $message, $context, $callback_acknowledged ) {
                        return $this->execute_founder_message( $message, $context, $callback_acknowledged );
                    },
                    'get_last_soft_deadline_result'       => function () {
                        return $this->last_soft_deadline_result;
                    },
                    'keep_typing_feedback'                => function ( $chat_id ) {
                        $this->keep_typing_feedback( $chat_id );
                    },
                    'end_typing_feedback'                 => function ( $chat_id ) {
                        $this->end_typing_feedback( $chat_id );
                    },
                    'debug_logger'                        => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->session_actor_message_runner;
    }

    private function get_founder_message_executor() {
        if ( null === $this->founder_message_executor && class_exists( 'Niwa_AI_Admin_Autonomy_Founder_Message_Executor' ) ) {
            $this->founder_message_executor = new Niwa_AI_Admin_Autonomy_Founder_Message_Executor(
                $this->telegram,
                $this->conversation_store,
                array(
                    'maybe_handle_founder_onboarding_message' => function ( $message, $context ) {
                        return $this->maybe_handle_founder_onboarding_message( $message, $context );
                    },
                    'process_command'                         => function ( $text, $context ) {
                        return $this->process_command( $text, $context );
                    },
                    'format_runtime_exception_message'        => function ( Throwable $e ) {
                        return $this->format_runtime_exception_message( $e );
                    },
                    'build_remove_keyboard'                   => function () {
                        return $this->build_remove_keyboard();
                    },
                    'text_length'                             => function ( $text ) {
                        return $this->text_length( $text );
                    },
                    'normalize_command_text'                  => function ( $text ) {
                        return $this->normalize_command_text( $text );
                    },
                    'is_founder_onboarding_complete'          => function () {
                        return ! $this->chat_agent || ! method_exists( $this->chat_agent, 'is_founder_onboarding_complete' )
                            ? true
                            : $this->chat_agent->is_founder_onboarding_complete();
                    },
                    'prepend_founder_intro'                   => function ( $reply ) {
                        return $this->prepend_founder_intro( $reply );
                    },
                    'normalize_outgoing_reply'                => function ( $reply ) {
                        return $this->normalize_outgoing_reply( $reply );
                    },
                    'finalize_turn_stream_feedback'           => function ( $context, $reply ) {
                        return $this->finalize_turn_stream_feedback( $context, $reply );
                    },
                    'consume_telegram_quote_current_directive' => function ( $text ) {
                        return $this->consume_telegram_quote_current_directive( $text );
                    },
                    'send_channel_message'                    => function ( $channel, $chat_id, $reply, $reply_to_id, $reply_markup, $context ) {
                        return $this->send_channel_message( $channel, $chat_id, $reply, $reply_to_id, $reply_markup, $context );
                    },
                    'get_founder_session_id'                  => function ( $chat_id ) {
                        return $this->get_founder_session_id( $chat_id );
                    },
					'fail_admin_ui_job'                      => function ( $context, $error ) {
						$context = is_array( $context ) ? $context : array();
						if ( 'admin_ui' !== sanitize_key( (string) ( $context['channel'] ?? '' ) ) ) {
							return false;
						}
						return $this->admin_ui_chat_job_transition(
							array(
								'type'       => 'fail',
								'job_id'     => $context['admin_ui_job_id'] ?? '',
								'session_id' => $context['session_id'] ?? '',
								'actor_id'   => $context['actor_message_id'] ?? '',
								'error'      => $error,
							)
						);
					},
                    'remember_exchange'                       => function ( $chat_id, $user_text, $reply, $context = array() ) {
                        $this->remember_exchange( $chat_id, $user_text, $reply, $context );
                    },
                    'enqueue_post_reply_memory_capture'       => function ( $session_id, $user_text, $reply, $context ) {
                        $this->enqueue_post_reply_memory_capture( $session_id, $user_text, $reply, $context );
                    },
                    'debug_logger'                            => function ( $event, $context = array() ) {
                        $this->log_turn_debug( $event, $context );
                    },
                )
            );
        }

        return $this->founder_message_executor;
    }

    private function get_founder_identity_helper() {
        if ( null === $this->founder_identity_helper && class_exists( 'Niwa_AI_Admin_Autonomy_Founder_Identity_Helper' ) ) {
            $this->founder_identity_helper = new Niwa_AI_Admin_Autonomy_Founder_Identity_Helper( $this->chat_agent );
        } elseif ( $this->founder_identity_helper && method_exists( $this->founder_identity_helper, 'set_chat_agent' ) ) {
            $this->founder_identity_helper->set_chat_agent( $this->chat_agent );
        }

        return $this->founder_identity_helper;
    }

    public function __construct( $store, $executor, $telegram, $chat_agent = null, $conversation_store = null, $scheduler = null, $session_state = null, $actor_runtime = null, $control_plane = null, $whatsapp = null ) {
        $this->store              = $store;
        $this->executor           = $executor;
        $this->telegram           = $telegram;
        $this->whatsapp           = $whatsapp;
        $this->chat_agent         = $chat_agent;
        $this->conversation_store = $conversation_store;
        $this->scheduler          = $scheduler;
        $this->session_state      = $session_state;
        $this->actor_runtime      = $actor_runtime;
        $this->control_plane      = $control_plane;
    }

    /**
     * Return the effective per-turn worker time limit (seconds).
     *
     * On LiteSpeed/LSAPI, set_time_limit() and ini_set('max_execution_time')
     * are ignored by the SAPI and the worker is hard-killed around 225s. We
     * clamp the soft budget to TURN_WORKER_TIME_LIMIT_LITESPEED so the
     * in-process soft_deadline triggers a graceful continuation snapshot
     * before the SAPI hard kill, instead of leaving streams in a locked
     * "stuck" state with no recovery signal.
     *
     * @return int Seconds.
     */
    public static function get_turn_worker_time_limit() {
        $sapi = function_exists( 'php_sapi_name' ) ? (string) php_sapi_name() : '';
        if ( '' !== $sapi && false !== stripos( $sapi, 'litespeed' ) ) {
            return self::TURN_WORKER_TIME_LIMIT_LITESPEED;
        }
        return self::TURN_WORKER_TIME_LIMIT;
    }

    private function log_turn_debug( $event, $context = array() ) {
        $logger = $this->get_runtime_trace_logger();
        if ( $logger ) {
            $logger->log( $event, is_array( $context ) ? $context : array( 'value' => $context ) );
        }
    }

    private function get_runtime_trace_logger() {
        if ( null === $this->runtime_trace_logger && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger' ) ) {
            $this->runtime_trace_logger = new Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger();
        }

        return $this->runtime_trace_logger;
    }

    private function raise_loopback_runner_runtime_limit() {
        $utils = $this->get_runtime_utils();
        if ( $utils ) {
            $utils->raise_loopback_runner_runtime_limit();
        }
    }

    public function register_hooks() {
        add_action( 'init', array( $this, 'maybe_boot_loopback_runner' ) );
        add_action( 'rest_api_init', array( $this, 'register_telegram_webhook_route' ) );
        add_action( 'rest_api_init', array( $this, 'register_whatsapp_webhook_route' ) );
        add_action( 'niwa_ai_admin_autonomy_poll_telegram', array( $this, 'poll_telegram_updates' ) );
        add_action( 'niwa_ai_admin_autonomy_process_scheduled_tasks', array( $this, 'process_scheduled_tasks' ) );
        add_action( 'niwa_ai_admin_autonomy_process_scheduled_job', array( $this, 'process_targeted_scheduled_job' ), 10, 2 );
        add_action( 'niwa_ai_admin_autonomy_restart_polling_runtime', array( $this, 'restart_polling_runtime' ), 10, 1 );
        add_action( self::POST_REPLY_MEMORY_HOOK, array( $this, 'process_post_reply_memory_queue' ) );
		add_action( Niwa_AI_Admin_Autonomy_Post_Reply_Memory_Queue::RETRY_HOOK, array( $this, 'retry_post_reply_memory_enqueue' ) );
        add_action( self::BACKGROUND_TICK_HOOK, array( $this, 'run_background_tick' ) );
		add_action( 'niwa_ai_retry_admin_ui_chat_delivery', array( $this, 'retry_admin_ui_chat_delivery' ), 10, 1 );
        add_action( self::WHATSAPP_GATEWAY_WATCHDOG_HOOK, array( $this, 'run_whatsapp_gateway_watchdog' ) );
        add_action( self::WHATSAPP_TEXT_BATCH_FLUSH_HOOK, array( $this, 'flush_whatsapp_text_batch' ) );
        add_action( self::VENDOR_INTEGRITY_HOOK, array( $this, 'run_vendor_integrity_check' ) );
        add_action( 'niwa_ai_admin_autonomy_run_workspace_heartbeat_now', array( $this, 'run_workspace_heartbeat_diagnostic' ) );
        add_action( 'niwa_ai_admin_autonomy_run_daily_report_now', array( $this, 'run_daily_report_diagnostic' ) );
        add_action( 'niwa_ai_admin_autonomy_wake_background', array( $this, 'wake_background_runtime' ), 10, 1 );
        add_action( 'admin_post_niwa_ai_admin_autonomy_poll_now', array( $this, 'handle_poll_now' ) );
        add_action( 'admin_post_niwa_ai_admin_autonomy_runner', array( $this, 'handle_loopback_runner' ) );
        add_action( 'admin_post_nopriv_niwa_ai_admin_autonomy_runner', array( $this, 'handle_loopback_runner' ) );
        add_action( 'admin_post_niwa_ai_admin_autonomy_typing', array( $this, 'handle_typing_runner' ) );
        add_action( 'admin_post_nopriv_niwa_ai_admin_autonomy_typing', array( $this, 'handle_typing_runner' ) );
        add_filter( 'niwa_ai_conversation_history_limit', array( $this, 'filter_founder_history_limit' ), 10, 2 );
        add_filter( 'niwa_ai_conversation_ttl', array( $this, 'filter_founder_history_ttl' ), 10, 2 );
    }

    public function run_whatsapp_gateway_watchdog() {
        if ( ! $this->whatsapp || ! method_exists( $this->whatsapp, 'get_gateway_status' ) ) {
            return array(
                'success' => false,
                'error'   => 'whatsapp_unavailable',
            );
        }

        $status = (array) $this->whatsapp->get_gateway_status( true );
        return array(
            'success' => true,
            'status'  => isset( $status['status'] ) ? (string) $status['status'] : '',
            'running' => ! empty( $status['running'] ),
            'pid'     => isset( $status['pid'] ) ? intval( $status['pid'] ) : 0,
            'restarted' => ! empty( $status['autoRestarted'] ),
        );
    }

    public function register_telegram_webhook_route() {
        register_rest_route(
            'niwa-ai/v1',
            '/telegram/webhook',
            array(
                'methods'             => 'POST',
                'callback'            => array( $this, 'handle_telegram_webhook_request' ),
                'permission_callback' => '__return_true',
            )
        );
    }

    public function handle_telegram_webhook_request( WP_REST_Request $request ) {
        $ingress = $this->get_telegram_ingress();
        if ( $ingress ) {
            return $ingress->handle_webhook_request( $request );
        }

        return new WP_REST_Response(
            array(
                'success' => false,
                'error'   => 'telegram_ingress_unavailable',
            ),
            500
        );
    }

    public function register_whatsapp_webhook_route() {
        register_rest_route(
            'niwa-ai/v1',
            '/whatsapp/webhook',
            array(
                array(
                    'methods'             => 'GET',
                    'callback'            => array( $this, 'handle_whatsapp_webhook_request' ),
                    'permission_callback' => function () {
                        return current_user_can( 'manage_options' );
                    },
                ),
                array(
                    'methods'             => 'POST',
                    'callback'            => array( $this, 'handle_whatsapp_webhook_request' ),
                    'permission_callback' => '__return_true',
                ),
            )
        );
    }

    public function handle_whatsapp_webhook_request( WP_REST_Request $request ) {
        if ( ! $this->whatsapp || ! class_exists( 'Niwa_AI_Admin_Autonomy_WhatsApp' ) ) {
            return new WP_REST_Response(
                array(
                    'success' => false,
                    'error'   => 'whatsapp_unavailable',
                ),
                500
            );
        }

		if ( 'GET' === strtoupper( (string) $request->get_method() ) ) {
			$status = $this->whatsapp->get_gateway_status();
			unset( $status['qrDataUrl'], $status['state_dir'], $status['pid'], $status['selfJid'] );
			return new WP_REST_Response(
				array(
					'success' => true,
					'channel' => 'whatsapp',
					'status'  => array(
						'configured' => ! empty( $status['configured'] ),
						'mode'       => isset( $status['mode'] ) ? (string) $status['mode'] : 'auto',
						'running'    => ! empty( $status['running'] ),
						'state'      => isset( $status['status'] ) ? (string) $status['status'] : 'not_started',
						'updatedAt'  => isset( $status['updatedAt'] ) ? (string) $status['updatedAt'] : '',
					),
				),
				200
			);
		}

        $payload = $this->whatsapp->handle_gateway_webhook( $request );
        if ( $payload instanceof WP_REST_Response ) {
            return $payload;
        }

        $messages                 = $this->whatsapp->extract_messages_from_gateway_payload( is_array( $payload ) ? $payload : array() );
        $results                  = array();
        $needs_whatsapp_batch_run = false;
        foreach ( $messages as $message ) {
            $result = $this->process_extracted_whatsapp_message( $message, true );
            if ( is_array( $result ) && 'whatsapp_text_batch' === (string) ( $result['buffered'] ?? '' ) ) {
                $needs_whatsapp_batch_run = true;
            }
            $results[] = $result;
        }

        $batch_runner_requested = false;
        if ( $needs_whatsapp_batch_run ) {
            $this->dispatch_loopback_runner( 'webhook-buffer', $this->get_runner_liveness_ttl() );
            $batch_runner_requested = true;
        }

        return new WP_REST_Response(
            array(
                'success'       => true,
                'webhook'       => true,
                'channel'       => 'whatsapp',
                'message_count' => count( $messages ),
                'results'       => $results,
                'actor_runtime' => array(
                    'queued_only'            => true,
                    'batch_runner_requested' => $batch_runner_requested,
                ),
            ),
            200
        );
    }

    public function process_telegram_webhook_update( $update ) {
        $ingress = $this->get_telegram_ingress();
        if ( $ingress ) {
            return $ingress->process_webhook_update( $update );
        }

        return array(
            'success' => false,
            'webhook' => true,
            'error'   => 'telegram_ingress_unavailable',
        );
    }

    public function cleanup_legacy_turn_runtime() {
        $legacy_options = array(
            Niwa_AI_Constants::OPT_ADMIN_TURN_QUEUE,
            Niwa_AI_Constants::OPT_ADMIN_STEERING_BUFFER,
            'niwa_ai_inbound_email_cron_scheduled',
            'niwa_ai_inbound_email_cron_next_run_utc',
            'niwa_ai_inbound_email_approval_queue_stub_available',
            'niwa_ai_inbound_email_last_duplicate_messages',
            'niwa_ai_inbound_email_last_queue_enqueue_at',
            'niwa_ai_operative_heartbeat_scheduler_override',
        );
        foreach ( $legacy_options as $option_name ) {
            delete_option( $option_name );
        }
        if ( function_exists( 'wp_clear_scheduled_hook' ) ) {
            wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_process_turn_queue' );
        }
        if ( $this->control_plane && method_exists( $this->control_plane, 'clear_legacy_session_locks' ) ) {
            $this->control_plane->clear_legacy_session_locks();
        }
    }

    public function filter_founder_history_limit( $limit, $session_id ) {
        $helper = $this->get_founder_identity_helper();
        return $helper ? $helper->filter_history_limit( $limit, $session_id ) : $limit;
    }

    public function filter_founder_history_ttl( $ttl, $session_id ) {
        $helper = $this->get_founder_identity_helper();
        return $helper ? $helper->filter_history_ttl( $ttl, $session_id ) : $ttl;
    }

    public function get_browser_session_id( $user_id = 0 ) {
        $helper = $this->get_browser_session_helper();
        return $helper ? $helper->get_session_id( $user_id ) : 'admin-ui-1';
    }

    private function get_browser_shared_founder_chat_id() {
        $helper = $this->get_browser_session_helper();
        return $helper ? $helper->get_shared_founder_chat_id() : '';
    }


	    public function get_browser_history( $user_id = 0 ) {
	        $helper = $this->get_browser_session_helper();
	        return $helper ? $helper->get_history( $user_id ) : array();
	    }

	    public function get_browser_display_history( $user_id = 0, $filters = array() ) {
	        $helper = $this->get_browser_session_helper();
	        return $helper ? $helper->get_display_history( $user_id, $filters ) : array();
	    }

    public function enqueue_browser_message( $prompt, $user_id = 0, $request_id = '', $job_id = '', $sequence = 0 ) {
        $prompt     = trim( (string) $prompt );
        $user_id    = max( 0, intval( $user_id ) );
        $request_id = substr( preg_replace( '/[^a-zA-Z0-9_\-:.]/', '', trim( (string) $request_id ) ), 0, 96 );
        $job_id     = substr( preg_replace( '/[^a-zA-Z0-9_\-]/', '', trim( (string) $job_id ) ), 0, 80 );
		$sequence   = max( 0, intval( $sequence ) );
        $session_id = $this->get_browser_session_id( $user_id );

        if ( '' === $prompt || '' === $job_id || '' === $session_id ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_actor_missing_identity',
            );
        }
        if ( ! $this->actor_runtime || ! method_exists( $this->actor_runtime, 'enqueue_message' ) ) {
            return array(
                'success' => false,
                'error'   => 'actor_runtime_unavailable',
            );
        }

        $actor_id = 'actor_admin_ui_' . md5( $session_id . '|' . $job_id );
        if ( ! $this->transition_admin_ui_chat_job(
            array(
                'type'       => 'reserve',
                'job_id'     => $job_id,
                'session_id' => $session_id,
                'request_id' => $request_id,
                'actor_id'   => $actor_id,
            )
        ) ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_actor_job_bind_failed',
            );
        }

        $context = $this->build_browser_actor_context( $session_id, $user_id );
        $context = is_array( $context ) ? $context : array();
        $context['channel']             = 'admin_ui';
        $context['session_id']          = $session_id;
        $context['source']              = 'admin_ui_job';
        $context['current_prompt']      = $prompt;
        $context['request_id']          = $request_id;
        $context['admin_ui_request_id'] = $request_id;
        $context['admin_ui_job_id']     = $job_id;
		$context['admin_ui_sequence']   = $sequence;

        $context_payload = $this->compact_queued_context( $context );
        $context_payload = is_array( $context_payload ) ? $context_payload : array();
        foreach ( array(
            'session_id',
            'admin_user_id',
            'admin_user_login',
            'admin_user_email',
            'admin_user_name',
            'ui_locale',
            'ui_language',
            'conversation_language',
            'shared_founder_session',
            'channel',
            'source',
            'current_prompt',
            'request_id',
            'admin_ui_request_id',
            'admin_ui_job_id',
			'admin_ui_sequence',
        ) as $key ) {
            if ( array_key_exists( $key, $context ) ) {
                $context_payload[ $key ] = $context[ $key ];
            }
        }

        $delivery_chat_id = isset( $context['chat_id'] ) ? trim( (string) $context['chat_id'] ) : '';
        if ( '' === $delivery_chat_id ) {
            $delivery_chat_id = $session_id;
        }

        $raw_id = 'admin-ui-job:' . $job_id;

        $entry = (array) $this->actor_runtime->enqueue_message(
            array(
                'source'           => 'admin_ui_job',
                'channel'          => 'admin_ui',
                'chat_id'          => $delivery_chat_id,
                'message_id'       => 0,
                'update_id'        => 0,
                'text'             => $prompt,
                'text_only'        => $prompt,
                'caption'          => '',
                'date'             => time(),
                'raw_message_id'   => $raw_id,
                'user_role'        => 'admin',
                'has_user_text'    => true,
                'media_refs'       => array(),
                'reply_media_refs' => array(),
                'context_payload'  => $context_payload,
            ),
            true,
            $actor_id
        );
        if ( ! empty( $entry['duplicate_completed'] ) ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_actor_completed_without_terminal',
            );
        }
        if ( empty( $entry['id'] ) || ! hash_equals( $actor_id, (string) $entry['id'] ) ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_actor_enqueue_failed',
            );
        }
        if ( ! $this->transition_admin_ui_chat_job(
            array(
                'type'       => 'bind',
                'job_id'     => $job_id,
                'session_id' => $session_id,
                'request_id' => $request_id,
                'actor_id'   => $actor_id,
            )
        ) ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_actor_job_confirm_failed',
            );
        }

        $dispatch = (array) $this->dispatch_loopback_runner( 'admin-ui', $this->get_runner_liveness_ttl() );
        if ( empty( $dispatch['success'] ) && empty( $dispatch['wake_pending'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $dispatch['error'] ) ? (string) $dispatch['error'] : 'admin_ui_actor_dispatch_failed',
            );
        }

        return array(
            'success'                        => true,
            'session_id'                     => $session_id,
            'continuation_pending'            => true,
            'admin_ui_continuation_queued'    => true,
            'admin_ui_continuation_actor_id' => $actor_id,
        );
    }


    private function build_browser_actor_context( $session_id, $user_id = 0 ) {
        $helper = $this->get_browser_message_context_helper();
        return $helper && method_exists( $helper, 'build_actor_context' )
            ? $helper->build_actor_context( $session_id, $user_id, $this->get_browser_shared_founder_chat_id() )
            : array();
    }


    public function poll_telegram_updates() {
        $runner = $this->get_telegram_poll_runner();
        if ( ! $runner ) {
            return array(
                'success' => false,
                'errors'  => array( 'telegram_poll_runner_unavailable' ),
            );
        }

        return $runner->run();
    }

    private function process_extracted_telegram_message( $message, $wake_runner = true ) {
        $message = is_array( $message ) ? $message : array();
        $this->log_turn_debug(
            'telegram_message_extracted',
            array(
                'channel'      => 'telegram',
                'chat_id'      => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
                'message_id'   => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
                'update_id'    => isset( $message['update_id'] ) ? intval( $message['update_id'] ) : 0,
                'success'      => ! empty( $message['success'] ),
                'kind'         => isset( $message['kind'] ) ? (string) $message['kind'] : '',
                'wake_runner'  => ! empty( $wake_runner ),
                'text_preview' => isset( $message['text'] ) ? (string) $message['text'] : '',
            )
        );
        $album_buffer = $this->get_album_buffer();

        if ( $album_buffer && ! empty( $message['success'] ) ) {
            if ( ! empty( $message['media_group_id'] ) ) {
                $album_buffer->buffer_album_part( $message );
                $this->log_telegram_transport_buffer_event( 'telegram_album_buffered', $message );
                if ( ! empty( $message['update_id'] ) ) {
                    $this->telegram->remember_processed_update( $message['update_id'] );
                }
                return array(
                    'success'  => true,
                    'ignored'  => true,
                    'buffered' => 'album',
                );
            }

            if ( ! empty( $message['text_only'] ) ) {
                foreach ( $album_buffer->flush_ready_text_fragments() as $combined ) {
                    if ( null !== $combined ) {
                        $this->handle_incoming_message( $combined, false );
                    }
                }
            }

            if ( $album_buffer->buffer_text_fragment( $message ) ) {
                $this->log_telegram_transport_buffer_event( 'telegram_text_buffered', $message );
                if ( ! empty( $message['chat_id'] ) ) {
                    $this->begin_typing_feedback( $message['chat_id'] );
                }
                if ( ! empty( $message['update_id'] ) ) {
                    $this->telegram->remember_processed_update( $message['update_id'] );
                }
                return array(
                    'success'  => true,
                    'ignored'  => true,
                    'buffered' => 'text_fragment',
                );
            }
        }

        return $this->handle_incoming_message( $message, $wake_runner );
    }

    private function process_extracted_whatsapp_message( $message, $wake_runner = true ) {
        $message = is_array( $message ) ? $message : array();
        if ( ! empty( $message['success'] ) ) {
            $message['channel'] = 'whatsapp';
        }

        $batcher = $this->get_whatsapp_text_batcher();
        if ( $batcher ) {
            foreach ( $batcher->flush_ready_messages() as $combined ) {
                if ( null !== $combined ) {
                    $this->log_whatsapp_text_batch_flushed( $combined );
                    $this->handle_incoming_message( $combined, true );
                }
            }

            if ( $batcher->buffer_message( $message ) ) {
                if ( ! empty( $message['chat_id'] ) ) {
                    $this->begin_typing_feedback( $message['chat_id'] );
                }
                $this->schedule_whatsapp_text_batch_flush();
                $this->log_turn_debug(
                    'whatsapp_text_batch_buffered',
                    array(
                        'chat_id'    => isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '',
                        'message_id' => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
                        'text_len'   => strlen( (string) ( $message['text_only'] ?? '' ) ),
                    )
                );
                return array(
                    'success'  => true,
                    'ignored'  => true,
                    'buffered' => 'whatsapp_text_batch',
                );
            }
        }

        return $this->handle_incoming_message( $message, $wake_runner );
    }

    public function flush_whatsapp_text_batch() {
        $batcher = $this->get_whatsapp_text_batcher();
        if ( ! $batcher ) {
            return array(
                'success' => false,
                'error'   => 'whatsapp_text_batcher_unavailable',
            );
        }

        $processed = 0;
        foreach ( $batcher->flush_ready_messages() as $combined ) {
            if ( null !== $combined ) {
                $this->log_whatsapp_text_batch_flushed( $combined );
                $this->handle_incoming_message( $combined, true );
                $processed++;
            }
        }
        if ( $batcher->has_pending() ) {
            $this->schedule_whatsapp_text_batch_flush();
        } elseif ( function_exists( 'wp_clear_scheduled_hook' ) ) {
            wp_clear_scheduled_hook( self::WHATSAPP_TEXT_BATCH_FLUSH_HOOK );
        }

        return array(
            'success'   => true,
            'processed' => $processed,
            'pending'   => $batcher->has_pending(),
        );
    }

    private function schedule_whatsapp_text_batch_flush() {
        $batcher = $this->get_whatsapp_text_batcher();
        $delay   = $batcher ? $batcher->next_flush_delay() : 5;
        $delay   = max( 1, intval( $delay ) );
        if ( function_exists( 'wp_schedule_single_event' ) && function_exists( 'wp_next_scheduled' ) ) {
            $next_run = wp_next_scheduled( self::WHATSAPP_TEXT_BATCH_FLUSH_HOOK );
            if ( $next_run && intval( $next_run ) <= time() && function_exists( 'wp_clear_scheduled_hook' ) ) {
                wp_clear_scheduled_hook( self::WHATSAPP_TEXT_BATCH_FLUSH_HOOK );
                $next_run = false;
            }
            if ( ! $next_run ) {
                $scheduled = wp_schedule_single_event( time() + $delay, self::WHATSAPP_TEXT_BATCH_FLUSH_HOOK );
                $this->log_turn_debug(
                    'whatsapp_text_batch_flush_scheduled',
                    array(
                        'delay'     => $delay,
                        'scheduled' => false !== $scheduled,
                    )
                );
            }
        }
    }

    private function log_whatsapp_text_batch_flushed( $combined ) {
        $combined = is_array( $combined ) ? $combined : array();
        $this->log_turn_debug(
            'whatsapp_text_batch_flushed',
            array(
                'chat_id'         => isset( $combined['chat_id'] ) ? (string) $combined['chat_id'] : '',
                'message_id'      => isset( $combined['message_id'] ) ? intval( $combined['message_id'] ) : 0,
                'batch_size'      => isset( $combined['batch_size'] ) ? intval( $combined['batch_size'] ) : 0,
                'raw_message_ids' => isset( $combined['raw_message_ids'] ) && is_array( $combined['raw_message_ids'] ) ? array_values( $combined['raw_message_ids'] ) : array(),
            )
        );
    }

    private function log_telegram_transport_buffer_event( $event, $message, $extra = array() ) {
        $logger = $this->get_telegram_buffer_event_logger();
        if ( $logger ) {
            $logger->log( $event, $message, $extra );
        }
    }

    private function has_pending_actor_messages() {
        $state = $this->get_pending_runtime_state();
        return $state ? $state->has_pending_actor_messages() : false;
    }

    private function has_active_actor_messages() {
        $state = $this->get_pending_runtime_state();
        return $state ? $state->has_active_actor_messages() : false;
    }

    private function has_pending_background_subagents() {
        $state = $this->get_pending_runtime_state();
        return $state ? $state->has_pending_background_subagents() : false;
    }

    private function process_actor_runtime( $limit = 3 ) {
        $this->log_turn_debug(
            'actor_runtime_process_start',
            array(
                'limit' => intval( $limit ),
            )
        );
        $this->reap_dead_actor_runtime_state();

        if ( $this->actor_runtime && method_exists( $this->actor_runtime, 'process_runtime' ) ) {
            $result = (array) $this->actor_runtime->process_runtime( $limit );
            $this->log_turn_debug( 'actor_runtime_process_end', $result );
            return $result;
        }

        $result = array(
            'success'            => true,
            'processed_sessions' => 0,
            'processed_messages' => 0,
            'pending'            => false,
            'errors'             => array(),
        );
        $this->log_turn_debug( 'actor_runtime_process_end', $result );
        return $result;
    }

    public function enqueue_daily_report_actor_message( $payload ) {
        if ( ! $this->actor_runtime || ! method_exists( $this->actor_runtime, 'enqueue_message' ) ) {
            return array(
                'success' => false,
                'queued'  => false,
                'reason'  => 'actor_runtime_unavailable',
                'error'   => 'Actor runtime is not available for daily report delivery.',
            );
        }

        $payload = is_array( $payload ) ? $payload : array();
        $target  = $this->resolve_daily_report_delivery_target();
        if ( empty( $target['success'] ) ) {
            return $target;
        }

        $report_date = isset( $payload['report_date'] ) ? (string) $payload['report_date'] : gmdate( 'Y-m-d' );
        $raw_id      = 'daily-report:' . $report_date . ':' . ( isset( $target['channel'] ) ? (string) $target['channel'] : '' ) . ':' . ( isset( $target['chat_id'] ) ? (string) $target['chat_id'] : '' );
        $prompt      = $this->build_daily_report_actor_prompt( $payload );
        $message     = array(
            'success'        => true,
            'source'         => 'daily_report',
            'channel'        => isset( $target['channel'] ) ? sanitize_key( (string) $target['channel'] ) : 'telegram',
            'chat_id'        => isset( $target['chat_id'] ) ? (string) $target['chat_id'] : '',
            'message_id'     => abs( crc32( $raw_id ) ),
            'update_id'      => 0,
            'text'           => $prompt,
            'text_only'      => $prompt,
            'caption'        => '',
            'date'           => time(),
            'from_id'        => isset( $target['from_id'] ) ? (string) $target['from_id'] : '',
            'from_me'        => false,
            'remote_jid'     => isset( $target['remote_jid'] ) ? (string) $target['remote_jid'] : '',
            'language_code'  => isset( $target['language_code'] ) ? sanitize_key( (string) $target['language_code'] ) : '',
            'kind'           => 'daily_report',
            'media_refs'     => array(),
            'reply_media_refs' => array(),
            'has_user_text'  => true,
            'raw_message_id' => $raw_id,
            'user_role'      => 'admin',
        );

        $context_payload = array(
            'source'                 => 'daily_report',
            'is_daily_report'        => true,
            'force_action'           => false,
            'daily_report_payload'   => $payload,
            'language_source_text'   => isset( $target['language_source_text'] ) ? (string) $target['language_source_text'] : '',
            'conversation_language'  => isset( $target['language_code'] ) ? sanitize_key( (string) $target['language_code'] ) : '',
			'session_id'             => 'admin-heartbeat-daily-report-' . substr( md5( (string) ( $target['channel'] ?? '' ) . ':' . (string) ( $target['chat_id'] ?? '' ) ), 0, 16 ),
        );
        if ( ! empty( $target['remote_jid'] ) ) {
            $context_payload['remote_jid'] = (string) $target['remote_jid'];
        }
        $message['context_payload'] = $context_payload;

        $entry = (array) $this->actor_runtime->enqueue_message( $message, true );
        if ( empty( $entry['id'] ) && empty( $entry['duplicate_existing'] ) ) {
            return array(
                'success' => false,
                'queued'  => false,
                'reason'  => 'actor_enqueue_failed',
                'error'   => 'Daily report actor enqueue failed.',
            );
        }

        $this->dispatch_loopback_runner( 'daily-report', $this->get_runner_liveness_ttl() );

        return array(
            'success'   => true,
            'queued'    => true,
            'reason'    => ! empty( $entry['duplicate_existing'] ) ? 'duplicate_actor' : 'queued_actor',
            'actor_id'  => isset( $entry['id'] ) ? (string) $entry['id'] : '',
            'target'    => $target,
        );
    }

    private function resolve_daily_report_delivery_target() {
        $origin = get_option( Niwa_AI_Constants::OPT_AUTONOMY_LAST_ADMIN_CHANNEL, array() );
        $origin = is_array( $origin ) ? $origin : array();
        $channel = isset( $origin['channel'] ) ? sanitize_key( (string) $origin['channel'] ) : '';
        $chat_id = isset( $origin['chat_id'] ) ? trim( (string) $origin['chat_id'] ) : '';
        if ( in_array( $channel, array( 'telegram', 'whatsapp' ), true ) && '' !== $chat_id ) {
            return array(
                'success'              => true,
                'channel'              => $channel,
                'chat_id'              => $chat_id,
                'from_id'              => isset( $origin['from_id'] ) ? (string) $origin['from_id'] : '',
                'remote_jid'           => isset( $origin['remote_jid'] ) ? (string) $origin['remote_jid'] : '',
                'language_code'        => isset( $origin['language_code'] ) ? sanitize_key( (string) $origin['language_code'] ) : '',
                'language_source_text' => isset( $origin['language_source_text'] ) ? (string) $origin['language_source_text'] : '',
                'source'               => 'last_admin_message',
            );
        }

        $owner_chat_id = $this->get_configured_telegram_owner_chat_id();
        if (
            '' !== $owner_chat_id
            && $this->telegram
            && (
                ! method_exists( $this->telegram, 'is_outbound_enabled' )
                || $this->telegram->is_outbound_enabled( $owner_chat_id )
            )
        ) {
            return array(
                'success'              => true,
                'channel'              => 'telegram',
                'chat_id'              => $owner_chat_id,
                'from_id'              => '',
                'remote_jid'           => '',
                'language_code'        => '',
                'language_source_text' => '',
                'source'               => 'telegram_owner',
            );
        }

        return array(
            'success' => false,
            'queued'  => false,
            'reason'  => 'missing_delivery_channel',
            'error'   => 'No Telegram or WhatsApp admin delivery channel is available for the daily report.',
        );
    }

    private function build_daily_report_actor_prompt( $payload ) {
        $payload_json = wp_json_encode( is_array( $payload ) ? $payload : array() );
        return implode(
            "\n",
            array(
                'Prepare the scheduled morning founder brief from this JSON snapshot.',
                'Use the main founder agent context and relationship memory.',
				'Treat site_profile as authoritative. Do not introduce WooCommerce, sales, orders, revenue, inventory, cart, or checkout sections when transactions_available is false.',
                'Do not ask for confirmation and do not mention this scheduler instruction.',
                'Only mention meaningful positive or clearly useful signals present in the snapshot.',
                'Do not mention missing systems, zeros, weak results, unavailable analytics, or internal JSON.',
                'Use at most two short paragraphs and no bullets.',
                'If there is no meaningful signal after reviewing the snapshot, return an empty final reply.',
                '',
                '[DAILY_REPORT_SNAPSHOT]',
                (string) $payload_json,
                '[/DAILY_REPORT_SNAPSHOT]',
            )
        );
    }

    private function reap_dead_actor_runtime_state() {
        $recovery = $this->get_actor_runtime_recovery();
        return $recovery ? $recovery->reap_dead_actor_runtime_state() : 0;
    }
    public function run_background_tick() {
        $jobs = array(
			'admin_ui_queue' => function () {
				return apply_filters( 'niwa_ai_admin_ui_chat_background_drain', array( 'success' => true, 'processed' => 0 ) );
			},
            'background_subagents' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'process_background_subagents' )
                    ? $this->chat_agent->process_background_subagents( 5 )
                    : array( 'success' => true );
            },
            'daily_report' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_send_daily_report' )
                    ? $this->chat_agent->maybe_send_daily_report()
                    : array( 'success' => true );
            },
            'workspace_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_send_workspace_heartbeat' )
                    ? $this->chat_agent->maybe_send_workspace_heartbeat()
                    : array( 'success' => true );
            },
            'operative_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_run_operative_heartbeat' )
                    ? $this->chat_agent->maybe_run_operative_heartbeat()
                    : array( 'success' => true );
            },
            'stock_manager_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_run_stock_manager_operative_heartbeat' )
                    ? $this->chat_agent->maybe_run_stock_manager_operative_heartbeat()
                    : array( 'success' => true );
            },
            'scheduled_jobs' => function () {
                return $this->process_due_scheduled_jobs( 5 );
            },
            'actor_runtime' => function () {
                return $this->process_actor_runtime( 5 );
            },
            'post_reply_memory' => function () {
                return $this->process_post_reply_memory_queue( 5 );
            },
            'companion_distillation' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'companion_distillation_tick' )
                    ? $this->chat_agent->companion_distillation_tick()
                    : array( 'success' => true );
            },
            'stuck_stream_recovery' => function () {
                return $this->recover_stuck_turn_streams( 90, 20 );
            },
        );

        $runner = $this->get_background_tick_runner();
        return $runner ? $runner->run( $jobs, 'background_tick_finished' ) : array(
            'success' => true,
            'errors'  => array(),
        );
    }

    public function run_workspace_heartbeat_diagnostic() {
        $runner = $this->get_manual_diagnostic_runner();
        return $runner ? $runner->run_workspace_heartbeat() : array( 'success' => false, 'sent' => false, 'reason' => 'diagnostic_runner_unavailable' );
    }

    public function run_daily_report_diagnostic() {
        $runner = $this->get_manual_diagnostic_runner();
        return $runner ? $runner->run_daily_report() : array( 'success' => false, 'sent' => false, 'reason' => 'diagnostic_runner_unavailable' );
    }

    public function run_vendor_integrity_check() {
        $monitor_file = defined( 'NIWA_AI_DIR' ) ? NIWA_AI_DIR . 'includes/admin-autonomy/class-admin-autonomy-vendor-integrity-monitor.php' : '';
        if ( '' === $monitor_file || ! is_file( $monitor_file ) ) {
            if ( function_exists( 'niwa_ai_record_vendor_integrity_monitor_corruption' ) ) {
                return niwa_ai_record_vendor_integrity_monitor_corruption( 'daily_vendor_integrity_check' );
            }

            return array(
                'success' => false,
                'blocked' => true,
                'error'   => 'vendor_integrity_monitor_missing',
            );
        }

        $monitor = $this->get_vendor_integrity_monitor();
        return $monitor ? $monitor->run() : array(
            'success' => false,
            'error'   => 'vendor_integrity_monitor_unavailable',
        );
    }

    private function run_timed_automation_tick() {
        $jobs = array(
            'background_subagents' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'process_background_subagents' )
                    ? $this->chat_agent->process_background_subagents( 5 )
                    : array( 'success' => true, 'processed' => 0 );
            },
            'daily_report' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_send_daily_report' )
                    ? $this->chat_agent->maybe_send_daily_report()
                    : array( 'success' => true );
            },
            'workspace_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_send_workspace_heartbeat' )
                    ? $this->chat_agent->maybe_send_workspace_heartbeat()
                    : array( 'success' => true );
            },
            'scheduled_jobs' => function () {
                return $this->process_due_scheduled_jobs( 5 );
            },
            'actor_runtime' => function () {
                return $this->process_actor_runtime( 5 );
            },
            'operative_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_run_operative_heartbeat' )
                    ? $this->chat_agent->maybe_run_operative_heartbeat()
                    : array( 'success' => true );
            },
            'stock_manager_heartbeat' => function () {
                return $this->chat_agent && method_exists( $this->chat_agent, 'maybe_run_stock_manager_operative_heartbeat' )
                    ? $this->chat_agent->maybe_run_stock_manager_operative_heartbeat()
                    : array( 'success' => true );
            },
        );

        $runner = $this->get_background_tick_runner();
        return $runner ? $runner->run( $jobs ) : array(
            'success' => true,
            'errors'  => array(),
        );
    }

    public function wake_background_runtime( $reason = 'manual' ) {
        if ( ! $this->telegram ) {
            return array(
                'success' => false,
                'error'   => 'Background runner state nije dostupan.',
            );
        }

        if ( function_exists( 'wp_schedule_single_event' ) ) {
            wp_schedule_single_event( time() + 5, self::BACKGROUND_TICK_HOOK );
        }

        return $this->dispatch_loopback_runner( 'background-wake', 75 );
    }

    public function wake_actor_continuation_runtime( $context = array() ) {
        $context = is_array( $context ) ? $context : array();
        $this->log_turn_debug(
            'actor_continuation_wake_requested',
            array(
                'reason'          => isset( $context['reason'] ) ? sanitize_key( (string) $context['reason'] ) : 'soft_deadline_continuation',
                'session_id'      => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'actor_id'        => isset( $context['actor_id'] ) ? (string) $context['actor_id'] : '',
                'checkpoint_mode' => isset( $context['checkpoint_mode'] ) ? sanitize_key( (string) $context['checkpoint_mode'] ) : '',
            )
        );

        return $this->dispatch_loopback_runner( 'chain', 75 );
    }

    private function is_telegram_webhook_active() {
        $state = $this->get_pending_runtime_state();
        return $state ? $state->is_telegram_webhook_active() : false;
    }

    private function has_pending_transport_buffers() {
        $state = $this->get_pending_runtime_state();
        return $state ? $state->has_pending_transport_buffers() : false;
    }

    public function process_scheduled_tasks( $limit = 3 ) {
        $runner = $this->get_scheduled_task_runner();
        if ( ! $runner ) {
            return array(
                'success' => false,
                'errors'  => array( 'scheduled_task_runner_unavailable' ),
            );
        }

        return $runner->run( $limit );
    }

    public function process_targeted_scheduled_job( $job_id = 0, $job_uuid = '' ) {
        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'process_targeted_job_event' ) ) {
            return array(
                'success' => false,
                'error'   => 'scheduler_unavailable',
            );
        }

        $result = $this->scheduler->process_targeted_job_event( $job_id, $job_uuid );
        if ( ! empty( $result['execution_result']['awaiting_actor_result'] ) ) {
            $result['actor_runtime'] = $this->process_actor_runtime( 5 );
        }

        return $result;
    }

    private function process_due_scheduled_jobs( $limit = 3 ) {
        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'process_due_jobs' ) ) {
            return array(
                'success'   => true,
                'processed' => 0,
            );
        }

        return $this->scheduler->process_due_jobs( $limit );
    }

    public function enqueue_scheduled_self_wake( $message, $job = array() ) {
        $message = is_array( $message ) ? $message : array();
        $job     = is_array( $job ) ? $job : array();
        if ( empty( $message['text'] ) ) {
            return array(
                'success' => false,
                'error'   => 'scheduled_self_wake_empty_message',
            );
        }

        $message['source'] = 'scheduled_self_wake';
        if ( empty( $message['scheduled_job'] ) ) {
            $message['scheduled_job'] = array(
                'id'       => intval( isset( $job['id'] ) ? $job['id'] : 0 ),
                'job_uuid' => isset( $job['job_uuid'] ) ? (string) $job['job_uuid'] : '',
                'title'    => isset( $job['title'] ) ? (string) $job['title'] : '',
            );
        }

        if ( $this->chat_agent && method_exists( $this->chat_agent, 'enqueue_scheduled_subagent' ) ) {
            return (array) $this->chat_agent->enqueue_scheduled_subagent( $message, $job );
        }

        $actor_intake = $this->get_actor_intake();
        return $actor_intake && method_exists( $actor_intake, 'enqueue' )
            ? $actor_intake->enqueue( $message, true, false )
            : array(
                'success' => false,
                'error'   => 'actor_intake_unavailable',
            );
    }

    public function enqueue_subagent_result( $job, $result = array() ) {
        $job       = is_array( $job ) ? $job : array();
        $result    = is_array( $result ) ? $result : array();
        $session_id = trim( (string) ( $job['parent_session_id'] ?? '' ) );
        $chat_id    = trim( (string) ( $job['chat_id'] ?? '' ) );
        $channel    = sanitize_key( (string) ( $job['channel'] ?? 'telegram' ) );
        if ( '' === $chat_id ) {
            return false;
        }

        $status  = sanitize_key( (string) ( $job['status'] ?? ( ! empty( $result['success'] ) ? 'completed' : 'failed' ) ) );
        $summary = trim( (string) ( 'completed' === $status ? ( $job['summary'] ?? $result['summary'] ?? '' ) : ( $job['error'] ?? $result['error'] ?? '' ) ) );
        if ( '' === $summary ) {
            return false;
        }

        $delivery_context = array(
            'source'     => 'subagent_result',
            'session_id' => $session_id,
            'job_id'     => (string) ( $job['id'] ?? '' ),
            'job_status' => $status,
        );
        $sent = $this->send_channel_message(
            $channel,
            $chat_id,
            $summary,
            isset( $job['reply_to_message_id'] ) ? intval( $job['reply_to_message_id'] ) : 0,
            array(),
            $delivery_context
        );
        if ( empty( $sent['success'] ) ) {
            return false;
        }
        if ( $this->conversation_store && '' !== $session_id ) {
            $this->conversation_store->append( $session_id, 'bot', $summary, array(), $delivery_context );
        }

        return true;
    }

    public function maybe_boot_loopback_runner( $force = false ) {
		if ( ! $this->telegram ) {
			return;
        }

        $state   = $this->telegram->get_runner_state();
        $now     = time();
        $timeout = $this->get_runner_liveness_ttl();

        if ( ! $force && $this->has_active_actor_messages() ) {
            return;
        }

        if ( ! $force ) {
            $state = $this->clear_stale_buffer_runner_state( $state, 'boot' );
        }

        if ( ! $force && intval( $state['active_until'] ) > $now ) {
            return;
        }

        // Init-action dispatch happens on every WP request — without this
        // throttle, a pending actor message turns every page load into a
        // dispatched boot runner (we saw 1+ boot/sec in production logs).
        // 5 seconds is short enough that a stuck queue still recovers, but
        // long enough that a flurry of admin/AJAX requests does not spawn a
        // worker per request.
        if ( ! $force && intval( $state['last_spawned_at'] ) > ( $now - 5 ) ) {
            return;
        }

		if ( $this->telegram->is_enabled() && method_exists( $this->telegram, 'ensure_webhook_registered' ) ) {
            $this->telegram->ensure_webhook_registered( false );
        }

        if ( $force ) {
            $this->dispatch_loopback_runner( 'force', $timeout );
            return;
        }

        if ( $this->has_pending_background_subagents() || $this->should_dispatch_timed_automation_fallback( $now ) ) {
            $this->dispatch_loopback_runner( 'background-wake', 75 );
        } elseif ( $this->has_pending_actor_messages() || $this->has_pending_transport_buffers() ) {
            $has_pending_buffers = $this->has_pending_transport_buffers();
            $this->dispatch_loopback_runner(
                $has_pending_buffers ? 'webhook-buffer' : 'boot',
                $has_pending_buffers ? 30 : $timeout
            );
        }
    }

    private function should_dispatch_timed_automation_fallback( $now = null ) {
        $fallback = $this->get_timed_automation_fallback();
        return $fallback ? $fallback->should_dispatch( $now ) : false;
    }

    private function get_due_timed_automation_hooks( $now = null ) {
        $fallback = $this->get_timed_automation_fallback();
        return $fallback ? $fallback->get_due_hooks( $now ) : array();
    }

    public function restart_polling_runtime( $reason = 'manual' ) {
        try {
            if ( ! $this->telegram ) {
                return array(
                    'success' => false,
                    'error'   => 'Telegram runtime nije dostupan.',
                );
            }

            $this->telegram->reset_runtime_state();
            $webhook = array(
                'success' => true,
                'skipped' => 'polling_transport',
            );
            if ( method_exists( $this->telegram, 'is_webhook_transport_enabled' ) && $this->telegram->is_webhook_transport_enabled() ) {
                $webhook = $this->telegram->set_webhook();
            } elseif ( method_exists( $this->telegram, 'delete_webhook' ) ) {
                $this->telegram->delete_webhook();
            }
            $this->telegram->schedule_poll_restart( 5 );

            if ( ! $this->telegram->is_enabled() ) {
                return array(
                    'success' => true,
                    'note'    => 'telegram_disabled',
                );
            }

            // Skip dispatching a fresh runner from the request that triggered the
            // restart. Running an agent inside the same process is expensive and
            // can leave stale active_until locks when the process times out.
            $manual_reason = 'manual' === sanitize_key( (string) $reason );
            if ( ! $manual_reason ) {
                return array(
                    'success' => true,
                    'note'    => 'runner_dispatch_deferred',
                );
            }

            $timeout = $this->get_runner_liveness_ttl();
            $result = $this->dispatch_loopback_runner( sanitize_key( (string) $reason ), $timeout );
            if ( empty( $webhook['success'] ) ) {
                $result['webhook_error'] = isset( $webhook['error'] ) ? (string) $webhook['error'] : 'setWebhook failed.';
            }
            return $result;
        } catch ( Throwable $e ) {
            error_log(
                sprintf(
                    '[Niwa AI] admin autonomy polling restart failed: %s in %s:%d',
                    $e->getMessage(),
                    $e->getFile(),
                    (int) $e->getLine()
                )
            );

            return array(
                'success' => false,
                'error'   => $this->format_runtime_exception_message( $e ),
            );
        }
    }

    public function handle_poll_now() {
        $this->assert_admin_post_nonce( 'niwa_ai_admin_autonomy_poll_now', 'niwa_ai_admin_autonomy_poll_now_nonce' );

        $result = $this->poll_telegram_updates();
        if ( empty( $result['success'] ) ) {
            $this->redirect_to_settings_page(
                'error',
                isset( $result['error'] ) ? (string) $result['error'] : 'Telegram fallback polling failed.',
                'runtime'
            );
        }

        $this->dispatch_loopback_runner( 'manual', 30 );

        $message = sprintf(
            'Polling zavrsen. Updates: %1$d, handled: %2$d, ignored: %3$d.',
            intval( isset( $result['updates'] ) ? $result['updates'] : 0 ),
            intval( isset( $result['handled'] ) ? $result['handled'] : 0 ),
            intval( isset( $result['ignored'] ) ? $result['ignored'] : 0 )
        );

        $this->redirect_to_settings_page( 'success', $message, 'runtime' );
    }

    private function run_background_wake_loop() {
        $loop = $this->get_background_wake_loop();
        return $loop ? $loop->run() : array();
    }

    private function has_enabled_operative_heartbeat( $continuous_only = false ) {
        if ( ! $this->chat_agent || ! method_exists( $this->chat_agent, 'get_operative_heartbeat_config' ) ) {
            return false;
        }

        foreach ( array( 'admin', 'stock_manager' ) as $role ) {
            $config = (array) $this->chat_agent->get_operative_heartbeat_config( $role );
            if ( empty( $config['enabled'] ) ) {
                continue;
            }

            $delivery_channel = isset( $config['delivery_channel'] ) ? sanitize_key( (string) $config['delivery_channel'] ) : 'telegram';
            if ( 'admin_ui' !== $delivery_channel && ( ! $this->telegram || ! $this->telegram->is_enabled() ) ) {
                continue;
            }

            $interval = isset( $config['interval_minutes'] ) ? floatval( $config['interval_minutes'] ) : 60.0;
            if ( ! $continuous_only || $interval <= 5.0 ) {
                return true;
            }
        }

        return false;
    }

    private function has_due_scheduled_jobs_soon( $window_seconds = 20 ) {
        global $wpdb;

        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'get_table_name' ) ) {
            return false;
        }

        $until = gmdate( 'Y-m-d H:i:s', time() + max( 0, intval( $window_seconds ) ) );
        $count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(1) FROM {$this->scheduler->get_table_name()} WHERE status = 'active' AND next_run_at IS NOT NULL AND next_run_at <= %s",
                $until
            )
        );

        return intval( $count ) > 0;
    }

    public function handle_loopback_runner() {
        ignore_user_abort( true );
        $this->raise_loopback_runner_runtime_limit();

        $runner_id = isset( $_REQUEST['runner_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['runner_id'] ) ) : '';

        if ( ! $this->verify_loopback_signature( 'niwa_runner' ) ) {
            status_header( 403 );
            echo 'forbidden';
            exit;
        }

        $reason     = isset( $_GET['reason'] ) ? sanitize_key( wp_unslash( $_GET['reason'] ) ) : '';
        $reason     = sanitize_key( (string) $reason );
        $active_ttl = $this->get_runner_liveness_ttl();
        if ( ! $this->loopback_runner_request_is_current( $runner_id ) ) {
            $this->log_turn_debug(
                'loopback_runner_stale_request_skipped',
                array(
                    'reason'    => $reason,
                    'runner_id' => $runner_id,
                    'transport' => 'web_loopback',
                )
            );
            status_header( 200 );
            echo 'stale-runner';
            exit;
        }

        // Acknowledge the dispatch immediately so the dispatcher can observe a
        // fast, reliable 200 (request received + accepted) without blocking on
        // the full runner turn. The actual work continues in the background
        // under ignore_user_abort( true ) after the connection is closed.
        $this->acknowledge_async_request( 'accepted' );

        if ( 'webhook-buffer' === $reason ) {
            usleep( $this->get_webhook_buffer_idle_wait_ms() * 1000 );
        }

        $this->execute_loopback_runner( $reason, $runner_id, 'web_loopback', $active_ttl );

        exit;
    }

    public function run_cli_runner( $reason = 'cli', $runner_id = '' ) {
        ignore_user_abort( true );
        $this->raise_loopback_runner_runtime_limit();

        $reason    = sanitize_key( (string) $reason );
        $runner_id = sanitize_text_field( (string) $runner_id );
        if ( '' === $reason ) {
            $reason = 'cli';
        }

		if ( ! $this->telegram ) {
			return array(
				'success'   => false,
				'error'     => 'runner_state_unavailable',
                'transport' => 'local_cli',
            );
        }

        if ( ! $this->loopback_runner_request_is_current( $runner_id ) ) {
            return array(
                'success'   => true,
                'skipped'   => 'stale_runner',
                'transport' => 'local_cli',
            );
        }

        if ( 'webhook-buffer' === $reason ) {
            usleep( $this->get_webhook_buffer_idle_wait_ms() * 1000 );
        }

        return $this->execute_loopback_runner( $reason, $runner_id, 'local_cli', $this->get_runner_liveness_ttl() );
    }

    private function get_webhook_buffer_idle_wait_ms() {
        $wait_ms = Niwa_AI_Admin_Autonomy_Telegram_Album_Buffer::TEXT_FRAG_GAP_MS + 250;
        $batcher = $this->get_whatsapp_text_batcher();
        if ( $batcher && $batcher->has_pending() ) {
            $wait_ms = max( $wait_ms, ( $batcher->next_flush_delay() * 1000 ) + 250 );
        }

        return max( 1, intval( $wait_ms ) );
    }

    private function execute_loopback_runner( $reason, $runner_id, $transport, $active_ttl ) {
        $executor = $this->get_loopback_runner_executor();
        return $executor
            ? $executor->execute( $reason, $runner_id, $transport, $active_ttl )
            : array(
                'success'   => false,
                'transport' => 'local_cli' === (string) $transport ? 'local_cli' : 'web_loopback',
                'error'     => 'loopback_runner_executor_unavailable',
            );
    }

    private function runner_state_is_owned_by_current_request( $state, $runner_id ) {
        $state     = is_array( $state ) ? $state : array();
        $runner_id = trim( (string) $runner_id );
        $active_id = isset( $state['active_runner_id'] ) ? trim( (string) $state['active_runner_id'] ) : '';

        if ( '' === $active_id ) {
            return true;
        }

        return '' !== $runner_id && hash_equals( $active_id, $runner_id );
    }

    private function loopback_runner_request_is_current( $runner_id ) {
        if ( ! $this->telegram || ! method_exists( $this->telegram, 'get_runner_state' ) ) {
            return true;
        }

        $runner_id = trim( (string) $runner_id );
        $state     = (array) $this->telegram->get_runner_state();
        $active_id = isset( $state['active_runner_id'] ) ? trim( (string) $state['active_runner_id'] ) : '';
        if ( '' === $active_id ) {
            return true;
        }

        if ( intval( isset( $state['active_until'] ) ? $state['active_until'] : 0 ) <= time() ) {
            return true;
        }

        $active_transport = isset( $state['active_transport'] ) ? sanitize_key( (string) $state['active_transport'] ) : '';
        $active_pid       = intval( isset( $state['active_pid'] ) ? $state['active_pid'] : 0 );
        if (
            'local_cli' === $active_transport
            && $active_pid > 0
            && class_exists( 'Niwa_AI_Admin_Autonomy_Process_Runtime' )
            && ! Niwa_AI_Admin_Autonomy_Process_Runtime::background_process_is_alive( $active_pid )
        ) {
            $state['active_until']     = 0;
            $state['active_runner_id'] = '';
            $state['active_transport'] = '';
            $state['active_pid']       = 0;
            $state['last_error']       = 'cleared_dead_local_cli_runner_' . $active_pid;
            $this->telegram->update_runner_state( $state );
            $this->log_turn_debug(
                'loopback_runner_dead_active_state_cleared',
                array(
                    'runner_id'         => $runner_id,
                    'dead_active_id'    => $active_id,
                    'dead_active_pid'   => $active_pid,
                    'active_transport'  => $active_transport,
                )
            );
            return true;
        }

        return '' !== $runner_id && hash_equals( $active_id, $runner_id );
    }

    private function refresh_current_loopback_runner_liveness( $ttl = null ) {
        if ( ! $this->telegram || ! method_exists( $this->telegram, 'get_runner_state' ) || ! method_exists( $this->telegram, 'update_runner_state' ) ) {
            return false;
        }

        $runner_id = trim( (string) $this->active_loopback_runner_id );
        if ( '' === $runner_id ) {
            return false;
        }

        $state = (array) $this->telegram->get_runner_state();
        if ( ! $this->runner_state_is_owned_by_current_request( $state, $runner_id ) ) {
            return false;
        }

        $now                     = time();
        $state['last_seen_at']   = $now;
        $state['active_until']   = max(
            intval( isset( $state['active_until'] ) ? $state['active_until'] : 0 ),
            $now + max( self::RUNNER_LIVENESS_TTL, null === $ttl ? $this->get_runner_liveness_ttl() : intval( $ttl ) )
        );
        $state['active_runner_id'] = $runner_id;
        if ( empty( $state['active_transport'] ) ) {
            $state['active_transport'] = 'web_loopback';
        }
        if ( empty( $state['last_transport'] ) ) {
            $state['last_transport'] = $state['active_transport'];
        }
        $state['last_error']     = '';
        $this->telegram->update_runner_state( $state );

        return true;
    }

    private function handle_telegram_reaction_message( $message ) {
        $reactions = $this->get_telegram_reactions();
        return $reactions ? $reactions->handle( $message ) : null;
    }

    private function authorize_telegram_message( $message ) {
        $channel = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : 'telegram';
        if ( 'whatsapp' === $channel ) {
            return $this->authorize_whatsapp_message( $message );
        }

        $authorization = $this->get_telegram_authorization();
        return $authorization
            ? (array) $authorization->resolve( $message )
            : array(
                'handled'   => false,
                'user_role' => 'admin',
            );
    }

    private function authorize_whatsapp_message( $message ) {
        if ( ! $this->whatsapp || ! method_exists( $this->whatsapp, 'get_user_role' ) ) {
            return array(
                'handled'   => true,
                'result'    => array(
                    'success' => true,
                    'ignored' => true,
                    'error'   => 'whatsapp_unavailable',
                ),
                'user_role' => 'admin',
            );
        }

        $chat_id   = isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '';
        if ( ! empty( $message['from_me'] ) ) {
            return array(
                'handled'   => false,
                'user_role' => 'admin',
            );
        }

        $user_role = $this->whatsapp->get_user_role( $chat_id );
        if ( null !== $user_role ) {
            return array(
                'handled'   => false,
                'user_role' => (string) $user_role,
            );
        }

        $this->log_turn_debug(
            'unauthorized_whatsapp_denied',
            array(
                'chat_id' => $chat_id,
                'from_id' => isset( $message['from_id'] ) ? (string) $message['from_id'] : '',
            )
        );

        return array(
            'handled' => true,
            'result'  => array(
                'success' => true,
                'ignored' => true,
            ),
        );
    }

    private function remember_admin_delivery_origin( $message ) {
        $message = is_array( $message ) ? $message : array();
        if ( ! empty( $message['callback_query_id'] ) ) {
            return;
        }

        $channel = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : 'telegram';
        if ( ! in_array( $channel, array( 'telegram', 'whatsapp' ), true ) ) {
            return;
        }

        $chat_id = isset( $message['chat_id'] ) ? trim( (string) $message['chat_id'] ) : '';
        if ( '' === $chat_id ) {
            return;
        }

        $text = isset( $message['text'] ) ? trim( (string) $message['text'] ) : '';
        if ( '' === $text && ! empty( $message['caption'] ) ) {
            $text = trim( (string) $message['caption'] );
        }

        update_option(
            Niwa_AI_Constants::OPT_AUTONOMY_LAST_ADMIN_CHANNEL,
            array(
                'channel'              => $channel,
                'chat_id'              => $chat_id,
                'message_id'           => isset( $message['message_id'] ) ? intval( $message['message_id'] ) : 0,
                'raw_message_id'       => isset( $message['raw_message_id'] ) ? (string) $message['raw_message_id'] : '',
                'from_id'              => isset( $message['from_id'] ) ? (string) $message['from_id'] : '',
                'remote_jid'           => isset( $message['remote_jid'] ) ? (string) $message['remote_jid'] : '',
                'language_code'        => isset( $message['language_code'] ) ? sanitize_key( (string) $message['language_code'] ) : '',
                'language_source_text' => $text,
                'updated_at'           => time(),
            ),
            false
        );
    }

    private function handle_incoming_message( $message, $wake_runner = true ) {
        $handler = $this->get_message_handler();
        return $handler
            ? $handler->handle( $message, $wake_runner )
            : array(
                'success' => false,
                'error'   => 'message_handler_unavailable',
                'reply'   => '',
            );
    }

    public function resolve_actor_session_id( $message ) {
        $chat_id = isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '';
        $channel = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : '';
		$context_payload = isset( $message['context_payload'] ) && is_array( $message['context_payload'] ) ? $message['context_payload'] : array();
		if ( 'daily_report' === sanitize_key( (string) ( $message['source'] ?? '' ) ) ) {
			$background_session_id = trim( (string) ( $context_payload['session_id'] ?? '' ) );
			if ( '' !== $background_session_id ) {
				return $background_session_id;
			}
		}
        if ( 'admin_ui' === $channel ) {
            $session_id      = isset( $context_payload['session_id'] ) ? trim( (string) $context_payload['session_id'] ) : '';
            if ( '' !== $session_id ) {
                return $session_id;
            }
        }
        if ( 'whatsapp' === $channel || 0 === strpos( $chat_id, 'whatsapp:' ) ) {
            $user_role = isset( $message['user_role'] ) ? sanitize_key( (string) $message['user_role'] ) : '';
            if ( 'admin' !== $user_role && empty( $message['from_me'] ) ) {
                $digits = preg_replace( '/[^0-9]/', '', 0 === strpos( $chat_id, 'whatsapp:' ) ? substr( $chat_id, 9 ) : $chat_id );
                if ( '' === $digits && ! empty( $message['from_id'] ) ) {
                    $digits = preg_replace( '/[^0-9]/', '', (string) $message['from_id'] );
                }
                if ( '' === $digits && ! empty( $message['remote_jid'] ) ) {
                    $digits = preg_replace( '/[^0-9]/', '', (string) $message['remote_jid'] );
                }
                if ( '' === $digits ) {
                    $digits = substr( md5( wp_json_encode( $message ) ), 0, 12 );
                }

                $prefix = 'stock_manager' === $user_role ? 'stock-whatsapp-' : 'lead-whatsapp-';
                return $prefix . $digits;
            }
        }

        return $this->get_founder_session_id( $chat_id );
    }

    public function run_session_actor_message( $session_id, $entry ) {
        $runner = $this->get_session_actor_message_runner();
        return $runner
            ? $runner->run( $session_id, $entry )
            : array(
                'success' => false,
                'error'   => 'session_actor_message_runner_unavailable',
            );
    }

    private function prepare_actor_message_for_execution( $message ) {
        $message = is_array( $message ) ? $message : array();
        $channel = isset( $message['channel'] ) ? sanitize_key( (string) $message['channel'] ) : '';
        $chat_id = isset( $message['chat_id'] ) ? (string) $message['chat_id'] : '';
        $is_whatsapp = 'whatsapp' === $channel
            || 0 === strpos( $chat_id, 'whatsapp:' )
            || 0 === strpos( $chat_id, 'whatsapp-group:' );
        if ( $is_whatsapp && empty( $message['from_me'] ) ) {
            $message['channel'] = 'whatsapp';
            $authorization = $this->authorize_whatsapp_message( $message );
            if ( ! empty( $authorization['handled'] ) ) {
                $result            = isset( $authorization['result'] ) && is_array( $authorization['result'] ) ? $authorization['result'] : array();
                $result['handled'] = true;
                return $result;
            }
            if ( isset( $authorization['user_role'] ) ) {
                $message['user_role'] = (string) $authorization['user_role'];
            }
        }

        $preparer = $this->get_actor_message_preparer();
        return $preparer ? $preparer->prepare( $message ) : array(
            'success' => true,
            'message' => is_array( $message ) ? $message : array(),
        );
    }

    private function is_media_only_message( $message ) {
        $preprocessor = $this->get_telegram_message_preprocessor();
        return $preprocessor ? $preprocessor->is_media_only_message( $message ) : false;
    }

    /**
     * Find the first unprocessed voice/audio ref, including audio uploaded as
     * a document. Video notes stay in the media builder for transcript+preview.
     */
    private function find_first_voice_ref( $message ) {
        $preprocessor = $this->get_telegram_message_preprocessor();
        return $preprocessor ? $preprocessor->find_first_voice_ref( $message ) : null;
    }

    /**
     * Phase 7 — find the first .md / .zip / .skill document ref in
     * media_refs[]. Image-MIME documents are excluded; they go through the
     * image branch in build_media_attachments_context.
     */
    private function find_first_skill_document_ref( $message ) {
        $preprocessor = $this->get_telegram_message_preprocessor();
        return $preprocessor ? $preprocessor->find_first_skill_document_ref( $message ) : null;
    }

    /**
     * Phase 7 — find the first .csv / .xlsx document ref in media_refs[].
     */
    private function find_first_spreadsheet_ref( $message ) {
        $preprocessor = $this->get_telegram_message_preprocessor();
        return $preprocessor ? $preprocessor->find_first_spreadsheet_ref( $message ) : null;
    }

    /**
     * Resolve an inbound Telegram/WhatsApp audio ref and transcribe it.
     *
     * @param array $ref Structured Telegram or WhatsApp media reference.
     * @return array { success: bool, text: string, error: string }
     */
    private function transcribe_voice_message( $ref ) {
        $transcriber = $this->get_telegram_voice_transcriber();
        return $transcriber
            ? $transcriber->transcribe( $ref )
            : array(
                'success' => false,
                'text'    => '',
                'error'   => 'Voice transcriber is not available.',
            );
    }
    private function build_message_context( $message ) {
        $builder = $this->get_telegram_message_context_builder();
        return $builder ? $builder->build( $message ) : array();
    }
    private function build_media_attachments_context( $message ) {
        $builder = $this->get_media_context_builder();
        return $builder ? $builder->build( $message ) : array();
    }

    private function describe_sticker_via_vision( $ref, $image_url = null ) {
        $builder = $this->get_media_context_builder();
        return $builder ? $builder->describe_sticker_via_vision( $ref, $image_url ) : '';
    }

    private function call_vision_describe_sticker( $image_url ) {
        $builder = $this->get_media_context_builder();
        return $builder ? $builder->call_vision_describe_sticker( $image_url ) : '';
    }
    private function should_queue_founder_message( $message ) {
        if ( ! empty( $message['callback_query_id'] ) ) {
            return true;
        }

        $text = isset( $message['text'] ) ? trim( (string) $message['text'] ) : '';
        $caption = isset( $message['caption'] ) ? trim( (string) $message['caption'] ) : '';
        if ( '' !== $text || '' !== $caption ) {
            return true;
        }

        // Phase 7 — sole truth is the structured Phase 2-6 fields. BC shim
        // checks (photo_file_id / voice_file_id / document_file_id / *_only)
        // were removed once consumers migrated to media_refs[].
        if ( ! empty( $message['media_refs'] ) && is_array( $message['media_refs'] ) ) {
            return true;
        }
        if ( ! empty( $message['reply_media_refs'] ) && is_array( $message['reply_media_refs'] ) ) {
            return true;
        }
        if ( ! empty( $message['location'] ) || ! empty( $message['contact'] ) ) {
            return true;
        }

        return false;
    }

    private function execute_founder_message( $message, $context, $callback_acknowledged = false ) {
        $executor = $this->get_founder_message_executor();
        return $executor ? $executor->execute( $message, $context, $callback_acknowledged ) : array(
            'success' => false,
            'reply'   => '',
            'error'   => 'founder_message_executor_unavailable',
        );
    }

    private function maybe_handle_founder_onboarding_message( $message, $context ) {
        $user_role = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';
        $text      = isset( $message['text'] ) ? (string) $message['text'] : '';

        if (
            'admin' !== $user_role
            || ! empty( $message['callback_query_id'] )
            || preg_match( '/^\s*\/(?:status|rollback|help|new|reset|restart|compact|subagents|sessions|heartbeat)\b/i', $text )
            || ! $this->chat_agent
            || ! method_exists( $this->chat_agent, 'maybe_handle_founder_onboarding' )
        ) {
            return array();
        }

        return (array) $this->chat_agent->maybe_handle_founder_onboarding( $text, $context );
    }

    private function compact_queued_context( $context ) {
        $compactor = $this->get_context_compactor();
        return $compactor ? $compactor->compact( $context ) : array();
    }
    private function process_command( $text, $context = array() ) {
        $text              = trim( (string) $text );
        $context           = is_array( $context ) ? $context : array();
        $command           = $this->normalize_command_text( $text );
        $callback_command  = $this->extract_callback_command( isset( $context['callback_data'] ) ? $context['callback_data'] : '' );
        $user_role         = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';
        $is_whatsapp       = 'whatsapp' === ( isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '' );
        $is_slash_command  = 0 === strpos( ltrim( $text ), '/' );
        if ( $is_whatsapp && ! $is_slash_command && '' === $callback_command ) {
            $command = '';
        }

        $whatsapp_command_access = $this->get_whatsapp_command_access();
        $whatsapp_guard = $whatsapp_command_access
            ? $whatsapp_command_access->guard( $command, $callback_command, $context )
            : null;
        if ( is_array( $whatsapp_guard ) ) {
            return $whatsapp_guard;
        }

        // Stock managers can only use /help, /start, /new, /reset, and natural chat.
        if ( 'stock_manager' === $user_role ) {
            $allowed_sm_commands = array( 'help', 'start', 'new', 'reset', 'stop' );
            if ( preg_match( '/^\/?(\w+)/i', $command, $cmd_match ) ) {
                $cmd_name = strtolower( $cmd_match[1] );
                if ( ! in_array( $cmd_name, $allowed_sm_commands, true ) && in_array( $cmd_name, array( 'status', 'rollback', 'compact', 'subagents', 'sessions', 'heartbeat', 'run' ), true ) ) {
                    return array( 'reply' => 'Ta komanda nije dostupna za stock manager nalog. Mozete koristiti /help, /new, /reset i /stop.' );
                }
            }
        }

        if ( in_array( $callback_command, array( 'approve', 'reject' ), true ) ) {
            return array(
                'reply'                => 'Approval flow je uklonjen. Posalji zahtev normalno i Niwa ce ga izvrsiti odmah cim napravi validan plan.',
                'reply_markup'         => $this->build_remove_keyboard(),
                'clear_callback_markup' => true,
            );
        }

        if ( 'status' === $callback_command ) {
            return array(
                'reply'                 => 'Status vise ne saljem automatski. Posalji /status kada zelis runtime stanje.',
                'reply_markup'          => $this->build_remove_keyboard(),
                'clear_callback_markup' => true,
                'remember_exchange'     => false,
            );
        }

        $runtime_command = $this->maybe_handle_runtime_command( $text, $context, $command, $callback_command );
        if ( is_array( $runtime_command ) ) {
            return $runtime_command;
        }

        // Rollback is now a regular tool — LLM decides when to use it.
        // The /rollback slash command remains as explicit manual trigger.

        if ( 'help' === $command ) {
            if ( 'stock_manager' === $user_role ) {
                return array( 'reply' => $this->build_stock_manager_help_message() );
            }
            return array( 'reply' => $this->build_help_message() );
        }

        if ( 'start' === $command ) {
            if ( 'stock_manager' === $user_role ) {
                return array( 'reply' => $this->build_stock_manager_help_message() );
            }

            if (
                'admin' === $user_role
                && $this->chat_agent
                && method_exists( $this->chat_agent, 'is_founder_onboarding_complete' )
                && ! $this->chat_agent->is_founder_onboarding_complete()
            ) {
                return $this->plan_or_chat( $text, $context, false );
            }

            if ( $this->chat_agent && method_exists( $this->chat_agent, 'build_founder_start_message' ) ) {
                return array( 'reply' => (string) $this->chat_agent->build_founder_start_message( $context ) );
            }

            return array( 'reply' => $this->build_help_message() );
        }

        if ( 'admin' === $user_role && preg_match( $is_whatsapp ? '/^\/run\s+(.+)$/is' : '/^\/?run\s+(.+)$/is', $text, $matches ) ) {
            return $this->plan_or_chat( trim( (string) $matches[1] ), $context, true );
        }

        return $this->plan_or_chat( $text, $context, false );
    }

    private function plan_or_chat( $prompt, $context, $strict_action ) {
        $prompt = trim( (string) $prompt );

        if ( $this->should_use_telegram_realtime_transport( $context ) ) {
            $this->keep_typing_feedback( $context['chat_id'] );
            $this->begin_turn_stream_feedback( $context );
            $context['turn_progress_callback'] = $this->compose_turn_progress_callbacks(
                isset( $context['turn_progress_callback'] ) && is_callable( $context['turn_progress_callback'] )
                    ? $context['turn_progress_callback']
                    : null,
                $this->build_turn_progress_callback(
                    $context['chat_id'],
                    isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0
                )
            );
        }

        $context['force_action'] = ! empty( $context['force_action'] ) || ! empty( $strict_action );
        $this->log_turn_debug(
            'turn_prompt_harness_ready',
            array(
                'channel'                     => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
                'session_id'                  => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'chat_id'                     => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'message_id'                  => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'update_id'                   => isset( $context['update_id'] ) ? intval( $context['update_id'] ) : 0,
                'strict_action'               => ! empty( $strict_action ),
                'force_action'                => ! empty( $context['force_action'] ),
                'prompt_preview'              => $prompt,
                'harness'                     => 'agentic_tool_discovery',
            )
        );
        $this->log_turn_debug(
            'turn_context_ready',
            array(
                'channel'          => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
                'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'chat_id'          => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'message_id'       => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'force_action'     => ! empty( $context['force_action'] ),
                'context_keys'     => array_keys( $context ),
                'context_payload'  => isset( $context['_original_context_payload'] ) ? $context['_original_context_payload'] : array(),
            )
        );
        $reply       = $this->chat_with_founder( $prompt, $context );
        $chat_result = is_array( $this->last_founder_chat_result ) ? $this->last_founder_chat_result : array();
        $result      = $chat_result;
        $result['reply'] = $reply;

        if ( ! empty( $this->last_soft_deadline_result['context_snapshot'] ) ) {
            $result['continuation'] = $this->last_soft_deadline_result;
        }

        return $result;
    }

    private function chat_with_founder( $prompt, $context ) {
        $context = is_array( $context ) ? $context : array();
        $this->last_founder_chat_result = array();
        if ( empty( $context['current_prompt'] ) ) {
            $context['current_prompt'] = (string) $prompt;
        }

        if ( ! $this->chat_agent ) {
            error_log( '[Niwa AI Admin Runtime] Founder chat agent is not available.' );
            return '';
        }

        if ( $this->should_use_telegram_realtime_transport( $context ) ) {
            $this->keep_typing_feedback( $context['chat_id'] );
        }

        $runtime_policy = class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Runtime_Policy' )
            ? ( new Niwa_AI_Admin_Autonomy_Turn_Runtime_Policy() )->resolve( $context, 'main', array() )
            : array( 'allow_soft_deadline_continuation' => true );
        $founder_channel = isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram';
        // Admin UI inline jobs hand captured snapshots to the durable actor lane
        // before the browser job completes, then actor delivery writes back into
        // the admin display transcript.
        $allow_soft_deadline_continuation = ! empty( $runtime_policy['allow_soft_deadline_continuation'] );
        $this->log_turn_debug(
            'founder_chat_start',
            array(
                'channel'          => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
                'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'chat_id'          => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'message_id'       => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'update_id'        => isset( $context['update_id'] ) ? intval( $context['update_id'] ) : 0,
                'runtime_policy'   => $runtime_policy,
                'prompt_preview'   => $prompt,
            )
        );

        $result = $this->chat_agent->respond( $prompt, $context );
        $this->last_founder_chat_result = is_array( $result ) ? $result : array();
        $this->log_turn_debug(
            'founder_chat_result',
            array(
                'channel'          => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
                'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'chat_id'          => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'message_id'       => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'success'          => ! empty( $result['success'] ),
                'cancelled'        => ! empty( $result['cancelled'] ),
                'cancelled_reason' => isset( $result['cancelled_reason'] ) ? (string) $result['cancelled_reason'] : '',
                'reply_len'        => isset( $result['reply'] ) ? $this->text_length( (string) $result['reply'] ) : 0,
                'error'            => isset( $result['error'] ) ? (string) $result['error'] : '',
                'model'            => isset( $result['model'] ) ? (string) $result['model'] : '',
                'turn_id'          => isset( $result['turn_id'] ) ? (string) $result['turn_id'] : '',
            )
        );

        // Capture recoverable cancellation so the session actor runtime can
        // enqueue a continuation from the exact saved trace.
        $continuation_reason = isset( $result['cancelled_reason'] ) ? sanitize_key( (string) $result['cancelled_reason'] ) : '';
        if (
            $allow_soft_deadline_continuation
            && in_array( $continuation_reason, array( 'soft_deadline', 'task_integrity_verification', 'write_reconciliation', 'task_checkpoint_continue' ), true )
            && ! empty( $result['context_snapshot'] )
        ) {
            $this->last_soft_deadline_result = array(
                'cancelled_reason' => $continuation_reason,
                'context_snapshot' => $result['context_snapshot'],
                'chat_id'          => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'channel'          => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram',
                'message_id'       => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'user_role'        => isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin',
                'context_payload'  => isset( $context['_original_context_payload'] ) ? (array) $context['_original_context_payload'] : array(),
            );
        }

		if (
            $allow_soft_deadline_continuation
            && ! empty( $this->last_soft_deadline_result )
            && in_array(
                sanitize_key( (string) ( $this->last_soft_deadline_result['cancelled_reason'] ?? '' ) ),
                array( 'soft_deadline', 'task_integrity_verification', 'write_reconciliation', 'task_checkpoint_continue' ),
                true
            )
        ) {
            $snapshot = isset( $this->last_soft_deadline_result['context_snapshot'] ) && is_array( $this->last_soft_deadline_result['context_snapshot'] )
                ? $this->last_soft_deadline_result['context_snapshot']
                : array();
            $this->log_turn_debug(
                'soft_deadline_continuation_reply_suppressed',
                array(
                    'channel'            => $founder_channel,
                    'session_id'         => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                    'chat_id'            => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                    'message_id'         => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                    'update_id'          => isset( $context['update_id'] ) ? intval( $context['update_id'] ) : 0,
                    'turn_id'            => isset( $result['turn_id'] ) ? (string) $result['turn_id'] : '',
                    'soft_deadline'      => isset( $context['soft_deadline'] ) ? intval( $context['soft_deadline'] ) : 0,
                    'continuation_depth' => isset( $snapshot['continuation_depth'] ) ? intval( $snapshot['continuation_depth'] ) : 0,
                    'admin_ui_job_id'    => isset( $context['admin_ui_job_id'] ) ? sanitize_key( (string) $context['admin_ui_job_id'] ) : '',
                )
            );
			return '';
		}

        if ( ! $allow_soft_deadline_continuation && ! empty( $result['cancelled_reason'] ) && 'soft_deadline' === $result['cancelled_reason'] ) {
            $reply = isset( $result['reply'] ) ? trim( (string) $result['reply'] ) : '';
            return '' !== $reply ? $reply : 'Tu sam, ali ovaj odgovor je prekinut zbog vremenskog ogranicenja. Posalji poruku ponovo ako treba da nastavim.';
        }

		if ( ! empty( $result['cancelled'] ) && ! empty( $context['cancel_probe'] ) && 'external' === ( $result['cancelled_reason'] ?? '' ) ) {
			return '';
		}

		if ( ! empty( $result['cancelled'] ) ) {
			return isset( $result['reply'] ) ? (string) $result['reply'] : '';
		}

		if ( empty( $result['success'] ) ) {
			if ( ! empty( $result['reply'] ) ) {
				return (string) $result['reply'];
			}
			if ( ! empty( $result['successful_write_execution'] ) ) {
				return __( 'At least one requested change was executed, but I could not verify the final response. I saved the execution evidence and will not repeat the write automatically.', 'niwa-ai' );
			}
			if ( 'admin_ui' === $founder_channel ) {
				$error_detail = isset( $result['error'] ) ? trim( (string) $result['error'] ) : '';
				return '' !== $error_detail
					? 'Odgovor nije uspeo: ' . $error_detail
					: 'Odgovor nije uspeo zbog greske u AI runtime-u. Posalji poruku ponovo.';
			}
			return '';
		}

        if ( ! isset( $result['reply'] ) ) {
            error_log( '[Niwa AI Admin Runtime] Founder chat completed without a reply payload.' );
            return '';
        }

        return (string) $result['reply'];
    }

    private function dispatch_loopback_runner( $reason = 'boot', $active_ttl = 30 ) {
        $dispatcher = $this->get_runner_dispatcher();
        if ( $dispatcher ) {
            return $dispatcher->dispatch( $reason, $active_ttl );
        }

        return array(
            'success' => false,
            'error'   => 'runner_dispatcher_unavailable',
        );
    }

	public function dispatch_admin_ui_actor_runner() {
		return (array) $this->dispatch_loopback_runner( 'admin-ui', $this->get_runner_liveness_ttl() );
	}

    private function should_use_local_cli_runner() {
        $dispatcher = $this->get_runner_dispatcher();
        return $dispatcher ? $dispatcher->should_use_local_cli_runner() : false;
    }

    private function clear_stale_buffer_runner_state( $state, $reason = '' ) {
        $state      = is_array( $state ) ? $state : array();
        $reason_key = sanitize_key( (string) $reason );

        if ( ! in_array( $reason_key, array( 'webhook-buffer', 'boot', 'message', 'admin-ui', 'chain', 'background-wake' ), true ) ) {
            return $state;
        }

        if ( ( ! $this->has_pending_transport_buffers() && ! $this->has_pending_actor_messages() ) || $this->has_active_actor_messages() ) {
            return $state;
        }

        $now              = time();
        $active_until     = intval( isset( $state['active_until'] ) ? $state['active_until'] : 0 );
        $last_seen_at     = intval( isset( $state['last_seen_at'] ) ? $state['last_seen_at'] : 0 );
        $last_spawned_at  = intval( isset( $state['last_spawned_at'] ) ? $state['last_spawned_at'] : 0 );
        $active_runner_id = isset( $state['active_runner_id'] ) ? trim( (string) $state['active_runner_id'] ) : '';

        if ( $active_until <= $now ) {
            return $state;
        }

        $last_progress_at = max( $last_seen_at, $last_spawned_at );
        if ( $last_progress_at > 0 && $last_progress_at > ( $now - 30 ) ) {
            return $state;
        }

        $state['active_until']      = 0;
        $state['active_runner_id']  = '';
        $state['wake_pending']      = 1;
        $state['wake_reason']       = $this->has_pending_actor_messages() ? 'message' : 'webhook-buffer';
        $state['wake_requested_at'] = $now;
        if ( '' === trim( (string) ( $state['last_error'] ?? '' ) ) ) {
            $state['last_error'] = 'stale_buffer_runner_cleared';
        }
        $this->telegram->update_runner_state( $state );
        $this->log_turn_debug(
            'loopback_runner_stale_state_cleared',
            array(
                'reason'           => $reason_key,
                'active_runner_id' => $active_runner_id,
                'last_seen_at'     => $last_seen_at,
                'last_spawned_at'  => $last_spawned_at,
                'active_until'     => $active_until,
            )
        );

        return $state;
    }

    public function get_chat_agent() {
        return $this->chat_agent;
    }

    private function acknowledge_async_request( $body = 'ok' ) {
        $utils = $this->get_runtime_utils();
        if ( $utils ) {
            $utils->acknowledge_async_request( $body );
        }
    }

    private function get_runner_liveness_ttl() {
        $utils = $this->get_runtime_utils();
        return $utils ? $utils->get_runner_liveness_ttl() : self::get_turn_worker_time_limit() + 120;
    }

    private function format_runtime_exception_message( Throwable $e ) {
        $utils = $this->get_runtime_utils();
        return $utils ? $utils->format_exception_message( $e ) : '';
    }

    private function rollback_last_successful_run() {
        $runner = $this->get_rollback_runner();
        return $runner ? $runner->run() : 'Rollback helper nije dostupan.';
    }

    private function build_status_message( $context = array() ) {
        $builder = $this->get_status_message_builder();
        return $builder ? $builder->build( $context ) : 'Niwa Admin Autonomy status';
    }
    private function build_help_message() {
        $builder = $this->get_help_message_builder();
        return $builder ? $builder->founder_help() : '';
    }

    private function build_stock_manager_help_message() {
        $builder = $this->get_help_message_builder();
        return $builder ? $builder->stock_manager_help() : '';
    }

    private function build_plan_preview_message( $run ) {
        $builder = $this->get_plan_preview_builder();
        return $builder ? $builder->build( $run ) : '';
    }

    private function enqueue_post_reply_memory_capture( $session_id, $user_text, $reply, $context = array() ) {
        $queue = $this->get_post_reply_memory_queue();
        if ( $queue ) {
            $queue->enqueue( $session_id, $user_text, $reply, $context );
            $this->wake_post_reply_memory_worker();
        }
    }

    private function wake_post_reply_memory_worker() {
        if ( function_exists( 'wp_schedule_single_event' ) && ! wp_next_scheduled( self::POST_REPLY_MEMORY_HOOK ) ) {
            wp_schedule_single_event( time() + 5, self::POST_REPLY_MEMORY_HOOK );
        }

        $this->dispatch_loopback_runner( 'background-wake', 75 );
    }

    public function process_post_reply_memory_queue( $limit = 3 ) {
        $queue = $this->get_post_reply_memory_queue();
        return $queue ? $queue->process( $limit ) : array(
            'success'   => true,
            'processed' => 0,
            'remaining' => 0,
            'errors'    => array(),
        );
    }

	public function retry_post_reply_memory_enqueue( $entry ) {
		$queue = $this->get_post_reply_memory_queue();
		return $queue ? $queue->enqueue_entry( $entry ) : false;
	}

    private function is_post_reply_memory_entry_stale( $entry ) {
        $queue = $this->get_post_reply_memory_queue();
        return $queue ? $queue->is_stale( $entry ) : false;
    }

    private function run_post_reply_memory_capture( $entry ) {
        $queue = $this->get_post_reply_memory_queue();
        if ( $queue ) {
            $queue->run_capture( $entry );
        }
    }

    private function remember_exchange( $chat_id, $user_text, $reply, $context = array() ) {
        if ( ! $this->conversation_store ) {
            return;
        }

        $context    = is_array( $context ) ? $context : array();
        $session_id = ! empty( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : $this->get_founder_session_id( $chat_id );
        if ( '' !== trim( (string) $user_text ) ) {
            $this->conversation_store->append( $session_id, 'user', $user_text, array(), $context );
        }
        if ( '' !== trim( (string) $reply ) ) {
            $this->conversation_store->append( $session_id, 'bot', $reply, array(), $context );
        }
    }

    private function maybe_absorb_identity_from_exchange( $user_text, $reply, $context = array() ) {
        $helper = $this->get_founder_identity_helper();
        if ( $helper ) {
            $helper->maybe_absorb_identity_from_exchange( $user_text, $reply, $context );
        }
    }

    private function prepend_founder_intro( $reply ) {
        $helper = $this->get_founder_identity_helper();
        return $helper ? $helper->prepend_founder_intro( $reply ) : (string) $reply;
    }

    private function send_channel_message( $channel, $chat_id, $reply, $reply_to_id = 0, $reply_markup = array(), $context = array() ) {
        $channel = sanitize_key( (string) $channel );
        if ( 'admin_ui' === $channel ) {
            return $this->send_admin_ui_channel_message( $chat_id, $reply, $context );
        }

        if ( 'whatsapp' === $channel ) {
            return $this->whatsapp && method_exists( $this->whatsapp, 'send_message' )
                ? $this->whatsapp->send_message( $chat_id, $reply, $context )
                : array(
                    'success' => false,
                    'error'   => 'whatsapp_unavailable',
                );
        }

        return $this->telegram && method_exists( $this->telegram, 'send_message' )
            ? $this->telegram->send_message( $chat_id, $reply, $reply_to_id, is_array( $reply_markup ) ? $reply_markup : array() )
            : array(
                'success' => false,
                'error'   => 'telegram_unavailable',
            );
    }

    private function send_admin_ui_channel_message( $chat_id, $reply, $context = array() ) {
        $reply   = trim( (string) $reply );
        $context = is_array( $context ) ? $context : array();
        if ( '' === $reply ) {
            return array(
                'success' => true,
                'skipped' => true,
                'reason'  => 'empty_reply',
            );
        }

        $session_id = isset( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : '';
        if ( '' === $session_id && is_string( $chat_id ) && 0 === strpos( $chat_id, 'admin-' ) ) {
            $session_id = trim( (string) $chat_id );
        }
        if ( '' === $session_id && '' !== trim( (string) $chat_id ) ) {
            $session_id = $this->get_founder_session_id( $chat_id );
        }
        if ( '' === $session_id ) {
            return array(
                'success' => false,
                'error'   => 'admin_ui_delivery_missing_session',
            );
        }

        $meta = array(
			'channel'            => 'admin_ui',
			'source'             => isset( $context['source'] ) ? sanitize_key( (string) $context['source'] ) : 'actor_runtime',
			'delivery_committed' => true,
        );
        foreach ( array( 'admin_user_id', 'admin_user_login', 'actor_message_id', 'admin_ui_job_id', 'request_id', 'admin_ui_sequence' ) as $key ) {
            if ( ! empty( $context[ $key ] ) ) {
                $meta[ $key ] = is_numeric( $context[ $key ] ) ? intval( $context[ $key ] ) : (string) $context[ $key ];
            }
        }

		$admin_ui_job_id = ! empty( $context['admin_ui_job_id'] ) ? (string) $context['admin_ui_job_id'] : '';
		$actor_id        = isset( $context['actor_message_id'] ) ? (string) $context['actor_message_id'] : '';
		if ( '' !== $admin_ui_job_id && ! $this->complete_admin_ui_continuation_job( $admin_ui_job_id, $session_id, $reply, $actor_id, ! empty( $context['admin_ui_delivery_retry'] ) ) ) {
			return array(
				'success' => false,
				'error'   => 'admin_ui_continuation_rejected',
				'session' => $session_id,
			);
		}
		if ( ! empty( $context['turn_id'] ) ) {
			$meta['turn_id'] = (string) $context['turn_id'];
		}
		if ( '' !== $admin_ui_job_id ) {
			$delivery_lock = $this->admin_ui_chat_job_transition( array( 'type' => 'begin_delivery', 'job_id' => $admin_ui_job_id ) );
			if ( ! is_string( $delivery_lock ) || '' === $delivery_lock ) {
				$this->schedule_admin_ui_delivery_retry( $admin_ui_job_id, $session_id, $reply, $actor_id );
				return array( 'success' => false, 'error' => 'admin_ui_delivery_locked', 'session' => $session_id );
			}
			try {
				$delivery = $this->admin_ui_chat_job_transition(
					array( 'type' => 'delivery_status', 'job_id' => $admin_ui_job_id, 'session_id' => $session_id, 'actor_id' => $actor_id )
				);
				if ( 'delivered' === (string) ( is_array( $delivery ) ? ( $delivery['delivery_status'] ?? '' ) : '' ) ) {
					if ( ! $this->admin_ui_chat_job_transition( array( 'type' => 'delivered', 'job_id' => $admin_ui_job_id, 'session_id' => $session_id, 'actor_id' => $actor_id ) ) ) {
						$this->schedule_admin_ui_delivery_retry( $admin_ui_job_id, $session_id, $reply, $actor_id );
						return array( 'success' => false, 'error' => 'admin_ui_delivery_cleanup_failed', 'session' => $session_id );
					}
					$this->enqueue_post_reply_memory_capture( $session_id, (string) ( $context['current_prompt'] ?? '' ), $reply, $context );
					return array( 'success' => true, 'channel' => 'admin_ui', 'session' => $session_id );
				}
				if ( ! $this->conversation_store || ! method_exists( $this->conversation_store, 'append' ) || false === $this->conversation_store->append( $session_id, 'bot', $reply, array(), $meta ) ) {
					$this->schedule_admin_ui_delivery_retry( $admin_ui_job_id, $session_id, $reply, $actor_id );
					return array( 'success' => false, 'error' => 'admin_ui_transcript_write_failed', 'session' => $session_id );
				}
				$this->enqueue_post_reply_memory_capture( $session_id, (string) ( $context['current_prompt'] ?? '' ), $reply, $context );
				if ( ! $this->admin_ui_chat_job_transition( array( 'type' => 'delivered', 'job_id' => $admin_ui_job_id, 'session_id' => $session_id, 'actor_id' => $actor_id ) ) ) {
					$this->schedule_admin_ui_delivery_retry( $admin_ui_job_id, $session_id, $reply, $actor_id );
					return array( 'success' => false, 'error' => 'admin_ui_delivery_mark_failed', 'session' => $session_id );
				}
			} finally {
				$this->admin_ui_chat_job_transition( array( 'type' => 'end_delivery', 'job_id' => $admin_ui_job_id, 'lock_token' => $delivery_lock ) );
			}
		} elseif ( $this->conversation_store && method_exists( $this->conversation_store, 'append' ) ) {
			$this->conversation_store->append( $session_id, 'bot', $reply, array(), $meta );
		}
        return array(
            'success' => true,
            'channel' => 'admin_ui',
            'session' => $session_id,
        );
    }

	private function complete_admin_ui_continuation_job( $job_id, $session_id, $reply, $actor_id = '', $delivery_retry = false ) {
        $job_id     = sanitize_key( (string) $job_id );
        $session_id = trim( (string) $session_id );
        $reply      = trim( (string) $reply );
        if ( '' === $job_id || '' === $reply ) {
			return false;
        }

		return (bool) $this->admin_ui_chat_job_transition(
			array(
				'type'       => 'complete',
				'job_id'     => $job_id,
				'session_id' => $session_id,
				'answer'     => $reply,
				'actor_id'   => sanitize_text_field( (string) $actor_id ),
				'delivery_retry' => ! empty( $delivery_retry ),
			)
		);
    }

	public function retry_admin_ui_chat_delivery( $job_id, $session_id = '', $reply = '', $actor_id = '' ) {
		$payload = $this->admin_ui_chat_job_transition(
			array(
				'type'   => 'delivery_payload',
				'job_id' => $job_id,
			)
		);
		$payload    = is_array( $payload ) ? $payload : array();
		$session_id = (string) ( $payload['session'] ?? ( $payload['session_id'] ?? $session_id ) );
		$reply      = (string) ( $payload['answer'] ?? $reply );
		$actor_id   = (string) ( $payload['actor_id'] ?? $actor_id );
		if ( 'delivered' === (string) ( $payload['delivery_status'] ?? '' ) ) {
			$cleaned = $this->admin_ui_chat_job_transition(
				array( 'type' => 'delivered', 'job_id' => $job_id, 'session_id' => $session_id, 'actor_id' => $actor_id )
			);
			return array(
				'success' => (bool) $cleaned,
				'channel' => 'admin_ui',
				'session' => $session_id,
				'error'   => $cleaned ? '' : 'admin_ui_delivery_cleanup_failed',
			);
		}
		if ( 'failed' === (string) ( $payload['status'] ?? '' ) || 'failed' === (string) ( $payload['delivery_status'] ?? '' ) ) {
			$this->admin_ui_chat_job_transition( array( 'type' => 'fail_delivery', 'job_id' => $job_id ) );
			return array( 'success' => false, 'error' => (string) ( $payload['error'] ?? 'admin_ui_chat_delivery_failed' ) );
		}
		if ( ! empty( $payload['delivery_failure_pending'] ) ) {
			$failed = $this->admin_ui_chat_job_transition( array( 'type' => 'fail_delivery', 'job_id' => $job_id ) );
			return array(
				'success' => false,
				'error'   => $failed ? 'admin_ui_chat_delivery_failed' : 'admin_ui_chat_delivery_failure_pending',
			);
		}
		if ( '' === trim( (string) $session_id ) || '' === trim( (string) $reply ) || '' === trim( (string) $actor_id ) ) {
			return array( 'success' => false, 'error' => 'admin_ui_delivery_payload_missing' );
		}

		return $this->send_admin_ui_channel_message(
			$session_id,
			$reply,
			array(
				'session_id'       => $session_id,
				'admin_ui_job_id'  => $job_id,
				'actor_message_id' => $actor_id,
				'current_prompt'    => (string) ( $payload['message'] ?? '' ),
				'request_id'        => (string) ( $payload['request_id'] ?? '' ),
				'source'            => 'actor_runtime',
				'admin_ui_delivery_retry' => true,
			)
		);
	}

	private function transition_admin_ui_chat_job( $transition ) {
		return (bool) $this->admin_ui_chat_job_transition( $transition );
	}

	private function admin_ui_chat_job_transition( $transition ) {
		return apply_filters( 'niwa_ai_admin_ui_chat_job_transition', false, is_array( $transition ) ? $transition : array() );
	}

	private function schedule_admin_ui_delivery_retry( $job_id, $session_id, $answer, $actor_id ) {
		$this->admin_ui_chat_job_transition(
			array( 'type' => 'retry_delivery', 'job_id' => $job_id, 'session_id' => $session_id, 'answer' => $answer, 'actor_id' => $actor_id )
		);
	}

	private function recover_admin_ui_actor_delivery( $session_id, $entry ) {
		$entry   = is_array( $entry ) ? $entry : array();
		$context = isset( $entry['context_payload'] ) && is_array( $entry['context_payload'] ) ? $entry['context_payload'] : array();
		$job_id  = isset( $context['admin_ui_job_id'] ) ? sanitize_key( (string) $context['admin_ui_job_id'] ) : '';
		$actor_id = ! empty( $entry['continuation_root_actor_message_id'] )
			? sanitize_text_field( (string) $entry['continuation_root_actor_message_id'] )
			: ( isset( $entry['id'] ) ? sanitize_text_field( (string) $entry['id'] ) : '' );
		if ( '' === $job_id || '' === $actor_id ) {
			return array( 'handled' => false );
		}

		$gate = $this->admin_ui_chat_job_transition(
			array(
				'type'       => 'actor_gate',
				'job_id'     => $job_id,
				'session_id' => $session_id,
				'actor_id'   => $actor_id,
			)
		);
		$gate = is_array( $gate ) ? $gate : array();
		if ( ! empty( $gate['allowed'] ) ) {
			return array( 'handled' => false );
		}
		if ( ! empty( $gate['hold'] ) ) {
			return array( 'handled' => true, 'deferred' => true, 'reason' => 'admin_ui_actor_confirmation_pending' );
		}
		if ( ! empty( $gate['committed'] ) ) {
			$delivery = $this->retry_admin_ui_chat_delivery( $job_id, $session_id, (string) ( $gate['answer'] ?? '' ), $actor_id );
			return ! empty( $delivery['success'] )
				? array( 'handled' => true, 'success' => true, 'recovered_delivery' => true )
				: array( 'handled' => true, 'retry' => true, 'error' => 'admin_ui_delivery_retry_failed' );
		}

		return array( 'handled' => true, 'success' => true, 'ignored' => true, 'reason' => 'admin_ui_continuation_cancelled' );
	}

	public function fail_admin_ui_actor_job( $session_id, $entry, $error = '' ) {
		$entry      = is_array( $entry ) ? $entry : array();
		$context    = isset( $entry['context_payload'] ) && is_array( $entry['context_payload'] ) ? $entry['context_payload'] : array();
		$job_id     = sanitize_key( (string) ( $context['admin_ui_job_id'] ?? '' ) );
		$actor_id   = ! empty( $entry['continuation_root_actor_message_id'] )
			? sanitize_text_field( (string) $entry['continuation_root_actor_message_id'] )
			: sanitize_text_field( (string) ( $entry['id'] ?? '' ) );
		$session_id = trim( (string) $session_id );
		if ( '' === $job_id || '' === $actor_id || '' === $session_id ) {
			return false;
		}

		$this->log_turn_debug(
			'admin_ui_actor_execution_failed',
			array(
				'job_id'   => $job_id,
				'actor_id' => $actor_id,
				'error'    => substr( trim( (string) $error ), 0, 500 ),
			)
		);
		$failed = $this->admin_ui_chat_job_transition(
			array(
				'type'       => 'fail',
				'job_id'     => $job_id,
				'session_id' => $session_id,
				'actor_id'   => $actor_id,
				'error'      => 'admin_ui_actor_execution_failed',
				'message'    => __( 'Admin chat nije uspeo da zavrsi obradu poruke. Neke trazene radnje su mozda izvrsene; prvo proveri trenutno stanje pre ponovnog slanja.', 'niwa-ai' ),
			)
		);
		if ( $failed ) {
			return true;
		}

		$gate = $this->admin_ui_chat_job_transition(
			array( 'type' => 'actor_gate', 'job_id' => $job_id, 'session_id' => $session_id, 'actor_id' => $actor_id )
		);
		return is_array( $gate ) && ( ! empty( $gate['cancelled'] ) || ! empty( $gate['committed'] ) );
	}

    private function get_founder_session_id( $chat_id ) {
        if ( 0 === strpos( (string) $chat_id, 'whatsapp:' ) ) {
            $telegram_chat_id = $this->get_configured_telegram_owner_chat_id();
            if ( '' !== $telegram_chat_id ) {
                return 'admin-telegram-' . preg_replace( '/[^0-9\\-]/', '', $telegram_chat_id );
            }
        }

        $helper = $this->get_founder_identity_helper();
        return $helper ? $helper->get_founder_session_id( $chat_id ) : 'admin-telegram-' . preg_replace( '/[^0-9\\-]/', '', (string) $chat_id );
    }

    private function get_configured_telegram_owner_chat_id() {
        if ( ! $this->telegram || ! method_exists( $this->telegram, 'get_settings' ) ) {
            return '';
        }

        $settings = (array) $this->telegram->get_settings();
        $chat_id  = isset( $settings['owner_chat_id'] ) ? preg_replace( '/[^0-9\\-]/', '', (string) $settings['owner_chat_id'] ) : '';

        return is_string( $chat_id ) ? $chat_id : '';
    }

    private function normalize_command_text( $text ) {
        $utils = $this->get_command_utils();
        return $utils ? $utils->normalize_command_text( $text ) : trim( (string) $text );
    }

    private function build_remove_keyboard() {
        $utils = $this->get_command_utils();
        return $utils ? $utils->build_remove_keyboard() : array( 'remove_keyboard' => true );
    }

    private function prepare_media_only_message( $message, &$context ) {
        $message['remember_as'] = '[image]';

        if ( ! $this->chat_agent || ! method_exists( $this->chat_agent, 'resolve_media_only_turn' ) ) {
            $message['text'] = 'Analiziraj prilozenu sliku i odgovori founderu prirodno i korisno.';
            return $message;
        }

        $resolution = (array) $this->chat_agent->resolve_media_only_turn( $context );
        $prompt     = isset( $resolution['prompt'] ) ? trim( (string) $resolution['prompt'] ) : '';
        if ( '' === $prompt ) {
            $prompt = 'Analiziraj prilozenu sliku i odgovori founderu prirodno i korisno.';
        }

        $message['text'] = $prompt;
        if ( ! empty( $resolution['remember_as'] ) ) {
            $message['remember_as'] = (string) $resolution['remember_as'];
        }
        if ( ! empty( $resolution['force_action'] ) ) {
            $context['force_action'] = true;
            $context['suppress_image_analysis'] = true;
        }

        return $message;
    }

    private function should_use_telegram_realtime_transport( $context ) {
        $context = is_array( $context ) ? $context : array();
        $channel = isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '';
        $chat_id = isset( $context['chat_id'] ) ? trim( (string) $context['chat_id'] ) : '';
		$source  = isset( $context['source'] ) ? sanitize_key( (string) $context['source'] ) : '';

		return 'telegram' === $channel
			&& '' !== $chat_id
			&& empty( $context['is_daily_report'] )
			&& 'daily_report' !== $source;
    }

    private function begin_turn_stream_feedback( $context ) {
        $lifecycle = $this->get_turn_stream_lifecycle();
        if ( $lifecycle ) {
            $lifecycle->begin( $context );
        }
    }

    private function build_turn_progress_callback( $chat_id, $message_id = 0 ) {
        $builder = $this->get_turn_progress_callback_builder();
        return $builder ? $builder->build( $chat_id, $message_id ) : null;
    }

    private function compose_turn_progress_callbacks( $first, $second ) {
        $callbacks = array_values( array_filter( array( $first, $second ), 'is_callable' ) );
        if ( empty( $callbacks ) ) {
            return null;
        }

        return function ( $event = '', $payload = array() ) use ( $callbacks ) {
            foreach ( $callbacks as $callback ) {
                call_user_func( $callback, $event, is_array( $payload ) ? $payload : array() );
            }
        };
    }

    private function refresh_actor_message_lease( $session_id, $message_id ) {
        $keeper = $this->get_actor_runtime_lease_keeper();
        return $keeper ? $keeper->refresh_message_lease( $session_id, $message_id ) : false;
    }

    private function wrap_actor_message_lease_callback( $session_id, $message_id, $progress_callback ) {
        $keeper = $this->get_actor_runtime_lease_keeper();
        return $keeper ? $keeper->wrap_message_lease_callback( $session_id, $message_id, $progress_callback ) : ( is_callable( $progress_callback ) ? $progress_callback : null );
    }

    private function turn_stream_state_key( $chat_id, $message_id = 0 ) {
        return 'niwa_ai_admin_turn_stream_' . md5( (string) $chat_id . '|' . intval( $message_id ) );
    }

    private function get_turn_stream_state( $chat_id, $message_id = 0 ) {
        $chat_id = trim( (string) $chat_id );
        if ( '' === $chat_id ) {
            return array();
        }

        $state = Niwa_AI_Durable_Cache::get( $this->turn_stream_state_key( $chat_id, $message_id ) );
        return is_array( $state ) ? $state : array();
    }

    private function update_turn_stream_state( $chat_id, $message_id, $state ) {
        $chat_id = trim( (string) $chat_id );
        if ( '' === $chat_id ) {
            return;
        }

        Niwa_AI_Durable_Cache::set(
            $this->turn_stream_state_key( $chat_id, $message_id ),
            is_array( $state ) ? $state : array(),
            self::TURN_PROGRESS_STATE_TTL
        );
    }

    private function clear_turn_stream_state( $chat_id, $message_id = 0 ) {
        $chat_id = trim( (string) $chat_id );
        if ( '' === $chat_id ) {
            return;
        }

        Niwa_AI_Durable_Cache::delete( $this->turn_stream_state_key( $chat_id, $message_id ) );
    }

    public function recover_stuck_turn_streams( $stale_after_seconds = 90, $limit = 20 ) {
        $recovery = $this->get_actor_runtime_recovery();
        return $recovery ? $recovery->recover_stuck_turn_streams( $stale_after_seconds, $limit ) : array(
            'success'   => true,
            'scanned'   => 0,
            'recovered' => 0,
            'sessions'  => array(),
            'errors'    => array(),
        );
    }
    private function maybe_flush_turn_stream_buffer( $state, $force = false ) {
        $buffer = $this->get_turn_stream_buffer();
        return $buffer ? $buffer->maybe_flush( $state, $force ) : ( is_array( $state ) ? $state : array() );
    }

    private function turn_stream_starts_with_internal_protocol( $buffer ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->starts_with_internal_protocol( $buffer ) : false;
    }

    private function extract_turn_stream_leading_xml_tag_name( $buffer ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->extract_leading_xml_tag_name( $buffer ) : '';
    }

    private function is_turn_stream_structured_protocol_tag( $tag ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->is_structured_protocol_tag( $tag ) : false;
    }

    private function prepare_turn_stream_flush_payload( $state, $force = false ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->prepare_flush_payload( $state, $force ) : array(
            'flush_raw'        => '',
            'flush_text'       => '',
            'remaining_buffer' => '',
        );
    }

    private function find_turn_stream_split_length( $buffer, $min_chars = 0 ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->find_split_length( $buffer, $min_chars ) : 0;
    }

    private function advance_turn_stream_split_length( $chars, $length ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->advance_split_length( $chars, $length ) : max( 0, intval( $length ) );
    }

    private function is_turn_stream_hard_boundary_char( $char ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->is_hard_boundary_char( $char ) : false;
    }

    private function should_flush_turn_stream_buffer( $state, $force = false ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->should_flush( $state, $force ) : false;
    }

    private function has_turn_stream_flush_boundary( $buffer, $length ) {
        $stream_buffer = $this->get_turn_stream_buffer();
        return $stream_buffer ? $stream_buffer->has_flush_boundary( $buffer, $length ) : false;
    }
    private function finalize_turn_stream_feedback( $context, $reply ) {
        $lifecycle = $this->get_turn_stream_lifecycle();
        return $lifecycle ? $lifecycle->finalize( $context, $reply ) : (string) $reply;
    }

    private function maybe_send_turn_tool_status( $state, $phase, $payload = array() ) {
        $messenger = $this->get_turn_tool_status_messenger();
        return $messenger ? $messenger->maybe_send( $state, $phase, $payload ) : ( is_array( $state ) ? $state : array() );
    }

    private function delete_turn_tool_status_message( $state, $payload = array(), $reason = 'finished' ) {
        $messenger = $this->get_turn_tool_status_messenger();
        return $messenger ? $messenger->delete( $state, $payload, $reason ) : ( is_array( $state ) ? $state : array() );
    }

    private function wait_for_turn_tool_status_minimum_visibility( $record ) {
        $messenger = $this->get_turn_tool_status_messenger();
        return $messenger ? $messenger->wait_for_minimum_visibility( $record ) : array(
            'sent_at'             => 0,
            'min_visible_seconds' => intval( self::TURN_TOOL_STATUS_MIN_VISIBLE_SECONDS ),
            'delay_seconds'       => 0,
            'visible_for_seconds' => 0,
        );
    }

    private function delete_all_turn_tool_status_messages( $state, $reason = 'turn_ended' ) {
        $messenger = $this->get_turn_tool_status_messenger();
        return $messenger ? $messenger->delete_all( $state, $reason ) : ( is_array( $state ) ? $state : array() );
    }

    private function should_show_tooluse_in_telegram_chat() {
        $messenger = $this->get_turn_tool_status_messenger();
        return $messenger ? $messenger->should_show() : false;
    }
    private function remember_turn_progress_locale( $message ) {
        $translator = $this->get_turn_tool_progress_translator();
        if ( $translator ) {
            $translator->remember_locale( $message );
        }
    }

    private function turn_progress_locale_key( $chat_id ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->locale_key( $chat_id ) : 'niwa_ai_admin_turn_locale_' . md5( trim( (string) $chat_id ) );
    }

    private function get_turn_progress_locale( $chat_id ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->get_locale( $chat_id ) : 'en_US';
    }

    private function normalize_turn_progress_locale( $locale ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->normalize_locale( $locale ) : '';
    }

    private function match_turn_progress_locale_by_language( $language ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->match_locale_by_language( $language ) : '';
    }

    private function translate_turn_tool_progress( $chat_id, $tool_name, $phase = 'start' ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->translate( $chat_id, $tool_name, $phase ) : '';
    }

    private function turn_tool_progress_key( $tool_name, $phase = 'start' ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->progress_key( $tool_name, $phase ) : '';
    }

    private function run_with_turn_progress_locale( $locale, $callback ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->run_with_locale( $locale, $callback ) : ( is_callable( $callback ) ? call_user_func( $callback ) : '' );
    }

    private function humanize_tool_name( $tool_name ) {
        $translator = $this->get_turn_tool_progress_translator();
        return $translator ? $translator->humanize_tool_name( $tool_name ) : strtolower( trim( str_replace( '_', ' ', (string) $tool_name ) ) );
    }
    private function text_length( $text ) {
        $utils = $this->get_runtime_utils();
        return $utils ? $utils->text_length( $text ) : strlen( (string) $text );
    }

    private function text_substr( $text, $start, $length = null ) {
        $utils = $this->get_runtime_utils();
        if ( $utils ) {
            return $utils->text_substr( $text, $start, $length );
        }

        return null === $length ? substr( (string) $text, intval( $start ) ) : substr( (string) $text, intval( $start ), intval( $length ) );
    }

    private function begin_typing_feedback( $chat_id ) {
        $typing = $this->get_typing_feedback();
        if ( $typing ) {
            $typing->begin( $chat_id );
        }
    }

    private function keep_typing_feedback( $chat_id ) {
        $typing = $this->get_typing_feedback();
        if ( $typing ) {
            $typing->keep( $chat_id );
        }
    }

    private function end_typing_feedback( $chat_id ) {
        $typing = $this->get_typing_feedback();
        if ( $typing ) {
            $typing->end( $chat_id );
        }
    }

    private function send_typing_indicator( $chat_id ) {
        $chat_id = trim( (string) $chat_id );
        if ( '' === $chat_id ) {
            return false;
        }

        if ( 0 === strpos( $chat_id, 'whatsapp:' ) ) {
            if ( $this->whatsapp && method_exists( $this->whatsapp, 'send_chat_action' ) ) {
                $this->whatsapp->send_chat_action( $chat_id, 'typing' );
                return true;
            }

            return false;
        }

        if ( $this->telegram && method_exists( $this->telegram, 'send_chat_action' ) ) {
            $this->telegram->send_chat_action( $chat_id, 'typing' );
            return true;
        }

        return false;
    }

    public function handle_typing_runner() {
        $typing = $this->get_typing_feedback();
        if ( $typing ) {
            $typing->handle_web_runner();
        }

        status_header( 500 );
        echo 'typing runtime unavailable';
        exit;
    }

    public function run_cli_typing_runner( $chat_id = '', $token = '' ) {
        $typing = $this->get_typing_feedback();
        if ( $typing ) {
            return $typing->run_cli( $chat_id, $token );
        }

        return array(
            'success'   => false,
            'error'     => 'typing_runtime_unavailable',
            'transport' => 'local_cli',
        );
    }

    private function send_typing_indicator_from_run( $run ) {
        $context = isset( $run['context'] ) && is_array( $run['context'] ) ? $run['context'] : array();
        $typing  = $this->get_typing_feedback();
        if ( $typing ) {
            $typing->send_from_run( $run, $this->should_use_telegram_realtime_transport( $context ) );
        }
    }

    private function normalize_outgoing_reply( $reply ) {
        $formatter = $this->get_outgoing_reply_formatter();
        return $formatter ? $formatter->normalize( $reply ) : (string) $reply;
    }

    private function strip_raw_tool_markup_from_reply( $reply ) {
        $formatter = $this->get_outgoing_reply_formatter();
        return $formatter ? $formatter->strip_raw_tool_markup( $reply ) : (string) $reply;
    }

    private function consume_telegram_quote_current_directive( $text ) {
        $formatter = $this->get_outgoing_reply_formatter();
        return $formatter ? $formatter->consume_quote_current_directive( $text ) : array(
            'text'          => (string) $text,
            'quote_current' => false,
        );
    }

    private function extract_callback_command( $callback_data ) {
        $formatter = $this->get_outgoing_reply_formatter();
        return $formatter ? $formatter->extract_callback_command( $callback_data ) : '';
    }

    private function is_stop_command_text( $text ) {
        $utils = $this->get_command_utils();
        return $utils ? $utils->is_stop_command_text( $text ) : false;
    }

    private function stop_runtime_for_session( $session_id, $chat_id = '', $message_id = 0, $source = 'command' ) {
        $stopper = $this->get_runtime_stopper();
        return $stopper
            ? $stopper->stop_session( $session_id, $chat_id, $message_id, $source )
            : 'Runtime stop helper nije dostupan.';
    }

    private function maybe_handle_runtime_command( $text, $context, $normalized_command, $callback_command ) {
        $handler = $this->get_runtime_command_handler();
        return $handler ? $handler->handle( $text, $context, $normalized_command, $callback_command ) : null;
    }
    private function assert_admin_post_nonce( $action, $nonce_key ) {
        $helper = $this->get_admin_request_helper();
        if ( $helper ) {
            $helper->assert_admin_post_nonce( $action, $nonce_key );
        }
    }

    private function redirect_to_settings_page( $notice, $message, $tab = '' ) {
        $helper = $this->get_admin_request_helper();
        if ( $helper ) {
            $helper->redirect_to_settings_page( $notice, $message, $tab );
        }
    }

    private function truncate_text( $text, $max = 90 ) {
        $utils = $this->get_runtime_utils();
        return $utils ? $utils->truncate_text( $text, $max ) : trim( (string) $text );
    }

    /* -------------------------------------------------------------------
     * Loopback request HMAC helpers (C1 security hardening).
     *
     * Instead of sending the raw secret in the URL query string (visible in
     * server access logs), we now send a time-limited HMAC signature in the
     * POST body.  The secret never leaves the server's memory.
     * ------------------------------------------------------------------- */

    /**
     * Build POST body fields for an HMAC-signed loopback request.
     *
     * @param string $prefix   Namespace prefix, e.g. 'niwa_runner'.
     * @param string $secret   The shared secret for this endpoint.
     * @return array  POST body fields (niwa_ts, niwa_sig).
     */
    private function build_loopback_signature_body( $prefix, $secret ) {
        $locks = $this->get_runtime_locks();
        return $locks ? $locks->build_loopback_signature_body( $prefix, $secret ) : array();
    }

    /**
     * Verify an incoming loopback request's HMAC signature.
     *
     * Accepts requests signed within the last 120 seconds to account for
     * network delay on non-blocking wp_remote_post() calls.
     *
     * @param string $prefix       Namespace prefix, e.g. 'niwa_runner'.
     * @param string $secret_override  If empty, the runner secret from Telegram
     *                                 state is used.  Typing endpoint passes the
     *                                 per-chat token directly.
     * @return bool  True when the signature is valid and fresh.
     */
    private function verify_loopback_signature( $prefix, $secret_override = '' ) {
        $locks = $this->get_runtime_locks();
        return $locks ? $locks->verify_loopback_signature( $prefix, $secret_override ) : false;
    }

    /**
     * Acquire a named lock atomically using a single DB query.
     *
     * Uses INSERT IGNORE for new locks and an atomic UPDATE ... WHERE for
     * expired locks, eliminating the race condition between delete_option()
     * and add_option().
     */
    private function acquire_lock( $name, $ttl ) {
        $locks = $this->get_runtime_locks();
        return $locks ? $locks->acquire( $name, $ttl ) : false;
    }

    private function release_lock( $name ) {
        $locks = $this->get_runtime_locks();
        if ( $locks ) {
            $locks->release( $name );
        }
    }

	private function force_release_lock( $name ) {
		$locks = $this->get_runtime_locks();
		if ( $locks ) {
			$locks->force_release( $name );
		}
	}

    private function format_timestamp( $timestamp ) {
        $utils = $this->get_runtime_utils();
        return $utils ? $utils->format_timestamp( $timestamp ) : '-';
    }
}
