fix(runtime): cancel de background task immédiat et fiable depuis le workstate (#199, QA verte)

Transforme l'annulation d'une background task en contrat immédiat, observable et non-best-effort:

- domain/background_task: états Cancelling/Cancelled/CancelFailed et transitions idempotentes; un kill PTY qui échoue est surfacé en CancelFailed plutôt qu'en succès silencieux.

- infrastructure/runner + store: cancel effectif de la task ciblée, reconcile marque Cancelling même sans live handle, et départage CancelFailed des tâches pending au boot.

- application/background + workstate + orchestrator/wake: chemin de cancel initié depuis le workstate, projection synchronisée de l'état (pas d'attente d'un cycle ambigu).

- frontend: projection workstate de l'état cancelled/cancelling/cancelFailed (cancellingRow/cancelFailedRow/cancelButtons), normalization et services.

Tests QA verts: background_task_cancel, background_task_runner cancel_wins_even_if_output_is_still_available, background_task_store reconcile_marks_cancelling_without_live_handle_cancel_failed_and_pending, workstate.test.tsx.
This commit is contained in:
2026-08-12 15:18:38 +02:00
parent f5e2e302ba
commit 46103397de
24 changed files with 699 additions and 76 deletions

View File

@ -942,12 +942,14 @@ impl OrchestratorService {
owner_agent_id,
rendezvous,
},
BackgroundTaskResult::Failure { .. } => DomainEvent::BackgroundTaskFailed {
project_id: project.id,
task_id,
owner_agent_id,
rendezvous,
},
BackgroundTaskResult::Failure { .. } | BackgroundTaskResult::CancelFailed { .. } => {
DomainEvent::BackgroundTaskFailed {
project_id: project.id,
task_id,
owner_agent_id,
rendezvous,
}
}
BackgroundTaskResult::Cancelled { .. } | BackgroundTaskResult::Expired { .. } => {
DomainEvent::BackgroundTaskCancelled {
project_id: project.id,
@ -1333,9 +1335,11 @@ impl OrchestratorService {
BackgroundTaskState::Queued => "Queued",
BackgroundTaskState::Running => "Running",
BackgroundTaskState::Waiting => "Waiting",
BackgroundTaskState::Cancelling => "Cancelling",
BackgroundTaskState::Completed => "Completed",
BackgroundTaskState::Failed => "Failed",
BackgroundTaskState::Cancelled => "Cancelled",
BackgroundTaskState::CancelFailed => "CancelFailed",
BackgroundTaskState::Expired => "Expired",
};
Ok(OrchestratorOutcome {

View File

@ -317,6 +317,10 @@ fn background_completion_prompt(task: &BackgroundTask) -> String {
"La tâche de fond {} est terminée : annulée.\nRaison : {}",
task.id, reason
),
Some(BackgroundTaskResult::CancelFailed { error, .. }) => format!(
"La tâche de fond {} est terminée : échec de l'annulation.\nErreur : {}",
task.id, error
),
Some(BackgroundTaskResult::Expired { reason, .. }) => format!(
"La tâche de fond {} est terminée : expirée.\nRaison : {}",
task.id, reason