<?php

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

class Niwa_AI_Admin_Autonomy_Subagent_Manager {
    const OPTION_KEY = Niwa_AI_Constants::OPT_AUTONOMY_SUBAGENT_JOBS;
    const MAX_SUBAGENT_DEPTH = 3;
    const MAX_QUEUED_JOB_AGE = DAY_IN_SECONDS;
    const MAX_RUNNING_JOB_IDLE_SECONDS = 900;
    const MAX_CONCURRENT_WORKERS = 5;
    const MAX_CONCURRENT_WRITE_WORKERS = 1;
    const MAX_SAVED_TERMINAL_JOBS = 50;
    const DISPATCH_LEASE_TTL = 60;
    const QUEUE_LOCK_TTL = 420;
    const MAX_WORKER_CONTINUATIONS = 6;

    private $turn_engine;
    private $session_state;
    private $telegram;
    private $conversation_store;
    private $workspace;
    private $callbacks = array();

    public function __construct( $turn_engine = null, $session_state = null, $telegram = null, $conversation_store = null, $workspace = null ) {
        $this->turn_engine        = $turn_engine;
        $this->session_state      = $session_state;
        $this->telegram           = $telegram;
        $this->conversation_store = $conversation_store;
        $this->workspace          = $workspace;
    }

    public function set_callbacks( $callbacks = array() ) {
        $this->callbacks = is_array( $callbacks ) ? $callbacks : array();
    }

    public function spawn( $bridge, $task, $context = array(), $options = array() ) {
        $task    = trim( (string) $task );
        $context = is_array( $context ) ? $context : array();
        $options = is_array( $options ) ? $options : array();

        if ( '' === $task ) {
            return array(
                'success' => false,
                'error'   => 'Subagent task je prazan.',
            );
        }

        if ( ! $this->turn_engine ) {
            return array(
                'success' => false,
                'error'   => 'Subagent runtime nije ucitan.',
            );
        }

        $depth = isset( $context['subagent_depth'] ) ? max( 0, intval( $context['subagent_depth'] ) ) : 0;
        if ( $depth >= self::MAX_SUBAGENT_DEPTH ) {
            return array(
                'success' => false,
                'error'   => 'Subagent depth limit je dostignut za ovaj turn.',
            );
        }

        $label             = isset( $options['label'] ) ? trim( (string) $options['label'] ) : 'worker';
        $scope             = isset( $options['scope'] ) ? sanitize_key( (string) $options['scope'] ) : 'general';
        $allow_writes      = ! empty( $options['allow_writes'] );
        $parent_session_id = isset( $context['session_id'] ) ? (string) $context['session_id'] : '';
        $child_session_id  = $this->build_child_session_id( $parent_session_id );
        $child_depth       = $depth + 1;
        $parent_turn_id    = isset( $options['parent_turn_id'] ) ? (string) $options['parent_turn_id'] : '';
        $announce          = ! array_key_exists( 'announce', $options ) || ! empty( $options['announce'] );
        $job_id            = $this->queue_job(
            array(
                'label'               => $label,
                'task'                => $task,
                'scope'               => $scope,
                'allow_writes'        => $allow_writes,
                'parent_session_id'   => $parent_session_id,
                'parent_turn_id'      => $parent_turn_id,
                'child_session_id'    => $child_session_id,
                'depth'               => $child_depth,
                'chat_id'             => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'channel'             => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram',
                'reply_to_message_id' => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'attachment_image_url' => isset( $context['attachment_image_url'] ) ? (string) $context['attachment_image_url'] : '',
                'announce'            => $announce,
            )
        );
        if ( '' === $job_id ) {
            return array(
                'success' => false,
                'error'   => 'Subagent queue je trenutno zauzet. Pokusaj ponovo.',
            );
        }

        $wake = $this->wake_background_worker( 'subagent-queued' );

        return array(
            'success'          => true,
            'session_id'       => $child_session_id,
            'job_id'           => $job_id,
            'label'            => $label,
            'scope'            => $scope,
            'allow_writes'     => $allow_writes,
            'summary'          => $this->build_queued_job_summary( false ),
            'error'            => '',
            'mode'             => 'session',
            'status'           => 'queued',
            'child_turn_id'    => '',
            'depth'            => $child_depth,
            'wake'             => $wake,
        );
    }

    public function spawn_session( $bridge, $task, $context = array(), $options = array() ) {
        return $this->spawn( $bridge, $task, $context, $options );
    }

    /**
     * Queue a small staged coding workflow without introducing a second queue.
     *
     * The founder runtime is the planner. Readers run in parallel, one writer
     * waits for every reader, and the reviewer waits for the writer.
     */
    public function start_coding_workflow( $bridge, $args, $context = array(), $options = array() ) {
        unset( $bridge );
        $args    = is_array( $args ) ? $args : array();
        $context = is_array( $context ) ? $context : array();
        $options = is_array( $options ) ? $options : array();

        $objective     = trim( (string) ( $args['objective'] ?? '' ) );
        $writer_task   = trim( (string) ( $args['writer_task'] ?? '' ) );
        $reviewer_task = trim( (string) ( $args['reviewer_task'] ?? '' ) );
        $reader_tasks  = array_values(
            array_filter(
                array_map(
                    static function ( $task ) {
                        return is_scalar( $task ) ? trim( (string) $task ) : '';
                    },
                    isset( $args['reader_tasks'] ) && is_array( $args['reader_tasks'] ) ? $args['reader_tasks'] : array()
                )
            )
        );

        if ( '' === $objective || '' === $writer_task || '' === $reviewer_task || empty( $reader_tasks ) ) {
            return array( 'success' => false, 'error' => 'Coding workflow requires objective, at least one reader task, writer_task, and reviewer_task.' );
        }
        $reader_tasks = array_slice( $reader_tasks, 0, 4 );

        $depth = isset( $context['subagent_depth'] ) ? max( 0, intval( $context['subagent_depth'] ) ) : 0;
        if ( $depth > 0 ) {
            return array( 'success' => false, 'error' => 'Coding workflow can be started only by the main founder runtime.' );
        }
        if ( ! $this->turn_engine ) {
            return array( 'success' => false, 'error' => 'Subagent runtime nije ucitan.' );
        }

        $parent_session_id = (string) ( $context['session_id'] ?? '' );
        $parent_turn_id    = (string) ( $options['turn_id'] ?? '' );
        $scope             = sanitize_key( (string) ( $args['scope'] ?? 'general' ) );
        $workflow_id       = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_workflow_', true );
        $lock              = $this->acquire_mutation_lock( 5 );
        if ( ! $lock ) {
            return array( 'success' => false, 'error' => 'Subagent queue je trenutno zauzet. Pokusaj ponovo.' );
        }

        try {
            $jobs     = $this->get_jobs();
            $existing = $this->find_existing_coding_workflow( $jobs, $parent_turn_id, $objective );
            if ( ! empty( $existing ) ) {
                return array(
                    'success'              => true,
                    'duplicate_suppressed' => true,
                    'workflow_id'          => (string) ( $existing['workflow_id'] ?? '' ),
                    'jobs'                 => $this->build_workflow_job_payloads( $jobs, (string) ( $existing['workflow_id'] ?? '' ) ),
                    'status'               => 'running',
                );
            }

            $common = array(
                'scope'               => $scope,
                'parent_session_id'   => $parent_session_id,
                'parent_turn_id'      => $parent_turn_id,
                'chat_id'             => (string) ( $context['chat_id'] ?? '' ),
                'channel'             => sanitize_key( (string) ( $context['channel'] ?? 'telegram' ) ),
                'reply_to_message_id' => intval( $context['message_id'] ?? 0 ),
                'depth'               => 1,
                'workflow_id'         => $workflow_id,
                'workflow_objective'  => $objective,
            );
            $reader_ids = array();
            foreach ( $reader_tasks as $index => $reader_task ) {
                $reader_id    = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_reader_', true );
                $reader_ids[] = $reader_id;
                $jobs[]       = $this->build_job_record(
                    array_merge(
                        $common,
                        array(
                            'label'            => 'code-reader-' . ( $index + 1 ),
                            'task'             => $this->build_workflow_stage_task( $objective, 'reader', $reader_task ),
                            'allow_writes'     => false,
                            'announce'         => false,
                            'status'           => 'queued',
                            'workflow_role'    => 'reader',
                            'workflow_stage'   => 1,
                            'dependency_ids'   => array(),
                            'child_session_id' => $this->build_child_session_id( $parent_session_id ),
                        )
                    ),
                    $reader_id
                );
            }

            $writer_id = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_writer_', true );
            $jobs[]     = $this->build_job_record(
                array_merge(
                    $common,
                    array(
                        'label'            => 'code-writer',
                        'task'             => $this->build_workflow_stage_task( $objective, 'writer', $writer_task ),
                        'allow_writes'     => true,
                        'announce'         => false,
                        'status'           => 'blocked',
                        'workflow_role'    => 'writer',
                        'workflow_stage'   => 2,
                        'dependency_ids'   => $reader_ids,
                        'child_session_id' => $this->build_child_session_id( $parent_session_id ),
                    )
                ),
                $writer_id
            );

            $reviewer_id = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_reviewer_', true );
            $jobs[]       = $this->build_job_record(
                array_merge(
                    $common,
                    array(
                        'label'            => 'code-reviewer',
                        'task'             => $this->build_workflow_stage_task( $objective, 'reviewer', $reviewer_task ),
                        'allow_writes'     => false,
                        'announce'         => true,
                        'status'           => 'blocked',
                        'workflow_role'    => 'reviewer',
                        'workflow_stage'   => 3,
                        'dependency_ids'   => array( $writer_id ),
                        'child_session_id' => $this->build_child_session_id( $parent_session_id ),
                    )
                ),
                $reviewer_id
            );

            $jobs = $this->prune_jobs( $jobs );
            $this->save_jobs( $jobs );
        } finally {
            $this->release_mutation_lock();
        }

        $wake = $this->wake_background_worker( 'coding-workflow-queued' );
        return array(
            'success'     => true,
            'workflow_id' => $workflow_id,
            'status'      => 'queued',
            'jobs'        => $this->build_workflow_job_payloads( $this->get_jobs(), $workflow_id ),
            'summary'     => 'Coding workflow queued: ' . count( $reader_ids ) . ' readers, one writer, then one reviewer.',
            'wake'        => $wake,
        );
    }

    public function enqueue_scheduled( $bridge, $message, $job = array() ) {
        $message = is_array( $message ) ? $message : array();
        $job     = is_array( $job ) ? $job : array();
        $task    = trim( (string) ( $message['text'] ?? '' ) );
        if ( '' === $task || ! $this->turn_engine ) {
            return array( 'success' => false, 'error' => '' === $task ? 'scheduled_subagent_task_empty' : 'subagent_runtime_unavailable' );
        }

        $runtime_context = isset( $message['context_payload'] ) && is_array( $message['context_payload'] ) ? $message['context_payload'] : array();
		foreach ( array( '_original_context_payload', 'continuation_context', 'cancel_probe', 'turn_progress_callback', 'soft_deadline' ) as $ephemeral_key ) {
			unset( $runtime_context[ $ephemeral_key ] );
		}
        $runtime_context['channel'] = sanitize_key( (string) ( $message['channel'] ?? $runtime_context['channel'] ?? 'telegram' ) );
        $runtime_context['chat_id'] = (string) ( $message['chat_id'] ?? '' );
        $runtime_context['source']  = 'scheduled_self_wake';
        $parent_session_id = trim( (string) ( $job['session_id'] ?? $runtime_context['session_id'] ?? '' ) );
        $child_session_id  = $this->build_child_session_id( $parent_session_id );
        $job_id = $this->queue_job(
            array(
                'label'                 => (string) ( $job['title'] ?? 'scheduled-worker' ),
                'task'                  => $task,
                'scope'                 => 'scheduled',
                'allow_writes'          => true,
                'parent_session_id'     => $parent_session_id,
                'child_session_id'      => $child_session_id,
                'chat_id'               => (string) ( $message['chat_id'] ?? '' ),
                'channel'               => (string) $runtime_context['channel'],
                'depth'                 => 1,
                'announce'              => true,
                'runtime_context'       => $runtime_context,
                'scheduled_actor_entry' => array(
                    'id'              => '',
                    'message'         => $message,
                    'context_payload' => $runtime_context,
                ),
            )
        );
        if ( '' === $job_id ) {
            return array( 'success' => false, 'error' => 'scheduled_subagent_queue_busy' );
        }

        $this->wake_background_worker( 'scheduled-subagent-queued' );
        return array(
            'success'  => true,
            'queued'   => true,
            'actor_id' => 'subagent-' . $job_id,
            'job_id'   => $job_id,
        );
    }

