__( 'Insufficient permissions.', 'prautoblogger' ) ), 403 ); } check_ajax_referer( PRAutoBlogger_Gen_History_Page::IO_NONCE_ACTION, 'nonce' ); $run_id = isset( $_POST['run_id'] ) ? sanitize_text_field( wp_unslash( $_POST['run_id'] ) ) : ''; if ( '' === $run_id ) { wp_send_json_error( array( 'message' => __( 'Missing run_id.', 'prautoblogger' ) ), 400 ); } $query = new PRAutoBlogger_Gen_History_Query(); // Run metadata. $run_meta = $query->get_run_meta( $run_id ); if ( null === $run_meta ) { wp_send_json_error( array( 'message' => __( 'Run not found.', 'prautoblogger' ) ), 404 ); } // Build the dossier URL when there is a linked post. $post_id = (int) ( $run_meta['post_id'] ?? 0 ); $dossier_url = $post_id > 0 ? PRAutoBlogger_Dossier_Page::url_for_post( $post_id ) : ''; $run_payload = array( 'run_id' => esc_html( (string) $run_meta['run_id'] ), 'status' => esc_html( (string) $run_meta['status'] ), 'settled_usd' => round( (float) ( $run_meta['settled_usd'] ?? 0 ), 6 ), 'started_at' => esc_html( (string) ( $run_meta['started_at'] ?? '' ) ), 'finished_at' => esc_html( (string) ( $run_meta['finished_at'] ?? '' ) ), 'post_id' => $post_id, 'post_title' => esc_html( (string) ( $run_meta['post_title'] ?? '' ) ), 'dossier_url' => esc_url( $dossier_url ), ); // Per-stage I/O. $raw_stages = $query->get_run_io( $run_id ); $stages = array(); foreach ( $raw_stages as $stage ) { $stages[] = array( // Label derived from Stage_Display_Map for UI coherence. 'stage' => esc_html( $stage['stage'] ), 'label' => esc_html( PRAutoBlogger_Stage_Display_Map::label( $stage['stage'] ) ), 'model' => esc_html( $stage['model'] ), 'agent_role' => esc_html( $stage['agent_role'] ), 'prompt_tokens' => (int) $stage['prompt_tokens'], 'completion_tokens' => (int) $stage['completion_tokens'], 'estimated_cost' => round( (float) $stage['estimated_cost'], 6 ), 'response_status' => esc_html( $stage['response_status'] ), 'error_message' => esc_html( $stage['error_message'] ), 'created_at' => esc_html( $stage['created_at'] ), // Input: null when stage has no request_json (e.g. image stages). // JS renders via textContent — no innerHTML escaping risk. 'input_system' => null !== $stage['input_system'] ? esc_html( $stage['input_system'] ) : null, 'input_user' => null !== $stage['input_user'] ? esc_html( $stage['input_user'] ) : null, // Output: null when no run_stages row (log-only stages). // output_pruned=true when meta_json exists but 'output' is absent (pruned). 'output' => null !== $stage['output'] ? esc_html( $stage['output'] ) : null, 'output_pruned' => (bool) $stage['output_pruned'], ); } wp_send_json_success( array( 'run' => $run_payload, 'stages' => $stages, ) ); } }