fix(domain,application,infrastructure,backend,app-tauri,frontend): Stop demande agent préserve la conversation ouverte — arrêt sans fermeture/clôture du chat, états cohérents après cancel sur les 3 adapters de session, cellule et workstate synchronisés — #343 (QA verte)

This commit is contained in:
2026-09-13 10:03:36 +02:00
parent a1e8a6466f
commit c37638ff24
26 changed files with 1210 additions and 232 deletions

View File

@ -12,16 +12,19 @@ use serde_json::Value;
use application::{
AgentBackgroundTaskState, AgentTicketState, AppError, AppExitWorkGuardDetail,
AppExitWorkGuardState, AttachLiveAgentOutput, BackgroundTaskKindLabel, CellAgentPresentation,
ConversationPreviewStatus, ConversationTurnWorkPreview, ConversationWorkSummary,
CreateProjectInput, CreateProjectOutput, ExecuteSlashCommandOutput, GitGraphOutput,
HealthInput, HealthReport, LayoutKind, ListProjectsOutput, ListSlashCommandsOutput,
LiveRuntimeState, LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState,
RestartCellAgentRuntimeOutput, RestoreOpenProjectFailure, RestoreOpenProjectsOutput,
SlashCommandEffect, StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage,
TurnSource, TurnView,
AppExitWorkGuardState, AttachLiveAgentOutput, BackgroundTaskKindLabel, CancelAgentChatOutput,
CellAgentPresentation, ConversationPreviewStatus, ConversationTurnWorkPreview,
ConversationWorkSummary, CreateProjectInput, CreateProjectOutput, ExecuteSlashCommandOutput,
GitGraphOutput, HealthInput, HealthReport, LayoutKind, ListProjectsOutput,
ListSlashCommandsOutput, LiveRuntimeState, LiveSessionKind, LiveSessionSnapshot,
OpenProjectOutput, ProjectWorkState, RestartCellAgentRuntimeOutput, RestoreOpenProjectFailure,
RestoreOpenProjectsOutput, SlashCommandEffect, StopLiveAgentOutput, TicketWorkSource,
TicketWorkStatus, TurnPage, TurnSource, TurnView,
};
use domain::ports::{
CancelCurrentTurnOutcome, ReplyProgress, ReplyProgressKind, ReplyProgressSource,
ReplyProgressStage,
};
use domain::ports::{ReplyProgress, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage};
use domain::{
AgentBusyState, PageCursor, PageDirection, Project, ProjectId, ProjectSystemPermissions,
ResolvedAgentSystemPermissions, SlashCommand, SystemPermissionSet, TurnRole,
@ -4763,6 +4766,45 @@ pub struct StopLiveAgentResponseDto {
pub already_stopped: bool,
}
/// Response DTO for a non-destructive `cancel_agent_chat` request.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelAgentChatResponseDto {
/// Stable structured session, still live after the operation.
pub session_id: String,
/// Idempotent turn-level result.
pub outcome: CancelAgentChatOutcomeDto,
}
/// Public cancellation outcomes, distinct from a missing-session error.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum CancelAgentChatOutcomeDto {
/// A running request was cancelled.
Cancelled,
/// That request had already been cancelled.
AlreadyCancelled,
/// The live session currently had no running request.
AlreadyCompleted,
}
impl From<CancelAgentChatOutput> for CancelAgentChatResponseDto {
fn from(output: CancelAgentChatOutput) -> Self {
Self {
session_id: output.session_id.to_string(),
outcome: match output.outcome {
CancelCurrentTurnOutcome::Cancelled => CancelAgentChatOutcomeDto::Cancelled,
CancelCurrentTurnOutcome::AlreadyCancelled => {
CancelAgentChatOutcomeDto::AlreadyCancelled
}
CancelCurrentTurnOutcome::AlreadyCompleted => {
CancelAgentChatOutcomeDto::AlreadyCompleted
}
},
}
}
}
/// One stopped runtime in an agent-level stop response.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
@ -6298,6 +6340,22 @@ mod tests {
use serde_json::json;
use uuid::Uuid;
#[test]
fn cancel_agent_chat_outcomes_are_stable_camel_case_values() {
let response = CancelAgentChatResponseDto::from(CancelAgentChatOutput {
session_id: domain::SessionId::from_uuid(Uuid::from_u128(343)),
outcome: CancelCurrentTurnOutcome::AlreadyCancelled,
});
assert_eq!(
serde_json::to_value(response).unwrap(),
json!({
"sessionId": Uuid::from_u128(343).to_string(),
"outcome": "alreadyCancelled"
})
);
}
#[test]
fn settings_dtos_are_additive_and_validate_language_and_icon_contracts() {
let legacy_user: UserSettingsDto = serde_json::from_value(json!({})).unwrap();