    public function send_to_session( $bridge, $session_id, $task, $context = array(), $options = array() ) {
        $session_id = trim( (string) $session_id );
        $task       = trim( (string) $task );
        $context    = is_array( $context ) ? $context : array();
        $options    = is_array( $options ) ? $options : array();

        if ( '' === $session_id || '' === $task ) {
            return array(
                'success' => false,
                'error'   => 'session_id i task su obavezni za sessions_send.',
            );
        }

        if ( ! $this->turn_engine ) {
            return array(
                'success' => false,
                'error'   => 'Subagent runtime nije ucitan.',
            );
        }

        $label             = isset( $options['label'] ) ? trim( (string) $options['label'] ) : 'worker';
        $scope             = isset( $options['scope'] ) ? sanitize_key( (string) $options['scope'] ) : 'general';
        $allow_writes      = ! empty( $options['allow_writes'] );
        $parent_session_id = isset( $context['session_id'] ) ? (string) $context['session_id'] : '';
        $parent_depth      = isset( $context['subagent_depth'] ) ? max( 0, intval( $context['subagent_depth'] ) ) : 0;
        $child_depth       = $this->resolve_existing_session_depth( $session_id, $parent_depth );
        if ( $child_depth > self::MAX_SUBAGENT_DEPTH ) {
            return array(
                'success' => false,
                'error'   => 'Target session depth je izvan dozvoljene granice.',
            );
        }
        $parent_turn_id    = isset( $options['parent_turn_id'] ) ? (string) $options['parent_turn_id'] : '';
        $announce          = ! empty( $options['announce'] );
        $job_id            = $this->queue_job(
            array(
                'label'               => $label,
                'task'                => $task,
                'scope'               => $scope,
                'allow_writes'        => $allow_writes,
                'parent_session_id'   => $parent_session_id,
                'parent_turn_id'      => $parent_turn_id,
                'child_session_id'    => $session_id,
                'depth'               => $child_depth,
                'chat_id'             => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'channel'             => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram',
                'reply_to_message_id' => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'attachment_image_url' => isset( $context['attachment_image_url'] ) ? (string) $context['attachment_image_url'] : '',
                'announce'            => $announce,
                'resumed'             => true,
            )
        );
        if ( '' === $job_id ) {
            return array(
                'success' => false,
                'error'   => 'Subagent queue je trenutno zauzet. Pokusaj ponovo.',
            );
        }

        $wake = $this->wake_background_worker( 'subagent-queued' );

        return array(
            'success'       => true,
            'session_id'    => $session_id,
            'job_id'        => $job_id,
            'label'         => $label,
            'scope'         => $scope,
            'allow_writes'  => $allow_writes,
            'summary'       => $this->build_queued_job_summary( true ),
            'status'        => 'queued',
            'mode'          => 'session_send',
            'child_turn_id' => '',
            'depth'         => $child_depth,
            'wake'          => $wake,
        );
    }

    public function process_queue( $bridge, $limit = 1 ) {
        $limit = max( 1, min( self::MAX_CONCURRENT_WORKERS, intval( $limit ) ) );
        if ( ! $this->turn_engine ) {
            return array(
                'success' => false,
                'error'   => 'Subagent runtime nije ucitan.',
            );
        }

        if ( ! $this->acquire_lock() ) {
            return array(
                'success'   => true,
                'processed' => 0,
                'note'      => 'subagent_lock',
            );
        }

        try {
            if ( class_exists( 'Niwa_AI_Admin_Autonomy_Process_Runtime' ) && Niwa_AI_Admin_Autonomy_Process_Runtime::can_spawn_local_cli() ) {
                $expired   = count( $this->expire_stale_queued_jobs() );
                $recovered = count( $this->recover_stale_running_jobs() );
                $workflow  = $this->advance_blocked_workflow_jobs();
                $dispatch  = $this->dispatch_queued_workers( $limit );
                $this->prune_saved_jobs();

                return array(
                    'success'    => empty( $dispatch['errors'] ),
                    'processed'  => 0,
                    'dispatched' => intval( $dispatch['dispatched'] ?? 0 ),
                    'announced'  => 0,
                    'recovered'  => $recovered,
                    'expired'    => $expired,
                    'workflow'   => $workflow,
                    'errors'     => isset( $dispatch['errors'] ) ? (array) $dispatch['errors'] : array(),
                    'transport'  => 'local_cli',
                );
            }

            $limit      = 1;
            $processed  = 0;
            $announced  = 0;
            $errors     = array();
            $expired    = count( $this->expire_stale_queued_jobs() );
            $recovered  = count( $this->recover_stale_running_jobs() );
            $workflow   = $this->advance_blocked_workflow_jobs();

            while ( $processed < $limit ) {
                $job = $this->claim_next_queued_job();
                if ( empty( $job['id'] ) ) {
                    break;
                }

                $result    = $this->run_job( $bridge, $job );
                if ( ! empty( $result['needs_continuation'] ) ) {
                    if ( $this->requeue_job_for_continuation( $job['id'], $result ) ) {
                        $this->send_worker_continuation_notice( $job, $result );
                        $this->wake_background_worker( 'subagent-continuation' );
                        $processed++;
                        break;
                    }

                    $result['cancelled'] = false;
                    $result['success']   = false;
                    $result['error']     = 'Worker needs continuation but the continuation limit or queue update blocked automatic resume.';
                }
                $final_job = $this->finalize_job_result( $job['id'], $result );
                if ( empty( $final_job ) ) {
                    $final_job                  = $job;
                    $final_job['status']        = ! empty( $result['cancelled'] ) ? 'cancelled' : ( ! empty( $result['success'] ) ? 'completed' : 'failed' );
                    $final_job['summary']       = isset( $result['summary'] ) ? (string) $result['summary'] : '';
                    $final_job['error']         = isset( $result['error'] ) ? (string) $result['error'] : '';
                    $final_job['child_turn_id'] = isset( $result['child_turn_id'] ) ? (string) $result['child_turn_id'] : '';
                }
				$result = $this->align_result_with_final_job( $final_job, $result );

                $this->record_job_result( $final_job, $result );
				$scheduled = ! empty( $final_job['scheduled_actor_entry'] ) && is_array( $final_job['scheduled_actor_entry'] );
                if ( ! empty( $final_job['announce'] ) && ( ! $scheduled || ! empty( $result['success'] ) ) && $this->announce_job_result( $final_job, $result ) ) {
                    $announced++;
                }
                if ( ! empty( $result['error'] ) ) {
                    $errors[] = (string) $result['error'];
                }

                $workflow = $this->advance_blocked_workflow_jobs();
                $processed++;
            }

            $this->prune_saved_jobs();
            if ( $this->has_pending_jobs() ) {
                $this->wake_background_worker( 'subagent-queue-remaining' );
            }

            return array(
                'success'   => true,
                'processed' => $processed,
                'announced' => $announced,
                'recovered' => $recovered,
                'expired'   => $expired,
                'workflow'  => $workflow,
                'errors'    => $errors,
            );
        } finally {
            $this->release_lock();
        }
    }

    public function process_job( $bridge, $job_id ) {
        $job = $this->claim_dispatched_job( $job_id );
        if ( empty( $job['id'] ) ) {
            return array( 'success' => true, 'processed' => 0, 'note' => 'job_not_claimable' );
        }

        $result = $this->run_job( $bridge, $job, false );
        if ( ! empty( $result['needs_continuation'] ) && $this->requeue_job_for_continuation( $job['id'], $result ) ) {
            $this->send_worker_continuation_notice( $job, $result );
            $this->process_queue( $bridge, self::MAX_CONCURRENT_WORKERS );
            return array( 'success' => true, 'processed' => 1, 'continued' => true );
        }

        $final_job = $this->finalize_job_result( $job['id'], $result );
        if ( empty( $final_job ) ) {
            return array( 'success' => false, 'processed' => 1, 'error' => 'subagent_result_finalize_failed' );
        }
		$result = $this->align_result_with_final_job( $final_job, $result );

        $this->record_job_result( $final_job, $result );
        $scheduled = ! empty( $final_job['scheduled_actor_entry'] ) && is_array( $final_job['scheduled_actor_entry'] );
        $already_delivered = ! empty( $result['scheduled_delivery_tool_successful'] ) || ! empty( $result['scheduled_delivery_complete'] );
        $announced = ! empty( $final_job['announce'] ) && ! $already_delivered && ( ! $scheduled || ! empty( $result['success'] ) ) && $this->announce_job_result( $final_job, $result );
        $scheduled_record = array();
        if ( $scheduled ) {
            $scheduled_result = $result;
            if ( $announced ) {
                $scheduled_result['scheduled_delivery_complete'] = true;
            }
            $scheduled_record = (array) Niwa_AI_Admin_Autonomy_Callbacks::call(
                $this->callbacks,
                'record_scheduled_result',
                array( $final_job, $scheduled_result ),
                array( 'success' => false, 'error' => 'scheduled_result_callback_unavailable' )
            );
        }
        $this->advance_blocked_workflow_jobs();
        $this->prune_saved_jobs();
        $this->process_queue( $bridge, self::MAX_CONCURRENT_WORKERS );

        return array(
            'success'   => empty( $result['error'] ),
            'processed' => 1,
            'announced' => $announced ? 1 : 0,
            'error'     => isset( $result['error'] ) ? (string) $result['error'] : '',
            'scheduled_record' => $scheduled_record,
        );
    }

    public function find_scheduled_job( $scheduled_job_id, $job_uuid, $attempt_token = '' ) {
        foreach ( $this->get_jobs() as $job ) {
            if ( ! is_array( $job ) || empty( $job['scheduled_actor_entry'] ) ) {
                continue;
            }
            $ref = isset( $job['scheduled_actor_entry']['message']['scheduled_job'] ) && is_array( $job['scheduled_actor_entry']['message']['scheduled_job'] )
                ? $job['scheduled_actor_entry']['message']['scheduled_job']
                : array();
            if ( intval( $ref['id'] ?? 0 ) !== intval( $scheduled_job_id ) || ! hash_equals( (string) ( $ref['job_uuid'] ?? '' ), (string) $job_uuid ) ) {
                continue;
            }
            if ( '' !== trim( (string) $attempt_token ) && ! hash_equals( trim( (string) $attempt_token ), trim( (string) ( $ref['attempt_token'] ?? '' ) ) ) ) {
                continue;
            }
            $status = (string) ( $job['status'] ?? '' );
            if ( ! in_array( $status, array( 'queued', 'dispatching', 'running' ), true ) ) {
                continue;
            }
            return array(
                'found'            => true,
                'state'            => in_array( $status, array( 'queued', 'dispatching' ), true ) ? 'pending' : 'active',
                'lease_expires_at' => intval( $job['progress_at'] ?? $job['started_at'] ?? time() ) + $this->get_running_job_idle_limit( $job ),
                'actor_id'         => 'subagent-' . (string) ( $job['id'] ?? '' ),
            );
        }

        return array( 'found' => false );
    }

