| @@ -177,8 +177,9 @@ | ||
| 177 | 177 | return $rate; |
| 178 | 178 | } |
| 179 | 179 | |
| 180 | 180 | $instructions = openstation_agent_get_instructions( $user->ID ); |
| 181 | + $instructions = openstation_agent_apply_vibes( $instructions, (int) $user->ID ); | |
| 181 | 182 | $abilities = openstation_agent_get_abilities( $user->ID ); |
| 182 | 183 | |
| 183 | 184 | list( $tool_defs, $slug_by_name ) = openstation_agent_runner_build_tools( $abilities ); |
| 184 | 185 | |
| @@ -522,11 +523,20 @@ | ||
| 522 | 523 | ? $generated['function_calls'] |
| 523 | 524 | : array(); |
| 524 | 525 | |
| 525 | 526 | if ( empty( $function_calls ) ) { |
| 526 | - $answer = openstation_agent_parse_answer( | |
| 527 | - isset( $generated['text'] ) && is_string( $generated['text'] ) ? $generated['text'] : '' | |
| 528 | - ); | |
| 527 | + // Belt-and-braces behind the same check in | |
| 528 | + // openstation_ai_client_generate(): a final turn with no | |
| 529 | + // extractable text is a failed generation, never a valid | |
| 530 | + // empty answer — without this, the run reports success and | |
| 531 | + // the chat renders nothing. | |
| 532 | + $text = isset( $generated['text'] ) && is_string( $generated['text'] ) ? $generated['text'] : ''; | |
| 533 | + if ( '' === trim( $text ) ) { | |
| 534 | + return openstation_agent_humanize_generate_error( | |
| 535 | + openstation_ai_empty_answer_error( 'The generation produced neither function calls nor answer text.' ) | |
| 536 | + ); | |
| 537 | + } | |
| 538 | + $answer = openstation_agent_parse_answer( $text ); | |
| 529 | 539 | return array( |
| 530 | 540 | 'text' => $answer['text'], |
| 531 | 541 | 'callToActions' => $answer['callToActions'], |
| 532 | 542 | 'toolCalls' => $tool_trace, |
| @@ -886,8 +896,19 @@ | ||
| 886 | 896 | 'detail' => $error->get_error_message(), |
| 887 | 897 | ) |
| 888 | 898 | ); |
| 889 | 899 | } |
| 900 | + if ( 'openstation_ai_empty_answer' === $error->get_error_code() ) { | |
| 901 | + $data = $error->get_error_data(); | |
| 902 | + return new WP_Error( | |
| 903 | + 'openstation_agent_empty_answer', | |
| 904 | + __( 'The model ran out of room before writing its answer — it most likely spent the whole output budget reasoning. Try a narrower request, or try again.', 'desktop-mode' ), | |
| 905 | + array( | |
| 906 | + 'status' => 502, | |
| 907 | + 'detail' => is_array( $data ) && isset( $data['detail'] ) ? (string) $data['detail'] : '', | |
| 908 | + ) | |
| 909 | + ); | |
| 910 | + } | |
| 890 | 911 | return $error; |
| 891 | 912 | } |
| 892 | 913 | |
| 893 | 914 | /** |
| @@ -947,12 +968,40 @@ | ||
| 947 | 968 | // confirmations arrive as renderable buttons, not typed-reply |
| 948 | 969 | // requests. Tool-call turns are unaffected — the model either |
| 949 | 970 | // calls a function or emits the JSON answer. |
| 950 | 971 | openstation_agent_answer_schema(), |
| 951 | - (string) $instructions . "\n\n" . openstation_agent_answer_prompt_appendix() | |
| 972 | + (string) $instructions . "\n\n" . openstation_agent_answer_prompt_appendix(), | |
| 973 | + array( 'source' => 'agents/runner' ) | |
| 952 | 974 | ); |
| 953 | 975 | } |
| 954 | 976 | ); |
| 977 | +} | |
| 978 | + | |
| 979 | +/** | |
| 980 | + * Append an agent's voice line to its instructions. | |
| 981 | + * | |
| 982 | + * **After the instructions, never before.** The two can disagree — a | |
| 983 | + * voice that says "blunt" against a workflow that says "always explain | |
| 984 | + * your reasoning" — and when they do, the workflow should win. Later | |
| 985 | + * text is the one the model weights more heavily, so position is the | |
| 986 | + * whole mechanism here. | |
| 987 | + * | |
| 988 | + * The line is stored through `openstation_agent_sanitize_vibes()`, | |
| 989 | + * which strips line breaks. That matters more than it looks: the | |
| 990 | + * composed prompt marks operator turns, and a multi-line voice line | |
| 991 | + * could otherwise fake a turn boundary. `agentsSecurity.php` pins it. | |
| 992 | + * | |
| 993 | + * @param string $instructions The agent's system prompt. | |
| 994 | + * @param int $user_id Agent user id. | |
| 995 | + * @return string | |
| 996 | + */ | |
| 997 | +function openstation_agent_apply_vibes( $instructions, $user_id ) { | |
| 998 | + $vibes = openstation_agent_get_vibes( $user_id ); | |
| 999 | + if ( '' === $vibes ) { | |
| 1000 | + return $instructions; | |
| 1001 | + } | |
| 1002 | + $line = 'Voice: ' . $vibes; | |
| 1003 | + return '' === $instructions ? $line : $instructions . "\n\n" . $line; | |
| 955 | 1004 | } |
| 956 | 1005 | |
| 957 | 1006 | /** |
| 958 | 1007 | * Run a callback with the WordPress HTTP timeout raised for the |