string, 'content' => string, ... ] * * @return string */ function salyut_agents_render_transcript( array $messages ): string { if ( empty( $messages ) ) { return ''; } $lines = []; foreach ( $messages as $msg ) { if ( empty( $msg['role'] ) || empty( $msg['content'] ) ) { continue; } $label = $msg['role'] === 'assistant' ? 'Assistant' : 'User'; $lines[] = "{$label}:\n{$msg['content']}\n"; } return trim( implode( "\n", $lines ) ); } /** * Check whether a session has been finalized. * * @param int $post_id * * @return bool */ function salyut_agents_is_finalized( int $post_id ): bool { $value = get_post_meta( $post_id, 'salyut_agents_finalized', true ); return $value === '1'; } /** * Finalize a chat session. * * Marks the session as ended, builds the transcript from the custom * messages table, and writes it to post_content for admin viewing. * Safe to call multiple times — idempotent via meta lock. * * @param int $post_id The salyut_lead post ID. * * @return void */ function salyut_agents_finalize_session( int $post_id ): void { // Guard: wrong post type. if ( get_post_type( $post_id ) !== 'salyut_lead' ) { return; } // Guard: already finalized (string '1' stored consistently). if ( salyut_agents_is_finalized( $post_id ) ) { return; } // Optimistic lock: write a sentinel value so concurrent calls // (Action Scheduler + lazy finalization) don't both proceed. // Note: update_post_meta returns false if value is unchanged, so // we check against the current value explicitly before writing. $current = get_post_meta( $post_id, 'salyut_agents_finalized', true ); if ( $current === 'locking' ) { // Another process is already finalizing. return; } update_post_meta( $post_id, 'salyut_agents_finalized', 'locking' ); // Read messages from the custom table. $messages = salyut_agents_get_session_messages( $post_id ); $transcript = salyut_agents_render_transcript( $messages ); $transcript = wp_kses_post( $transcript ); // Preserve the session header that was written at session creation, // then append the full transcript below a divider. $post = get_post( $post_id ); $original_content = $post ? $post->post_content : ''; preg_match( '/^(.*?)(User:|Assistant:|$)/s', $original_content, $matches ); $header = isset( $matches[1] ) ? trim( $matches[1] ) : ''; $final_content = $header ? $header . "\n\n---\n\n" . $transcript : $transcript; wp_update_post( [ 'ID' => $post_id, 'post_content' => $final_content, ] ); // Release the lock with the final value (string '1' for consistent meta queries). update_post_meta( $post_id, 'salyut_agents_finalized', '1' ); // Store the finalization time so the digest query can find leads by date. // The digest uses post_date from wp_posts rather than this meta, but this // acts as an audit trail for when the session actually completed. update_post_meta( $post_id, 'salyut_agents_finalized_at', time() ); }