	public function cancel_jobs_for_scheduled_job( $scheduled_job_id, $job_uuid, $reason = 'Scheduled task cancelled by founder.' ) {
		$scheduled_job_id = intval( $scheduled_job_id );
		$job_uuid         = trim( (string) $job_uuid );
		if ( $scheduled_job_id <= 0 || '' === $job_uuid ) {
			return array( 'success' => false, 'cancelled' => 0, 'signaled' => 0, 'error' => 'scheduled_job_reference_missing' );
		}

		if ( ! $this->acquire_mutation_lock( 3 ) ) {
			return array( 'success' => false, 'cancelled' => 0, 'signaled' => 0, 'error' => 'subagent_queue_locked' );
		}

		$cancelled = 0;
		$signaled  = 0;
		try {
			$jobs = $this->get_jobs();
			$now  = time();
			foreach ( $jobs as $index => $job ) {
				$ref = isset( $job['scheduled_actor_entry']['message']['scheduled_job'] ) && is_array( $job['scheduled_actor_entry']['message']['scheduled_job'] )
					? $job['scheduled_actor_entry']['message']['scheduled_job']
					: array();
				if ( intval( $ref['id'] ?? 0 ) !== $scheduled_job_id || ! hash_equals( $job_uuid, (string) ( $ref['job_uuid'] ?? '' ) ) ) {
					continue;
				}

				$status = (string) ( $job['status'] ?? '' );
				if ( in_array( $status, array( 'queued', 'dispatching' ), true ) ) {
					$jobs[ $index ] = $this->build_cancelled_job( $job, $reason, false, $now );
					$cancelled++;
				} elseif ( 'running' === $status ) {
					$jobs[ $index ] = $this->build_cancelled_job( $job, $reason, true, $now );
					$signaled++;
				}
			}
			$this->save_jobs( $jobs );
		} finally {
			$this->release_mutation_lock();
		}

		return array( 'success' => true, 'cancelled' => $cancelled, 'signaled' => $signaled );
	}

    public function has_pending_jobs( $parent_session_id = '' ) {
        $parent_session_id = trim( (string) $parent_session_id );
        foreach ( $this->get_jobs() as $job ) {
            if ( ! is_array( $job ) ) {
                continue;
            }
            if ( '' !== $parent_session_id && (string) ( isset( $job['parent_session_id'] ) ? $job['parent_session_id'] : '' ) !== $parent_session_id ) {
                continue;
            }
            $status = isset( $job['status'] ) ? (string) $job['status'] : '';
            if ( 'queued' === $status || ( 'dispatching' === $status && $this->is_dispatch_job_stale( $job ) ) ) {
                return true;
            }
            if ( 'running' === $status && $this->is_running_job_stale( $job ) ) {
                return true;
            }

        }

        return false;
    }

    public function list_jobs_for_session( $parent_session_id = '', $limit = 12, $include_completed = true ) {
        $parent_session_id = trim( (string) $parent_session_id );
        $limit             = max( 1, min( 50, intval( $limit ) ) );
        $jobs              = array();

        foreach ( $this->get_jobs() as $job ) {
            if ( ! is_array( $job ) ) {
                continue;
            }
            if ( '' !== $parent_session_id && (string) $job['parent_session_id'] !== $parent_session_id ) {
                continue;
            }
            if ( ! $include_completed && in_array( (string) $job['status'], array( 'completed', 'failed', 'cancelled' ), true ) ) {
                continue;
            }
            $jobs[] = $this->build_job_payload( $job );
        }

        usort(
            $jobs,
            function ( $a, $b ) {
                return intval( isset( $b['created_at'] ) ? $b['created_at'] : 0 ) <=> intval( isset( $a['created_at'] ) ? $a['created_at'] : 0 );
            }
        );

        return array_slice( $jobs, 0, $limit );
    }

    public function control( $action, $args = array() ) {
        $action = sanitize_key( (string) $action );
        $args   = is_array( $args ) ? $args : array();

        if ( 'list' === $action ) {
            return array(
                'success' => true,
                'jobs'    => $this->list_jobs_for_session(
                    isset( $args['parent_session_id'] ) ? (string) $args['parent_session_id'] : '',
                    isset( $args['limit'] ) ? intval( $args['limit'] ) : 12,
                    ! isset( $args['include_completed'] ) || ! empty( $args['include_completed'] )
                ),
            );
        }

        if ( in_array( $action, array( 'kill_all', 'cancel_all', 'stop' ), true ) ) {
            $parent_session_id = isset( $args['parent_session_id'] ) ? (string) $args['parent_session_id'] : '';
            $result = $this->cancel_jobs_for_parent_session( $parent_session_id, 'Cancelled by founder.' );
            $this->advance_blocked_workflow_jobs();
            return $result;
        }

        $job_id = isset( $args['job_id'] ) ? trim( (string) $args['job_id'] ) : '';
        if ( '' === $job_id ) {
            return array(
                'success' => false,
                'error'   => 'Subagent job_id je obavezan.',
            );
        }

        $jobs  = $this->get_jobs();
        $index = $this->find_job_index( $jobs, $job_id );
        if ( null === $index ) {
            return array(
                'success' => false,
                'error'   => 'Subagent job nije pronadjen.',
            );
        }

        if ( 'status' === $action ) {
            return array(
                'success' => true,
                'job'     => $this->build_job_payload( $jobs[ $index ] ),
            );
        }

        if ( 'kill' === $action ) {
            $result = $this->control_kill_job( $job_id );
            $this->advance_blocked_workflow_jobs();
            return $result;
        }

        if ( 'steer' === $action ) {
            $instruction = trim( (string) ( isset( $args['instruction'] ) ? $args['instruction'] : '' ) );
            if ( '' === $instruction ) {
                return array(
                    'success' => false,
                    'error'   => 'Steer instruction je prazna.',
                );
            }

            $status = isset( $jobs[ $index ]['status'] ) ? (string) $jobs[ $index ]['status'] : 'queued';
            if ( 'queued' === $status ) {
                return $this->control_steer_queued_job( $job_id, $instruction );
            }

            $followup_id = $this->queue_job(
                array(
                    'label'               => isset( $jobs[ $index ]['label'] ) ? (string) $jobs[ $index ]['label'] : 'worker',
                    'task'                => 'Continue prior subagent work with this steering update: ' . $instruction,
                    'scope'               => isset( $jobs[ $index ]['scope'] ) ? (string) $jobs[ $index ]['scope'] : 'general',
                    'allow_writes'        => ! empty( $jobs[ $index ]['allow_writes'] ),
                    'parent_session_id'   => isset( $jobs[ $index ]['parent_session_id'] ) ? (string) $jobs[ $index ]['parent_session_id'] : '',
                    'parent_turn_id'      => isset( $jobs[ $index ]['parent_turn_id'] ) ? (string) $jobs[ $index ]['parent_turn_id'] : '',
                    'child_session_id'    => isset( $jobs[ $index ]['child_session_id'] ) ? (string) $jobs[ $index ]['child_session_id'] : '',
                    'chat_id'             => isset( $jobs[ $index ]['chat_id'] ) ? (string) $jobs[ $index ]['chat_id'] : '',
                    'channel'             => isset( $jobs[ $index ]['channel'] ) ? (string) $jobs[ $index ]['channel'] : 'telegram',
                    'reply_to_message_id' => isset( $jobs[ $index ]['reply_to_message_id'] ) ? intval( $jobs[ $index ]['reply_to_message_id'] ) : 0,
                    'attachment_image_url' => isset( $jobs[ $index ]['attachment_image_url'] ) ? (string) $jobs[ $index ]['attachment_image_url'] : '',
                    'depth'               => isset( $jobs[ $index ]['depth'] ) ? intval( $jobs[ $index ]['depth'] ) : 1,
                )
            );
            if ( '' === $followup_id ) {
                return array(
                    'success' => false,
                    'error'   => 'Nisam uspela da zakazem follow-up worker zadatak. Pokusaj ponovo.',
                );
            }

            return array(
                'success'      => true,
                'job'          => $this->build_job_payload( $jobs[ $index ] ),
                'followup_job' => $followup_id,
                'summary'      => 'Steer zahtev je zakazan kao follow-up worker task.',
            );
        }

        if ( 'logs' === $action ) {
            $child_session_id = isset( $jobs[ $index ]['child_session_id'] ) ? (string) $jobs[ $index ]['child_session_id'] : '';
            $logs             = $this->session_state && method_exists( $this->session_state, 'get_session_transcript_excerpt' )
                ? $this->session_state->get_session_transcript_excerpt( $child_session_id, isset( $args['limit'] ) ? intval( $args['limit'] ) : 20 )
                : array();

            return array(
                'success' => true,
                'job'     => $this->build_job_payload( $jobs[ $index ] ),
                'logs'    => $logs,
            );
        }

        return array(
            'success' => false,
            'error'   => 'Nepodrzana subagent control akcija.',
        );
    }

    public function cancel_jobs_for_parent_session( $parent_session_id, $reason = 'Cancelled by founder.' ) {
        $parent_session_id = trim( (string) $parent_session_id );
        if ( '' === $parent_session_id ) {
            return array(
                'success' => false,
                'error'   => 'parent_session_id je obavezan za cancel_all.',
            );
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array(
                'success' => false,
                'error'   => 'Subagent queue je trenutno zakljucan. Pokusaj ponovo.',
            );
        }

        $queued_cancelled = 0;
        $running_signaled = 0;
        try {
            $jobs = $this->get_jobs();
            $now  = time();
            foreach ( $jobs as $index => $job ) {
                if ( ! is_array( $job ) || $parent_session_id !== ( isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '' ) ) {
                    continue;
                }

                $status = isset( $job['status'] ) ? (string) $job['status'] : 'queued';
                if ( in_array( $status, array( 'blocked', 'queued', 'dispatching' ), true ) ) {
					$jobs[ $index ] = $this->build_cancelled_job( $job, $reason, false, $now );
                    $queued_cancelled++;
                } elseif ( 'running' === $status ) {
					$jobs[ $index ] = $this->build_cancelled_job( $job, $reason, true, $now );
                    $running_signaled++;
                }
            }

            $this->save_jobs( $jobs );
        } finally {
            $this->release_mutation_lock();
        }

        return array(
            'success'           => true,
            'queued_cancelled'  => $queued_cancelled,
            'running_signaled'  => $running_signaled,
            'summary'           => 'Subagent stop: queued_cancelled=' . $queued_cancelled . ', running_signaled=' . $running_signaled . '.',
        );
    }

