fix(backend,orchestrator,chat): l'objectif suspendu guide le premier tour post-restart — #290 regression partielle (register_suspended_turn capture la demande utilisateur exacte au point d'entrée chat agent_send/chat.send avec source diagnostique, RestartRendezvousCoordinator conserve tour/objectif/restart_attempt_id, take_continuation consommable exactement une fois et seulement par la tentative courante: suspended_restart_continuation_prompt place l'objectif utilisateur avant toute consigne generique et StructuredHumanTurnTransition::Resume porte le prompt host-provided injecte au runtime de remplacement; nettoyage du tour suspendu sur succes, echec, annulation et abandon/expiration; diagnostics projet/agent/turn_id/restart_attempt_id/source; rendez-vous inter-agents #289 inchanges; QA verte reconfirmee par Git: cargo check --workspace, backend self_restart_contract_tests 3/3, orchestrator_service restart 10/10, workstate_actions 9/9, app-tauri chat_bridge 31/31, web-server websocket_chat_send 3/3, fmt --check, diff --check; restants: rebuild AppImage + validation live reprise objectif apres restart; carnet #290 iteration regression + passage store qa inclus; scripts/ hors périmètre; aucun push)

This commit is contained in:
2026-09-09 09:17:36 +02:00
parent 93a1c23a29
commit 18c1b01afb
10 changed files with 426 additions and 41 deletions

View File

@ -2620,13 +2620,14 @@ pub async fn agent_send(
.collect();
let session_meta = state.structured_sessions.meta_for_session(&sid);
let turn_guard = session_meta.as_ref().map(|(project_id, agent_id, _, _)| {
backend::StructuredHumanTurnGuard::start(
backend::StructuredHumanTurnGuard::start_with_source(
std::sync::Arc::clone(&state.turn_watch_input),
std::sync::Arc::clone(&state.input_mailbox),
std::sync::Arc::clone(&state.restart_rendezvous),
*project_id,
*agent_id,
&prompt_for_model,
&prompt,
"agent_send",
)
});
@ -2711,6 +2712,7 @@ pub async fn agent_send(
'logical_turn: loop {
let mut concrete_terminal = false;
let mut resume = false;
let mut resume_prompt = None;
for event in current_stream {
let provider_terminal = matches!(
event,
@ -2722,8 +2724,9 @@ pub async fn agent_send(
if let Some(guard) = turn_guard.as_mut() {
match runtime.block_on(guard.transition_after_provider_terminal_async()) {
backend::StructuredHumanTurnTransition::Complete => {}
backend::StructuredHumanTurnTransition::Resume => {
backend::StructuredHumanTurnTransition::Resume { prompt } => {
resume = true;
resume_prompt = Some(prompt);
break;
}
backend::StructuredHumanTurnTransition::Failed(message) => {
@ -2789,7 +2792,10 @@ pub async fn agent_send(
if let Some(guard) = turn_guard.as_mut() {
match runtime.block_on(guard.transition_after_provider_terminal_async()) {
backend::StructuredHumanTurnTransition::Complete => {}
backend::StructuredHumanTurnTransition::Resume => resume = true,
backend::StructuredHumanTurnTransition::Resume { prompt } => {
resume = true;
resume_prompt = Some(prompt);
}
backend::StructuredHumanTurnTransition::Failed(message) => {
terminal_visible = true;
let _ = bridge.send_output_settled(
@ -2824,8 +2830,9 @@ pub async fn agent_send(
);
break;
};
match runtime.block_on(replacement.send(application::SELF_RESTART_CONTINUATION_PROMPT))
{
let resume_prompt = resume_prompt
.unwrap_or_else(|| application::SELF_RESTART_CONTINUATION_PROMPT.to_owned());
match runtime.block_on(replacement.send(&resume_prompt)) {
Ok(stream) => current_stream = stream,
Err(error) => {
terminal_visible = true;