    private function build_child_session_id( $parent_session_id ) {
        $root = trim( (string) $parent_session_id );
        if ( '' === $root ) {
            $root = 'admin-telegram-subagent';
        }

        $uuid = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'subagent_', true );
        return $root . ':subagent:' . $uuid;
    }

    private function queue_job( $args ) {
        $args = is_array( $args ) ? $args : array();
        $lock = $this->acquire_mutation_lock( 5 );
        if ( ! $lock ) {
            return '';
        }

        try {
            $jobs   = $this->get_jobs();
            $duplicate_job_id = $this->find_duplicate_parent_turn_job_id( $jobs, $args );
            if ( '' !== $duplicate_job_id ) {
                return $duplicate_job_id;
            }
            $job_id = function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_subagent_', true );
            $jobs[] = $this->build_job_record( $args, $job_id );
            $jobs = $this->prune_jobs( $jobs );
            $this->save_jobs( $jobs );


            return $job_id;
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function build_job_record( $args, $job_id = '' ) {
        $args   = is_array( $args ) ? $args : array();
        $status = sanitize_key( (string) ( $args['status'] ?? 'queued' ) );
        if ( ! in_array( $status, array( 'queued', 'blocked' ), true ) ) {
            $status = 'queued';
        }

        return array(
            'id'                    => '' !== trim( (string) $job_id ) ? (string) $job_id : ( function_exists( 'wp_generate_uuid4' ) ? wp_generate_uuid4() : uniqid( 'niwa_subagent_', true ) ),
            'label'                 => sanitize_text_field( (string) ( $args['label'] ?? 'worker' ) ),
            'task'                  => (string) ( $args['task'] ?? '' ),
            'scope'                 => sanitize_key( (string) ( $args['scope'] ?? 'general' ) ),
            'allow_writes'          => ! empty( $args['allow_writes'] ),
            'parent_session_id'     => sanitize_text_field( (string) ( $args['parent_session_id'] ?? '' ) ),
            'parent_turn_id'        => sanitize_text_field( (string) ( $args['parent_turn_id'] ?? '' ) ),
            'child_session_id'      => sanitize_text_field( (string) ( $args['child_session_id'] ?? '' ) ),
            'chat_id'               => sanitize_text_field( (string) ( $args['chat_id'] ?? '' ) ),
            'channel'               => sanitize_key( (string) ( $args['channel'] ?? 'telegram' ) ),
            'reply_to_message_id'   => intval( $args['reply_to_message_id'] ?? 0 ),
            'attachment_image_url'  => esc_url_raw( (string) ( $args['attachment_image_url'] ?? '' ) ),
            'depth'                 => max( 1, min( self::MAX_SUBAGENT_DEPTH, intval( $args['depth'] ?? 1 ) ) ),
            'announce'              => ! array_key_exists( 'announce', $args ) || ! empty( $args['announce'] ),
            'resumed'               => ! empty( $args['resumed'] ),
            'runtime_context'       => isset( $args['runtime_context'] ) && is_array( $args['runtime_context'] ) ? $args['runtime_context'] : array(),
            'scheduled_actor_entry' => isset( $args['scheduled_actor_entry'] ) && is_array( $args['scheduled_actor_entry'] ) ? $args['scheduled_actor_entry'] : array(),
            'workflow_id'           => sanitize_text_field( (string) ( $args['workflow_id'] ?? '' ) ),
            'workflow_objective'    => (string) ( $args['workflow_objective'] ?? '' ),
            'workflow_role'         => sanitize_key( (string) ( $args['workflow_role'] ?? '' ) ),
            'workflow_stage'        => max( 0, intval( $args['workflow_stage'] ?? 0 ) ),
            'dependency_ids'        => array_values( array_filter( array_map( 'strval', isset( $args['dependency_ids'] ) && is_array( $args['dependency_ids'] ) ? $args['dependency_ids'] : array() ) ) ),
            'dependency_context_attached' => false,
            'status'                => $status,
            'attempts'              => 0,
            'created_at'            => time(),
            'queued_at'             => 'queued' === $status ? time() : 0,
            'started_at'            => 0,
            'finished_at'           => 0,
            'announced_at'          => 0,
            'summary'               => '',
            'error'                 => '',
            'child_turn_id'         => '',
            'continuation_context'  => array(),
            'continuation_count'    => 0,
        );
    }

    private function build_workflow_stage_task( $objective, $role, $task ) {
        $rules = array(
            'reader'   => 'Read and analyze only. Return concrete evidence, file paths, risks, and the smallest recommended change. Do not modify files.',
            'writer'   => 'Use the dependency reports below, make the smallest safe implementation, then run focused lint/tests. You are the only write-enabled worker in this workflow.',
            'reviewer' => 'Review the completed writer result and current code. Do not modify files. Check correctness, regression risk, diff quality, lint/tests, and report PASS or actionable findings.',
        );

        return "Workflow objective:\n" . trim( (string) $objective )
            . "\n\nStage role: " . sanitize_key( (string) $role )
            . "\n" . (string) ( $rules[ $role ] ?? '' )
            . "\n\nStage task:\n" . trim( (string) $task );
    }

    private function find_existing_coding_workflow( $jobs, $parent_turn_id, $objective ) {
        $parent_turn_id = trim( (string) $parent_turn_id );
        $objective      = trim( (string) preg_replace( '/\s+/', ' ', (string) $objective ) );
        if ( '' === $parent_turn_id || '' === $objective ) {
            return array();
        }

        foreach ( (array) $jobs as $job ) {
            if ( ! is_array( $job ) || $parent_turn_id !== trim( (string) ( $job['parent_turn_id'] ?? '' ) ) ) {
                continue;
            }
            if ( in_array( (string) ( $job['status'] ?? '' ), array( 'failed', 'cancelled' ), true ) ) {
                continue;
            }
            $job_objective = trim( (string) preg_replace( '/\s+/', ' ', (string) ( $job['workflow_objective'] ?? '' ) ) );
            if ( '' !== (string) ( $job['workflow_id'] ?? '' ) && $objective === $job_objective ) {
                return $job;
            }
        }

        return array();
    }

    private function build_workflow_job_payloads( $jobs, $workflow_id ) {
        $payloads = array();
        foreach ( (array) $jobs as $job ) {
            if ( is_array( $job ) && (string) ( $job['workflow_id'] ?? '' ) === (string) $workflow_id ) {
                $payloads[] = $this->build_job_payload( $job );
            }
        }
        return $payloads;
    }

    private function find_duplicate_parent_turn_job_id( $jobs, $args ) {
        $args           = is_array( $args ) ? $args : array();
        $parent_turn_id = trim( (string) ( $args['parent_turn_id'] ?? '' ) );
        if ( '' === $parent_turn_id ) {
            return '';
        }

        $task         = trim( (string) preg_replace( '/\s+/', ' ', (string) ( $args['task'] ?? '' ) ) );
        $scope        = sanitize_key( (string) ( $args['scope'] ?? 'general' ) );
        $allow_writes = ! empty( $args['allow_writes'] );
        $resumed      = ! empty( $args['resumed'] );
        foreach ( (array) $jobs as $job ) {
            if ( ! is_array( $job ) || $parent_turn_id !== trim( (string) ( $job['parent_turn_id'] ?? '' ) ) ) {
                continue;
            }
            if ( in_array( (string) ( $job['status'] ?? '' ), array( 'failed', 'cancelled' ), true ) ) {
                continue;
            }
            $job_allow_writes = ! empty( $job['allow_writes'] );
            if ( $scope !== sanitize_key( (string) ( $job['scope'] ?? 'general' ) ) || $allow_writes !== $job_allow_writes ) {
                continue;
            }
            if ( $task !== trim( (string) preg_replace( '/\s+/', ' ', (string) ( $job['task'] ?? '' ) ) ) ) {
                continue;
            }
            if ( $resumed && (string) ( $args['child_session_id'] ?? '' ) !== (string) ( $job['child_session_id'] ?? '' ) ) {
                continue;
            }

            return (string) ( $job['id'] ?? '' );
        }

        return '';
    }

    private function run_job( $bridge, $job, $release_queue_lock_on_shutdown = true ) {
        $job_id                                        = isset( $job['id'] ) ? (string) $job['id'] : '';
        $child_session_id                              = isset( $job['child_session_id'] ) ? (string) $job['child_session_id'] : '';
        $child_history                                 = $this->conversation_store ? $this->conversation_store->get( $child_session_id ) : array();
        $snapshot                                      = $this->session_state && method_exists( $this->session_state, 'get_session_snapshot' )
            ? (array) $this->session_state->get_session_snapshot( $child_session_id )
            : array();
        $child_has_state                               = ! empty( $snapshot )
            && (
                intval( isset( $snapshot['turn_count'] ) ? $snapshot['turn_count'] : 0 ) > 0
                || ! empty( $snapshot['worklog_summary'] )
                || ! empty( $snapshot['sticky_notes'] )
                || ! empty( $snapshot['recent_subagents'] )
            );
        $child_context                                 = array();
        $child_context['session_id']                   = $child_session_id;
        $child_context['history']                      = is_array( $child_history ) ? $child_history : array();
        $child_context['is_new_session']               = empty( $child_history ) && ! $child_has_state;
        $child_context['is_subagent']                  = true;
        $child_context['subagent_depth']               = max( 1, intval( isset( $job['depth'] ) ? $job['depth'] : 1 ) );
        $child_context['subagent_scope']               = isset( $job['scope'] ) ? (string) $job['scope'] : 'general';
        $child_context['subagent_label']               = isset( $job['label'] ) ? (string) $job['label'] : 'worker';
        $child_context['subagent_allow_writes']        = ! empty( $job['allow_writes'] );
        $child_context['subagent_parent_session_id']   = isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '';
        $child_context['force_action']                 = false;
        $child_context['attachment_image_url']         = isset( $job['attachment_image_url'] ) ? (string) $job['attachment_image_url'] : '';
        if ( is_object( $bridge ) && method_exists( $bridge, 'prepare_context' ) ) {
            $child_context = array_merge(
                $child_context,
                (array) $bridge->prepare_context( $child_session_id, $child_context['history'] )
            );
        }
        if ( ! empty( $job['runtime_context'] ) && is_array( $job['runtime_context'] ) ) {
            $child_context = array_merge( $child_context, $job['runtime_context'] );
            $child_context['session_id']                 = $child_session_id;
			$child_context['is_new_session']             = empty( $child_history ) && ! $child_has_state;
            $child_context['is_subagent']                = true;
            $child_context['subagent_parent_session_id'] = isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '';
        }
        $worker_lock_ttl = $this->get_running_job_idle_limit( $job );
        if ( ! empty( $job['continuation_context'] ) && is_array( $job['continuation_context'] ) ) {
            $child_context['continuation_context'] = $job['continuation_context'];
        }
        $job_shutdown_complete = false;
        register_shutdown_function(
            function () use ( &$job_shutdown_complete, $job_id, $release_queue_lock_on_shutdown ) {
                if ( $job_shutdown_complete || '' === $job_id ) {
                    return;
                }

                $error    = error_get_last();
                $is_fatal = $error && in_array( intval( $error['type'] ), array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR ), true );
                $message  = $is_fatal && $error
                    ? 'Worker PHP fatal before completion: ' . basename( (string) $error['file'] ) . ':' . intval( $error['line'] ) . ' ' . substr( (string) $error['message'], 0, 200 )
                    : 'Worker process ended before completion; marked aborted so the queue can recover.';

                $this->mark_running_job_aborted_on_shutdown( $job_id, $message );
                if ( $release_queue_lock_on_shutdown ) {
                    $this->release_lock();
                }

            }
        );

        $result = $this->turn_engine->run(
            $bridge,
            isset( $job['task'] ) ? (string) $job['task'] : '',
            $child_context,
            array(
                'mode'           => 'subagent',
                'allow_writes'   => ! empty( $job['allow_writes'] ),
                'subagent_label' => isset( $job['label'] ) ? (string) $job['label'] : 'worker',
                'subagent_scope' => isset( $job['scope'] ) ? (string) $job['scope'] : 'general',
                'parent_session_id' => isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '',
                'parent_turn_id' => isset( $job['parent_turn_id'] ) ? (string) $job['parent_turn_id'] : '',
                'subagent_depth' => max( 1, intval( isset( $job['depth'] ) ? $job['depth'] : 1 ) ),
                'job_id'         => $job_id,
                'lock_ttl'       => $worker_lock_ttl,
                'cancel_probe'   => function() use ( $job_id ) {
                    return $this->is_job_cancel_requested( $job_id );
                },
                'progress_callback' => function( $event, $payload = array() ) use ( $job_id ) {
                    $summary = '';
                    if ( ! empty( $payload['tool'] ) ) {
                        $summary = 'Tool: ' . (string) $payload['tool'];
                    } elseif ( ! empty( $payload['stage'] ) ) {
                        $summary = 'Model stage: ' . (string) $payload['stage'];
                    }
                    $this->update_job_progress( $job_id, (string) $event, $summary );
                },
            )
        );
        if ( ! empty( $result['cancelled'] ) && 'soft_deadline' === ( isset( $result['cancelled_reason'] ) ? (string) $result['cancelled_reason'] : '' ) && ! empty( $result['context_snapshot'] ) && is_array( $result['context_snapshot'] ) ) {
            $job_shutdown_complete = true;
            return array(
                'success'              => false,
                'cancelled'            => true,
                'needs_continuation'   => true,
                'continuation_reason'  => 'soft_deadline',
                'continuation_context' => $result['context_snapshot'],
                'summary'              => 'Worker reached the safe time window and queued an automatic continuation.',
                'error'                => '',
                'child_turn_id'        => isset( $result['turn_id'] ) ? (string) $result['turn_id'] : '',
            );
        }

        if ( $this->conversation_store ) {
            $user_task = isset( $job['task'] ) ? trim( (string) $job['task'] ) : '';
            $bot_reply = ! empty( $result['success'] ) ? trim( (string) ( isset( $result['reply'] ) ? $result['reply'] : '' ) ) : trim( (string) ( isset( $result['error'] ) ? $result['error'] : '' ) );
            if ( '' !== $user_task ) {
                $this->conversation_store->append( $child_session_id, 'user', $user_task );
            }
            if ( '' !== $bot_reply ) {
                $this->conversation_store->append( $child_session_id, 'bot', $bot_reply );
            }
        }

        $normalized = array(
            'success'       => ! empty( $result['success'] ),
            'cancelled'     => ! empty( $result['cancelled'] ),
            'summary'       => trim( (string) ( isset( $result['reply'] ) ? $result['reply'] : '' ) ),
            'error'         => ! empty( $result['success'] ) ? '' : ( isset( $result['error'] ) ? (string) $result['error'] : '' ),
            'child_turn_id' => isset( $result['turn_id'] ) ? (string) $result['turn_id'] : '',
        );
        foreach ( array( 'reply', 'processor_success', 'processor_error', 'successful_write_execution', 'scheduled_delivery_tool_successful', 'scheduled_delivery_complete', 'scheduled_delivery_child_scheduled', 'task_completion_state', 'extension_pending_approval', 'retry_original_task', 'executed_tools', 'delivery', 'cancelled_reason', 'context_snapshot' ) as $result_key ) {
            if ( array_key_exists( $result_key, $result ) ) {
                $normalized[ $result_key ] = $result[ $result_key ];
            }
        }


        $job_shutdown_complete = true;
        return $normalized;
    }

    private function record_job_result( $job, $result ) {
        if ( ! $this->session_state ) {
            return;
        }

        $this->session_state->record_subagent_result(
            isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '',
            isset( $job['parent_turn_id'] ) ? (string) $job['parent_turn_id'] : '',
            array(
                'id'         => ! empty( $result['child_turn_id'] ) ? (string) $result['child_turn_id'] : ( isset( $job['child_session_id'] ) ? (string) $job['child_session_id'] : '' ),
                'job_id'     => isset( $job['id'] ) ? (string) $job['id'] : '',
                'label'      => isset( $job['label'] ) ? (string) $job['label'] : 'worker',
                'task'       => isset( $job['task'] ) ? (string) $job['task'] : '',
                'status'     => ! empty( $result['cancelled'] ) ? 'cancelled' : ( ! empty( $result['success'] ) ? 'completed' : 'failed' ),
                'summary'    => ! empty( $result['success'] ) ? (string) $result['summary'] : (string) $result['error'],
                'session_id' => isset( $job['child_session_id'] ) ? (string) $job['child_session_id'] : '',
            )
        );

    }

    private function build_queued_job_summary( $resumed = false ) {
        return $resumed
            ? 'Poslala sam nastavak rada u postojecu internu session nit.'
            : 'Pokrenula sam interni worker u pozadini i javicu se cim zavrsi.';
    }

    private function resolve_job_chat_id( $job ) {
        $job     = is_array( $job ) ? $job : array();
        $chat_id = isset( $job['chat_id'] ) ? trim( (string) $job['chat_id'] ) : '';
        if ( '' !== $chat_id ) {
            return $chat_id;
        }

        $parent_session_id = isset( $job['parent_session_id'] ) ? trim( (string) $job['parent_session_id'] ) : '';
        if ( 0 === strpos( $parent_session_id, 'admin-telegram-' ) ) {
            return substr( $parent_session_id, strlen( 'admin-telegram-' ) );
        }

        return '';
    }

    private function wake_background_worker( $reason ) {
        $reason = sanitize_key( (string) $reason );
        if ( '' === $reason ) {
            $reason = 'subagent-queued';
        }

        $scheduled = false;
        if ( function_exists( 'wp_schedule_single_event' ) && class_exists( 'Niwa_AI_Admin_Autonomy' ) ) {
            $scheduled = (bool) wp_schedule_single_event( time() + 3, Niwa_AI_Admin_Autonomy::BACKGROUND_TICK_HOOK );
        }

        $dispatched = false;
        if ( function_exists( 'has_action' ) && function_exists( 'do_action' ) && has_action( 'niwa_ai_admin_autonomy_wake_background' ) ) {
            do_action( 'niwa_ai_admin_autonomy_wake_background', $reason );
            $dispatched = true;
        }

        return array(
            'scheduled'  => $scheduled,
            'dispatched' => $dispatched,
            'reason'     => $reason,
        );
    }

    private function announce_job_result( $job, $result = array() ) {
        $job_id = trim( (string) ( $job['id'] ?? '' ) );
        if ( '' === $job_id || ! $this->claim_job_announcement( $job_id ) ) {
            return false;
        }

        $notified = Niwa_AI_Admin_Autonomy_Callbacks::call(
            $this->callbacks,
            'notify_parent_result',
            array( $job, is_array( $result ) ? $result : array() ),
            false
        );
        if ( ! empty( $notified ) ) {
            $this->mark_job_announced( $job_id );
            return true;
        }

        $chat_id = isset( $job['chat_id'] ) ? trim( (string) $job['chat_id'] ) : '';
        if ( '' === $chat_id || ! $this->telegram ) {
            $this->release_job_announcement( $job_id );
            return false;
        }

        $message = $this->build_announce_message( $job, $result );
        $sent    = $this->telegram->send_message(
            $chat_id,
            $message,
            isset( $job['reply_to_message_id'] ) ? intval( $job['reply_to_message_id'] ) : 0
        );

        if ( empty( $sent['success'] ) ) {
            $this->release_job_announcement( $job_id );
            return false;
        }

        if ( $this->conversation_store && ! empty( $job['parent_session_id'] ) ) {
            $this->conversation_store->append( (string) $job['parent_session_id'], 'bot', $message );
        }

        $this->mark_job_announced( $job_id );
        return true;
    }

    private function build_announce_message( $job, $result = array() ) {
        $result        = is_array( $result ) ? $result : array();
        $label         = isset( $job['label'] ) ? trim( (string) $job['label'] ) : 'worker';
        $task          = isset( $job['task'] ) ? trim( (string) $job['task'] ) : '';
        $status        = isset( $job['status'] ) ? (string) $job['status'] : 'completed';
        $summary       = 'completed' === $status ? trim( (string) ( isset( $job['summary'] ) ? $job['summary'] : '' ) ) : trim( (string) ( isset( $job['error'] ) ? $job['error'] : '' ) );
        $lines         = array();

        if ( 'cancelled' === $status ) {
            $lines[] = 'Zaustavila sam interni worker "' . ( '' !== $label ? $label : 'worker' ) . '".';
        } elseif ( 'completed' === $status ) {
            $lines[] = 'Uspela sam, interni worker "' . ( '' !== $label ? $label : 'worker' ) . '" je uspesno zavrsio zadatak.';
        } else {
            $lines[] = 'Nisam uspela da dovrsim interni worker "' . ( '' !== $label ? $label : 'worker' ) . '".';
        }
        if ( '' !== $task ) {
            $lines[] = 'Zadatak: ' . $this->truncate_text( $task, 180 );
        }
        if ( '' !== $summary ) {
            $lines[] = 'Ishod: ' . $this->truncate_text( $summary, 700 );
        }
        $message = implode( "\n", $lines );
        if ( $this->workspace && method_exists( $this->workspace, 'normalize_assistant_text' ) ) {
            $message = (string) $this->workspace->normalize_assistant_text( $message );
        }

        return $message;
    }

    private function get_jobs() {
		wp_cache_delete( self::OPTION_KEY, 'options' );
        $jobs = get_option( self::OPTION_KEY, array() );
        return is_array( $jobs ) ? $jobs : array();
    }

    private function save_jobs( $jobs ) {
        update_option( self::OPTION_KEY, array_values( (array) $jobs ), false );
    }

    private function dispatch_queued_workers( $limit ) {
        $jobs       = $this->reserve_queued_jobs_for_dispatch( $limit );
        $dispatched = 0;
        $errors     = array();
        $script     = dirname( __DIR__, 2 ) . '/bin/subagent-runner.php';

        foreach ( $jobs as $job ) {
            $job_id = isset( $job['id'] ) ? (string) $job['id'] : '';
            $spawn  = Niwa_AI_Admin_Autonomy_Process_Runtime::spawn_background_php(
                $script,
                array( 'job-id' => $job_id ),
                dirname( __DIR__, 2 ),
                $this->get_running_job_idle_limit( $job ) + 60
            );
            if ( empty( $spawn['success'] ) ) {
                $this->return_dispatch_to_queue( $job_id, isset( $spawn['error'] ) ? (string) $spawn['error'] : 'subagent_process_spawn_failed' );
                $errors[] = isset( $spawn['error'] ) ? (string) $spawn['error'] : 'subagent_process_spawn_failed';
                continue;
            }

            $this->record_dispatch_pid( $job_id, intval( $spawn['pid'] ?? 0 ) );
            $dispatched++;
        }

        return array( 'dispatched' => $dispatched, 'errors' => $errors );
    }

    private function reserve_queued_jobs_for_dispatch( $limit ) {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        try {
            $jobs       = $this->get_jobs();
            $now        = time();
            $active     = 0;
            $active_writers = 0;
            $sessions   = array();
            $reserved   = array();
            foreach ( $jobs as $job ) {
                $status = is_array( $job ) ? (string) ( $job['status'] ?? '' ) : '';
                if ( in_array( $status, array( 'dispatching', 'running' ), true ) && ! $this->job_is_stale_for_capacity( $job, $now ) ) {
                    $active++;
                    if ( ! empty( $job['allow_writes'] ) ) {
                        $active_writers++;
                    }
                    $sessions[ (string) ( $job['child_session_id'] ?? '' ) ] = true;
                }
            }

            $capacity = min( max( 0, self::MAX_CONCURRENT_WORKERS - $active ), max( 1, intval( $limit ) ) );
            if ( $capacity < 1 ) {
                return array();
            }

            foreach ( $jobs as $index => $job ) {
                if ( count( $reserved ) >= $capacity ) {
                    break;
                }
                if ( ! is_array( $job ) || 'queued' !== (string) ( $job['status'] ?? '' ) || $this->is_queued_job_stale( $job ) ) {
                    continue;
                }
                if ( ! empty( $job['allow_writes'] ) && $active_writers >= self::MAX_CONCURRENT_WRITE_WORKERS ) {
                    continue;
                }
                $session_id = (string) ( $job['child_session_id'] ?? '' );
                if ( '' !== $session_id && ! empty( $sessions[ $session_id ] ) ) {
                    continue;
                }

                $jobs[ $index ]['status']           = 'dispatching';
                $jobs[ $index ]['dispatch_started_at'] = $now;
                $jobs[ $index ]['progress_event']   = 'dispatching';
                $jobs[ $index ]['progress_summary'] = 'Dedicated worker process is starting.';
                $jobs[ $index ]['progress_at']      = $now;
                $reserved[]                         = $jobs[ $index ];
                if ( ! empty( $job['allow_writes'] ) ) {
                    $active_writers++;
                }
                if ( '' !== $session_id ) {
                    $sessions[ $session_id ] = true;
                }
            }

            if ( ! empty( $reserved ) ) {
                $this->save_jobs( $jobs );
            }
            return $reserved;
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function claim_dispatched_job( $job_id ) {
        $lock = $this->acquire_mutation_lock( 5 );
        if ( ! $lock ) {
            return array();
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, trim( (string) $job_id ) );
            if ( null === $index || 'dispatching' !== (string) ( $jobs[ $index ]['status'] ?? '' ) ) {
                return array();
            }

            $now                                      = time();
            $jobs[ $index ]['status']                = 'running';
            $jobs[ $index ]['started_at']            = $now;
            $jobs[ $index ]['attempts']              = intval( $jobs[ $index ]['attempts'] ?? 0 ) + 1;
            $jobs[ $index ]['worker_pid']            = function_exists( 'getmypid' ) ? intval( getmypid() ) : 0;
            $jobs[ $index ]['progress_event']        = 'running';
            $jobs[ $index ]['progress_summary']      = 'Dedicated worker session started.';
            $jobs[ $index ]['progress_at']           = $now;
            $this->save_jobs( $jobs );
            return $jobs[ $index ];
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function record_dispatch_pid( $job_id, $pid ) {
        return $this->update_dispatch_state( $job_id, 'dispatch_pid', max( 0, intval( $pid ) ) );
    }

    private function return_dispatch_to_queue( $job_id, $error ) {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return false;
        }
        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index || 'dispatching' !== (string) ( $jobs[ $index ]['status'] ?? '' ) ) {
                return false;
            }
            $jobs[ $index ]['status']           = 'queued';
            $jobs[ $index ]['dispatch_started_at'] = 0;
            $jobs[ $index ]['progress_event']   = 'dispatch_failed';
            $jobs[ $index ]['progress_summary'] = $this->truncate_text( $error, 180 );
            $jobs[ $index ]['progress_at']      = time();
            $this->save_jobs( $jobs );
            return true;
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function update_dispatch_state( $job_id, $key, $value ) {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return false;
        }
        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index || ! in_array( (string) ( $jobs[ $index ]['status'] ?? '' ), array( 'dispatching', 'running' ), true ) ) {
                return false;
            }
            $jobs[ $index ][ sanitize_key( $key ) ] = $value;
            $this->save_jobs( $jobs );
            return true;
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function is_dispatch_job_stale( $job, $now = 0 ) {
        $now        = $now > 0 ? intval( $now ) : time();
        $dispatched = intval( $job['dispatch_started_at'] ?? 0 );
        return $dispatched > 0 && $dispatched < ( $now - self::DISPATCH_LEASE_TTL );
    }

    private function job_is_stale_for_capacity( $job, $now ) {
        return 'dispatching' === (string) ( $job['status'] ?? '' )
            ? $this->is_dispatch_job_stale( $job, $now )
            : $this->is_running_job_stale( $job, $now );
    }

    private function claim_next_queued_job() {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        try {
            $jobs = $this->get_jobs();
            if ( $this->has_active_running_job( $jobs ) ) {
                return array();
            }

            foreach ( $jobs as $index => $job ) {
                if ( ! is_array( $job ) || 'queued' !== ( isset( $job['status'] ) ? (string) $job['status'] : '' ) ) {
                    continue;
                }

                if ( $this->is_queued_job_stale( $job ) ) {
                    $jobs[ $index ]['status']           = 'cancelled';
                    $jobs[ $index ]['error']            = 'Cancelled because the queued subagent job expired before a background tick could run it.';
                    $jobs[ $index ]['finished_at']      = time();
                    $jobs[ $index ]['progress_event']   = 'expired';
                    $jobs[ $index ]['progress_summary'] = 'Queued worker expired before execution.';
                    $jobs[ $index ]['progress_at']      = time();
                    $this->save_jobs( $jobs );
                    continue;
                }

                $jobs[ $index ]['status']           = 'running';
                $jobs[ $index ]['started_at']       = time();
                $jobs[ $index ]['attempts']         = intval( isset( $job['attempts'] ) ? $job['attempts'] : 0 ) + 1;
                $jobs[ $index ]['progress_event']   = 'running';
                $jobs[ $index ]['progress_summary'] = 'Worker session started.';
                $jobs[ $index ]['progress_at']      = time();
                $this->save_jobs( $jobs );

                return $jobs[ $index ];
            }

            return array();
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function is_queued_job_stale( $job ) {
        $queued_at = isset( $job['queued_at'] ) && intval( $job['queued_at'] ) > 0 ? intval( $job['queued_at'] ) : intval( $job['created_at'] ?? 0 );
        if ( $queued_at <= 0 ) {
            return false;
        }

        return $queued_at < ( time() - self::MAX_QUEUED_JOB_AGE );
    }

    private function is_running_job_stale( $job, $now = 0 ) {
        return '' !== $this->get_running_job_stale_reason( $job, $now );
    }

    private function has_active_running_job( $jobs ) {
        foreach ( (array) $jobs as $job ) {
            if ( ! is_array( $job ) || 'running' !== ( isset( $job['status'] ) ? (string) $job['status'] : '' ) ) {
                continue;
            }

            if ( ! $this->is_running_job_stale( $job ) ) {
                return true;
            }
        }

        return false;
    }

    private function get_running_job_stale_reason( $job, $now = 0 ) {
        $now           = $now > 0 ? intval( $now ) : time();
        $last_progress = isset( $job['progress_at'] ) ? intval( $job['progress_at'] ) : 0;
        $started_at    = isset( $job['started_at'] ) ? intval( $job['started_at'] ) : 0;
        $idle_since    = $last_progress > 0 ? $last_progress : $started_at;
        if ( $idle_since <= 0 ) {
            return '';
        }

        $idle_limit = $this->get_running_job_idle_limit( $job );
        return $idle_since < ( $now - $idle_limit ) ? 'idle_timeout' : '';
    }

    private function get_running_job_idle_limit( $job ) {
        $job   = is_array( $job ) ? $job : array();
        $limit = self::MAX_RUNNING_JOB_IDLE_SECONDS;

        if ( class_exists( 'Niwa_AI_Admin_Autonomy' ) && method_exists( 'Niwa_AI_Admin_Autonomy', 'get_turn_worker_time_limit' ) ) {
            $limit = max( $limit, intval( Niwa_AI_Admin_Autonomy::get_turn_worker_time_limit() ) + 300 );
        }

        return $limit;
    }

    private function requeue_job_for_continuation( $job_id, $result ) {
        $job_id = trim( (string) $job_id );
        $result = is_array( $result ) ? $result : array();
        if ( '' === $job_id ) {
            return false;
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return false;
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index || 'running' !== (string) ( $jobs[ $index ]['status'] ?? '' ) || ! empty( $jobs[ $index ]['cancel_requested'] ) ) {
                return false;
            }

            $count = intval( isset( $jobs[ $index ]['continuation_count'] ) ? $jobs[ $index ]['continuation_count'] : 0 );
            if ( $count >= self::MAX_WORKER_CONTINUATIONS ) {
                return false;
            }

            $now                                      = time();
            $jobs[ $index ]['status']                = 'queued';
            $jobs[ $index ]['started_at']            = 0;
            $jobs[ $index ]['finished_at']           = 0;
            $jobs[ $index ]['summary']               = '';
            $jobs[ $index ]['error']                 = '';
            $jobs[ $index ]['cancel_requested']      = 0;
            $jobs[ $index ]['cancel_requested_at']   = 0;
            $jobs[ $index ]['continuation_count']    = $count + 1;
            $jobs[ $index ]['progress_event']        = 'continuation_queued';
            $jobs[ $index ]['progress_summary']      = isset( $result['summary'] ) && '' !== trim( (string) $result['summary'] )
                ? (string) $result['summary']
                : 'Worker queued an automatic continuation.';
            $jobs[ $index ]['progress_at']           = $now;
            $jobs[ $index ]['child_turn_id']         = isset( $result['child_turn_id'] ) ? (string) $result['child_turn_id'] : '';
            if ( ! empty( $result['continuation_context'] ) && is_array( $result['continuation_context'] ) ) {
                $jobs[ $index ]['continuation_context'] = $result['continuation_context'];
            }
            if ( ! empty( $result['continuation_task'] ) ) {
                $jobs[ $index ]['task'] = (string) $result['continuation_task'];
            }

            $this->save_jobs( $jobs );
            return true;
        } finally {
            $this->release_mutation_lock();
        }
    }


    private function send_worker_continuation_notice( $job, $result ) {
        $chat_id = $this->resolve_job_chat_id( $job );
        if ( '' === $chat_id || ! $this->telegram || ! method_exists( $this->telegram, 'send_message' ) ) {
            return false;
        }

        $count = intval( isset( $job['continuation_count'] ) ? $job['continuation_count'] : 0 ) + 1;
        $text  = "Zadatak je veci od jednog sigurnog runtime prozora.\n";
        $text .= 'Nastavljam automatski u sledecem run-u, faza #' . $count . ".\n";
        $text .= 'Razlog: ' . ( isset( $result['continuation_reason'] ) ? (string) $result['continuation_reason'] : 'continuation' );

        return $this->telegram->send_message(
            $chat_id,
            $text,
            isset( $job['reply_to_message_id'] ) ? intval( $job['reply_to_message_id'] ) : 0
        );
    }

    private function expire_stale_queued_jobs() {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        $expired = array();
        try {
            $jobs = $this->get_jobs();
            foreach ( $jobs as $index => $job ) {
                if ( ! is_array( $job ) || 'queued' !== (string) ( $job['status'] ?? '' ) || ! $this->is_queued_job_stale( $job ) ) {
                    continue;
                }
                $jobs[ $index ] = $this->build_cancelled_job(
                    $job,
                    'Cancelled because the queued subagent job expired before a background worker could run it.',
                    false
                );
                $jobs[ $index ]['progress_event'] = 'expired';
                $expired[] = $jobs[ $index ];
            }
            if ( ! empty( $expired ) ) {
                $this->save_jobs( $jobs );
            }
        } finally {
            $this->release_mutation_lock();
        }

        foreach ( $expired as $job ) {
            $result = array( 'success' => false, 'cancelled' => true, 'error' => (string) ( $job['error'] ?? '' ) );
            $this->record_job_result( $job, $result );
            if ( ! empty( $job['announce'] ) ) {
                $this->announce_job_result( $job, $result );
            }
        }

        return $expired;
    }

    private function recover_stale_running_jobs() {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        $recovered = array();
        try {
            $jobs = $this->get_jobs();
            $now  = time();
            foreach ( $jobs as $index => $job ) {
                if ( ! is_array( $job ) ) {
                    continue;
                }

                $status = isset( $job['status'] ) ? (string) $job['status'] : '';
                if ( 'dispatching' === $status && $this->is_dispatch_job_stale( $job, $now ) ) {
                    $jobs[ $index ]['status']              = 'queued';
                    $jobs[ $index ]['dispatch_started_at'] = 0;
                    $jobs[ $index ]['progress_event']      = 'dispatch_recovered';
                    $jobs[ $index ]['progress_summary']    = 'Worker process did not claim the job; returned to queue.';
                    $jobs[ $index ]['progress_at']         = $now;
                    $recovered[]                           = $jobs[ $index ];
                    continue;
                }
                if ( 'running' !== $status ) {
                    continue;
                }

                $stale_reason = $this->get_running_job_stale_reason( $job, $now );
                if ( '' === $stale_reason ) {
                    continue;
                }

                $error = 'Worker stopped reporting progress before completing; marked stale so parent can recover.';
                $jobs[ $index ]['status']           = 'failed';
                $jobs[ $index ]['error']            = $error;
                $jobs[ $index ]['stale_reason']     = $stale_reason;
                $jobs[ $index ]['stale_detected_at'] = $now;
                $jobs[ $index ]['finished_at']      = $now;
                $jobs[ $index ]['progress_event']   = 'stale_failed';
                $jobs[ $index ]['progress_summary'] = $error;
                $jobs[ $index ]['progress_at']      = $now;
                $recovered[] = $jobs[ $index ];
            }

            if ( ! empty( $recovered ) ) {
                $this->save_jobs( $jobs );
            }
        } finally {
            $this->release_mutation_lock();
        }

        foreach ( $recovered as $job ) {
            if ( 'queued' === (string) ( $job['status'] ?? '' ) ) {
                continue;
            }
            $this->record_job_result(
                $job,
                array(
                    'success'       => false,
                    'cancelled'     => false,
                    'summary'       => '',
                    'error'         => isset( $job['error'] ) ? (string) $job['error'] : 'Worker stopped before completion.',
                    'child_turn_id' => isset( $job['child_turn_id'] ) ? (string) $job['child_turn_id'] : '',
                )
            );
        }

        return $recovered;
    }

    private function mark_running_job_aborted_on_shutdown( $job_id, $message ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return false;
        }

        $lock = $this->acquire_mutation_lock( 2 );
        if ( ! $lock ) {
            return false;
        }

        try {
			$jobs  = $this->get_jobs();
			$index = $this->find_job_index( $jobs, $job_id );
			if ( null === $index || ! isset( $jobs[ $index ] ) || ! is_array( $jobs[ $index ] ) ) {
				return false;
			}

			if ( 'cancelled' === (string) ( $jobs[ $index ]['status'] ?? '' ) || ! empty( $jobs[ $index ]['cancel_requested'] ) ) {
				return true;
			}
			if ( 'running' !== (string) ( $jobs[ $index ]['status'] ?? '' ) ) {
				return false;
			}

			$now                                      = time();
			$jobs[ $index ]['status']                = 'failed';
			$jobs[ $index ]['error']                 = (string) $message;
			$jobs[ $index ]['stale_reason']          = 'shutdown_aborted';
			$jobs[ $index ]['stale_detected_at']     = $now;
			$jobs[ $index ]['finished_at']           = $now;
			$jobs[ $index ]['progress_event']        = 'shutdown_aborted';
			$jobs[ $index ]['progress_summary']      = $this->truncate_text( (string) $message, 180 );
			$jobs[ $index ]['progress_at']           = $now;
			$this->save_jobs( $jobs );

			return true;
		} finally {
			$this->release_mutation_lock();
		}
    }

    private function finalize_job_result( $job_id, $result ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return array();
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index ) {
                return array();
            }
            $current_status = (string) ( $jobs[ $index ]['status'] ?? '' );
            if ( in_array( $current_status, array( 'completed', 'failed', 'cancelled' ), true ) ) {
                return $jobs[ $index ];
            }
            if ( ! empty( $jobs[ $index ]['cancel_requested'] ) ) {
				$jobs[ $index ] = $this->build_cancelled_job(
					$jobs[ $index ],
					(string) ( $jobs[ $index ]['cancelled_reason'] ?? $jobs[ $index ]['error'] ?? 'Worker session cancelled.' ),
					true
				);
				$jobs[ $index ]['child_turn_id'] = isset( $result['child_turn_id'] ) ? (string) $result['child_turn_id'] : '';
				$this->save_jobs( $jobs );
				return $jobs[ $index ];
			}

            $jobs[ $index ]['status']           = ! empty( $result['cancelled'] ) ? 'cancelled' : ( ! empty( $result['success'] ) ? 'completed' : 'failed' );
            $jobs[ $index ]['summary']          = isset( $result['summary'] ) ? (string) $result['summary'] : '';
            $jobs[ $index ]['error']            = isset( $result['error'] ) ? (string) $result['error'] : '';
            $jobs[ $index ]['finished_at']      = time();
            $jobs[ $index ]['child_turn_id']    = isset( $result['child_turn_id'] ) ? (string) $result['child_turn_id'] : '';
            $jobs[ $index ]['progress_event']   = ! empty( $result['cancelled'] ) ? 'cancelled' : ( ! empty( $result['success'] ) ? 'completed' : 'failed' );
            $jobs[ $index ]['progress_summary'] = ! empty( $result['cancelled'] ) ? 'Worker session cancelled.' : ( isset( $result['summary'] ) && '' !== trim( (string) $result['summary'] ) ? (string) $result['summary'] : ( isset( $result['error'] ) ? (string) $result['error'] : '' ) );
            $jobs[ $index ]['progress_at']      = time();
            $this->save_jobs( $jobs );

            return $jobs[ $index ];
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function mark_job_announced( $job_id ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return array();
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array();
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index ) {
                return array();
            }

            $jobs[ $index ]['announced_at']           = time();
            $jobs[ $index ]['announcement_claimed_at'] = 0;
            $this->save_jobs( $jobs );

            return $jobs[ $index ];
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function claim_job_announcement( $job_id ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return false;
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return false;
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index || ! empty( $jobs[ $index ]['announced_at'] ) ) {
                return false;
            }

            $claimed_at = intval( $jobs[ $index ]['announcement_claimed_at'] ?? 0 );
            if ( $claimed_at > time() - 10 * MINUTE_IN_SECONDS ) {
                return false;
            }

            $jobs[ $index ]['announcement_claimed_at'] = time();
            $this->save_jobs( $jobs );
            return true;
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function release_job_announcement( $job_id ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return;
        }

        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return;
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null !== $index && empty( $jobs[ $index ]['announced_at'] ) ) {
                $jobs[ $index ]['announcement_claimed_at'] = 0;
                $this->save_jobs( $jobs );
            }
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function advance_blocked_workflow_jobs() {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array( 'promoted' => 0, 'failed' => 0 );
        }

        try {
            $advanced = $this->advance_workflow_jobs_in_memory( $this->get_jobs() );
            if ( ! empty( $advanced['changed'] ) ) {
                $this->save_jobs( $advanced['jobs'] );
            }
        } finally {
            $this->release_mutation_lock();
        }

        foreach ( (array) ( $advanced['terminal_jobs'] ?? array() ) as $job ) {
            $result = array( 'success' => false, 'error' => (string) ( $job['error'] ?? 'Coding workflow dependency failed.' ) );
            $this->record_job_result( $job, $result );
            if ( empty( $job['announce'] ) || ! empty( $job['announced_at'] ) ) {
                continue;
            }
            $this->announce_job_result( $job, $result );
        }

        return array(
            'promoted' => intval( $advanced['promoted'] ?? 0 ),
            'failed'   => intval( $advanced['failed'] ?? 0 ),
        );
    }

    private function advance_workflow_jobs_in_memory( $jobs ) {
        $jobs          = array_values( (array) $jobs );
        $promoted      = 0;
        $failed        = 0;
        $terminal_jobs = array();
        $changed       = false;

        // ponytail: a bounded in-memory dependency pass is enough for the
        // small workflow (max six jobs); add a DAG service only if this grows.
        for ( $pass = 0; $pass < count( $jobs ) + 1; $pass++ ) {
            $pass_changed = false;
            $by_id        = array();
            foreach ( $jobs as $job ) {
                if ( is_array( $job ) && '' !== (string) ( $job['id'] ?? '' ) ) {
                    $by_id[ (string) $job['id'] ] = $job;
                }
            }

            foreach ( $jobs as $index => $job ) {
                if ( ! is_array( $job ) || 'blocked' !== (string) ( $job['status'] ?? '' ) || '' === (string) ( $job['workflow_id'] ?? '' ) ) {
                    continue;
                }

                $dependencies = isset( $job['dependency_ids'] ) && is_array( $job['dependency_ids'] ) ? array_values( $job['dependency_ids'] ) : array();
                $reports      = array();
                $waiting      = false;
                $blocker      = '';
                foreach ( $dependencies as $dependency_id ) {
                    $dependency_id = (string) $dependency_id;
                    if ( ! isset( $by_id[ $dependency_id ] ) ) {
                        $blocker = 'Required workflow dependency is missing: ' . $dependency_id;
                        break;
                    }
                    $dependency = $by_id[ $dependency_id ];
                    $status     = (string) ( $dependency['status'] ?? '' );
                    if ( in_array( $status, array( 'failed', 'cancelled' ), true ) ) {
                        $blocker = 'Workflow dependency ' . (string) ( $dependency['label'] ?? $dependency_id ) . ' ended with status ' . $status . '.';
                        break;
                    }
                    if ( 'completed' !== $status ) {
                        $waiting = true;
                        break;
                    }
                    $reports[] = $this->build_workflow_dependency_report( $dependency );
                }

                if ( $waiting ) {
                    continue;
                }
                if ( '' !== $blocker ) {
                    $now                                      = time();
                    $jobs[ $index ]['status']                = 'failed';
                    $jobs[ $index ]['error']                 = $blocker;
                    $jobs[ $index ]['finished_at']           = $now;
                    $jobs[ $index ]['progress_event']        = 'dependency_failed';
                    $jobs[ $index ]['progress_summary']      = $blocker;
                    $jobs[ $index ]['progress_at']           = $now;
                    $terminal_jobs[]                         = $jobs[ $index ];
                    $failed++;
                    $changed       = true;
                    $pass_changed  = true;
                    continue;
                }

                if ( ! empty( $reports ) && empty( $jobs[ $index ]['dependency_context_attached'] ) ) {
                    $jobs[ $index ]['task'] .= "\n\nCompleted dependency reports:\n\n" . implode( "\n\n", $reports );
                    $jobs[ $index ]['dependency_context_attached'] = true;
                }
                $now                                      = time();
                $jobs[ $index ]['status']                = 'queued';
                $jobs[ $index ]['queued_at']             = $now;
                $jobs[ $index ]['progress_event']        = 'dependencies_completed';
                $jobs[ $index ]['progress_summary']      = 'All workflow dependencies completed; worker queued.';
                $jobs[ $index ]['progress_at']           = $now;
                $promoted++;
                $changed      = true;
                $pass_changed = true;
            }

            if ( ! $pass_changed ) {
                break;
            }
        }

        return compact( 'jobs', 'promoted', 'failed', 'terminal_jobs', 'changed' );
    }

    private function build_workflow_dependency_report( $job ) {
        $job     = is_array( $job ) ? $job : array();
        $summary = trim( (string) ( $job['summary'] ?? $job['error'] ?? '' ) );
        return '[' . (string) ( $job['label'] ?? 'worker' ) . ' | ' . (string) ( $job['status'] ?? '' ) . ']'
            . "\nSession: " . (string) ( $job['child_session_id'] ?? '' )
            . "\nTask: " . $this->truncate_text( (string) ( $job['task'] ?? '' ), 600 )
            . "\nReport: " . $this->truncate_text( $summary, 3000 );
    }

    private function prune_saved_jobs() {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return;
        }

        try {
            $this->save_jobs( $this->prune_jobs( $this->get_jobs() ) );
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function control_kill_job( $job_id ) {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array(
                'success' => false,
                'error'   => 'Subagent queue je trenutno zakljucan. Pokusaj ponovo.',
            );
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index ) {
                return array(
                    'success' => false,
                    'error'   => 'Subagent job nije pronadjen.',
                );
            }

            $status = isset( $jobs[ $index ]['status'] ) ? (string) $jobs[ $index ]['status'] : 'queued';
            if ( in_array( $status, array( 'blocked', 'queued', 'dispatching' ), true ) ) {
				$jobs[ $index ] = $this->build_cancelled_job( $jobs[ $index ], 'Cancelled by founder.', false );
                $this->save_jobs( $jobs );

                return array(
                    'success' => true,
                    'job'     => $this->build_job_payload( $jobs[ $index ] ),
                    'summary' => 'Queued subagent je otkazan.',
                );
            }

            if ( 'running' === $status ) {
				$jobs[ $index ] = $this->build_cancelled_job( $jobs[ $index ], 'Cancelled by founder.', true );
                $this->save_jobs( $jobs );

                return array(
                    'success' => true,
                    'job'     => $this->build_job_payload( $jobs[ $index ] ),
                    'summary' => 'Cancellation request je zabelezen. Ako worker jos nije zavrsio, ne mogu da garantujem instant stop u ovom runtime-u.',
                );
            }

            return array(
                'success' => true,
                'job'     => $this->build_job_payload( $jobs[ $index ] ),
                'summary' => 'Subagent vise nije aktivan.',
            );
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function control_steer_queued_job( $job_id, $instruction ) {
        $lock = $this->acquire_mutation_lock( 3 );
        if ( ! $lock ) {
            return array(
                'success' => false,
                'error'   => 'Subagent queue je trenutno zakljucan. Pokusaj ponovo.',
            );
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index ) {
                return array(
                    'success' => false,
                    'error'   => 'Subagent job nije pronadjen.',
                );
            }

            $jobs[ $index ]['task']       = trim( (string) $jobs[ $index ]['task'] ) . "\n\nSteer update:\n" . $instruction;
            $jobs[ $index ]['steer_note'] = $instruction;
            $this->save_jobs( $jobs );

            return array(
                'success' => true,
                'job'     => $this->build_job_payload( $jobs[ $index ] ),
                'summary' => 'Queued subagent je azuriran novim smernicama.',
            );
        } finally {
            $this->release_mutation_lock();
        }
    }

    private function prune_jobs( $jobs ) {
        $jobs = array_values( (array) $jobs );
        $active   = array();
        $terminal = array();
        foreach ( $jobs as $job ) {
            if ( is_array( $job ) && in_array( (string) ( $job['status'] ?? '' ), array( 'completed', 'failed', 'cancelled' ), true ) ) {
                $terminal[] = $job;
            } else {
                $active[] = $job;
            }
        }

        usort(
            $terminal,
            static function ( $a, $b ) {
                return intval( $b['created_at'] ?? 0 ) <=> intval( $a['created_at'] ?? 0 );
            }
        );

        // Never prune queued/running/blocked work; dependency ids must remain resolvable.
        return array_merge( $active, array_slice( $terminal, 0, self::MAX_SAVED_TERMINAL_JOBS ) );
    }

    private function find_job_index( $jobs, $job_id ) {
        foreach ( (array) $jobs as $index => $job ) {
            if ( ! is_array( $job ) ) {
                continue;
            }
            if ( isset( $job['id'] ) && (string) $job['id'] === (string) $job_id ) {
                return $index;
            }
        }

        return null;
    }

	private function build_cancelled_job( $job, $reason, $request_stop, $now = 0 ) {
		$job                        = is_array( $job ) ? $job : array();
		$now                        = $now > 0 ? intval( $now ) : time();
		$reason                     = '' !== trim( (string) $reason ) ? (string) $reason : 'Worker session cancelled.';
		$job['status']              = 'cancelled';
		$job['error']               = $reason;
		$job['cancelled_reason']    = $reason;
		$job['cancel_requested']    = $request_stop ? 1 : 0;
		$job['cancel_requested_at'] = $request_stop ? $now : 0;
		$job['finished_at']         = $now;
		$job['progress_event']      = 'cancelled';
		$job['progress_summary']    = $reason;
		$job['progress_at']         = $now;
		return $job;
	}

	private function align_result_with_final_job( $job, $result ) {
		$result = is_array( $result ) ? $result : array();
		$status = (string) ( $job['status'] ?? '' );
		if ( 'cancelled' === $status ) {
			$result['success']          = false;
			$result['cancelled']        = true;
			$result['cancelled_reason'] = (string) ( $job['cancelled_reason'] ?? $job['error'] ?? 'Worker session cancelled.' );
			$result['error']            = $result['cancelled_reason'];
		} elseif ( 'failed' === $status ) {
			$result['success']   = false;
			$result['cancelled'] = false;
			$result['error']     = (string) ( $job['error'] ?? 'Worker failed.' );
		} elseif ( 'completed' === $status ) {
			$result['success'] = true;
			$result['error']   = '';
			$result['summary'] = (string) ( $job['summary'] ?? $result['summary'] ?? '' );
		}
		return $result;
	}

    private function build_job_payload( $job ) {
        $job = is_array( $job ) ? $job : array();

        return array(
            'id'                => isset( $job['id'] ) ? (string) $job['id'] : '',
            'label'             => isset( $job['label'] ) ? (string) $job['label'] : 'worker',
            'task'              => isset( $job['task'] ) ? (string) $job['task'] : '',
            'scope'             => isset( $job['scope'] ) ? (string) $job['scope'] : 'general',
            'allow_writes'      => ! empty( $job['allow_writes'] ),
            'status'            => isset( $job['status'] ) ? (string) $job['status'] : 'queued',
            'attempts'          => intval( isset( $job['attempts'] ) ? $job['attempts'] : 0 ),
            'created_at'        => intval( isset( $job['created_at'] ) ? $job['created_at'] : 0 ),
            'started_at'        => intval( isset( $job['started_at'] ) ? $job['started_at'] : 0 ),
            'finished_at'       => intval( isset( $job['finished_at'] ) ? $job['finished_at'] : 0 ),
            'announced_at'      => intval( isset( $job['announced_at'] ) ? $job['announced_at'] : 0 ),
            'progress_at'       => intval( isset( $job['progress_at'] ) ? $job['progress_at'] : 0 ),
            'progress_event'    => isset( $job['progress_event'] ) ? (string) $job['progress_event'] : '',
            'progress_summary'  => isset( $job['progress_summary'] ) ? (string) $job['progress_summary'] : '',
            'parent_session_id' => isset( $job['parent_session_id'] ) ? (string) $job['parent_session_id'] : '',
            'channel'           => isset( $job['channel'] ) ? (string) $job['channel'] : 'telegram',
            'child_session_id'  => isset( $job['child_session_id'] ) ? (string) $job['child_session_id'] : '',
            'child_turn_id'     => isset( $job['child_turn_id'] ) ? (string) $job['child_turn_id'] : '',
            'summary'           => isset( $job['summary'] ) ? (string) $job['summary'] : '',
            'error'             => isset( $job['error'] ) ? (string) $job['error'] : '',
            'cancel_requested'  => ! empty( $job['cancel_requested'] ),
            'announce'          => ! empty( $job['announce'] ),
            'resumed'           => ! empty( $job['resumed'] ),
            'parent_turn_id'    => isset( $job['parent_turn_id'] ) ? (string) $job['parent_turn_id'] : '',
            'depth'             => max( 1, intval( isset( $job['depth'] ) ? $job['depth'] : 1 ) ),
            'scheduled'         => ! empty( $job['scheduled_actor_entry'] ),
            'workflow_id'       => isset( $job['workflow_id'] ) ? (string) $job['workflow_id'] : '',
            'workflow_role'     => isset( $job['workflow_role'] ) ? (string) $job['workflow_role'] : '',
            'workflow_stage'    => intval( $job['workflow_stage'] ?? 0 ),
            'dependency_ids'    => isset( $job['dependency_ids'] ) && is_array( $job['dependency_ids'] ) ? array_values( $job['dependency_ids'] ) : array(),
        );
    }

    private function resolve_existing_session_depth( $session_id, $parent_depth = 0 ) {
        $session_id = trim( (string) $session_id );
        if ( '' !== $session_id && $this->session_state && method_exists( $this->session_state, 'get_session_snapshot' ) ) {
            $snapshot = (array) $this->session_state->get_session_snapshot( $session_id );
            if ( isset( $snapshot['subagent_depth'] ) ) {
                return max( 1, intval( $snapshot['subagent_depth'] ) );
            }
        }

        return max( 1, min( self::MAX_SUBAGENT_DEPTH, intval( $parent_depth ) + 1 ) );
    }

    public function is_job_cancel_requested( $job_id ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return false;
        }

        $jobs  = $this->get_jobs();
        $index = $this->find_job_index( $jobs, $job_id );
        if ( null === $index ) {
            return false;
        }

        return ! empty( $jobs[ $index ]['cancel_requested'] );
    }

    public function update_job_progress( $job_id, $event, $summary = '' ) {
        $job_id = trim( (string) $job_id );
        if ( '' === $job_id ) {
            return false;
        }

        $lock = $this->acquire_mutation_lock( 2 );
        if ( ! $lock ) {
            return false;
        }

        try {
            $jobs  = $this->get_jobs();
            $index = $this->find_job_index( $jobs, $job_id );
            if ( null === $index ) {
                return false;
            }
			if ( ! in_array( (string) ( $jobs[ $index ]['status'] ?? '' ), array( 'dispatching', 'running' ), true ) || ! empty( $jobs[ $index ]['cancel_requested'] ) ) {
				return false;
			}

            $jobs[ $index ]['progress_event']   = sanitize_key( (string) $event );
            $jobs[ $index ]['progress_summary'] = $this->truncate_text( (string) $summary, 180 );
            $jobs[ $index ]['progress_at']      = time();
            $this->save_jobs( $jobs );

            return true;
        } finally {
            $this->release_mutation_lock();
        }
    }

    /**
     * Acquire the subagent queue lock atomically.
     *
     * Uses INSERT IGNORE + atomic UPDATE instead of the non-atomic
     * delete_option() + add_option() pattern to prevent race conditions.
     */
    private function acquire_lock() {
        global $wpdb;

        $option_name = self::OPTION_KEY . '_lock';
        $now         = time();
        $expires     = $now + self::QUEUE_LOCK_TTL;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $inserted = $wpdb->query(
            $wpdb->prepare(
                "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')",
                $option_name,
                (string) $expires
            )
        );

        if ( $inserted ) {
            wp_cache_delete( $option_name, 'options' );
            return true;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $claimed = $wpdb->query(
            $wpdb->prepare(
                "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND CAST(option_value AS UNSIGNED) < %d",
                (string) $expires,
                $option_name,
                $now
            )
        );

        if ( $claimed ) {
            wp_cache_delete( $option_name, 'options' );
        }

        return $claimed > 0;
    }

    private function acquire_mutation_lock( $wait_seconds = 0 ) {
        $deadline = microtime( true ) + max( 0, intval( $wait_seconds ) );

        do {
            if ( $this->acquire_named_lock( self::OPTION_KEY . '_mutation_lock', 30 ) ) {
                return true;
            }

            if ( microtime( true ) >= $deadline ) {
                break;
            }

            usleep( 150000 );
        } while ( true );

        return false;
    }

    private function release_mutation_lock() {
        delete_option( self::OPTION_KEY . '_mutation_lock' );
    }

    private function acquire_named_lock( $option_name, $ttl ) {
        global $wpdb;

        $option_name = sanitize_key( (string) $option_name );
        $now         = time();
        $expires     = $now + max( 5, intval( $ttl ) );

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $inserted = $wpdb->query(
            $wpdb->prepare(
                "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no')",
                $option_name,
                (string) $expires
            )
        );

        if ( $inserted ) {
            wp_cache_delete( $option_name, 'options' );
            return true;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery
        $claimed = $wpdb->query(
            $wpdb->prepare(
                "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND CAST(option_value AS UNSIGNED) < %d",
                (string) $expires,
                $option_name,
                $now
            )
        );

        if ( $claimed ) {
            wp_cache_delete( $option_name, 'options' );
        }

        return $claimed > 0;
    }

    private function release_lock() {
        delete_option( self::OPTION_KEY . '_lock' );
    }

    private function truncate_text( $text, $limit ) {
        $text  = trim( (string) $text );
        $limit = max( 40, intval( $limit ) );
        if ( '' === $text || strlen( $text ) <= $limit ) {
            return $text;
        }

        return substr( $text, 0, $limit - 3 ) . '...';
    }
}
