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();

View File

@ -17,21 +17,21 @@ use application::{
AgentWakeService, AllocateManagedTemp, AppError, AssignIssueAgent, AssignSkillToAgent,
AssignTicketToSprint, AssignedSkillResolver, AttachLiveAgent, AuthenticateSession,
BackgroundCommandArchive, BulkDeleteIssues, BulkUpdateIssuePriority, BulkUpdateIssueStatus,
CancelBackgroundTask, ChangeAgentProfile, CheckEmbedderSuggestion, ClearChatDraft,
CloneOpenCodeProfileFromSeed, CloneProfileFromSeed, CloseProject, CloseTab, CloseTerminal,
CloseTicketAssistant, ConfigureProfiles, ContextGuardUseCases, CreateAgentFromScratch,
CreateAgentFromTemplate, CreateIssue, CreateLayout, CreateMemory, CreateProject, CreateSkill,
CreateSprint, CreateTemplate, DeleteAgent, DeleteEmbedderProfile, DeleteIssue, DeleteLayout,
DeleteMemory, DeleteModelArtifact, DeleteModelServer, DeleteProfile, DeleteSkill, DeleteSprint,
DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift, DetectProfiles,
DismissEmbedderSuggestion, EnsureLocalModelServer, ExecuteSlashCommand, FirstRunState,
GcManagedTemp, GetAppExitWorkGuardState, GetLiveStateLean, GetMemory, GetProjectPermissions,
GetProjectSystemPermissions, GetProjectWorkState, GitBranches, GitCheckout, GitCommit,
GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage, HarvestMemoryFromTurn,
HealthUseCase, ImportChatAttachments, InspectConversation, InstallPluginFromArchive,
InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent, LaunchAgentInput,
LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels, ListDevices,
ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories, ListModelServers,
CancelAgentChat, CancelBackgroundTask, ChangeAgentProfile, CheckEmbedderSuggestion,
ClearChatDraft, CloneOpenCodeProfileFromSeed, CloneProfileFromSeed, CloseProject, CloseTab,
CloseTerminal, CloseTicketAssistant, ConfigureProfiles, ContextGuardUseCases,
CreateAgentFromScratch, CreateAgentFromTemplate, CreateIssue, CreateLayout, CreateMemory,
CreateProject, CreateSkill, CreateSprint, CreateTemplate, DeleteAgent, DeleteEmbedderProfile,
DeleteIssue, DeleteLayout, DeleteMemory, DeleteModelArtifact, DeleteModelServer, DeleteProfile,
DeleteSkill, DeleteSprint, DeleteTemplate, DescribeEmbedderEngines, DetectAgentDrift,
DetectProfiles, DismissEmbedderSuggestion, EnsureLocalModelServer, ExecuteSlashCommand,
FirstRunState, GcManagedTemp, GetAppExitWorkGuardState, GetLiveStateLean, GetMemory,
GetProjectPermissions, GetProjectSystemPermissions, GetProjectWorkState, GitBranches,
GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
HarvestMemoryFromTurn, HealthUseCase, ImportChatAttachments, InspectConversation,
InstallPluginFromArchive, InstallPluginFromDirectory, JsonPluginManifestValidator, LaunchAgent,
LaunchAgentInput, LinkIssues, ListAgents, ListAgentsInput, ListClaudeModels, ListCodexModels,
ListDevices, ListEmbedderProfiles, ListIssues, ListLayouts, ListMemories, ListModelServers,
ListOpenCodeProviders, ListPluginRuntimeContributions, ListPlugins, ListProfiles,
ListProjectFiles, ListProjectPluginSettings, ListProjects, ListResumableAgents, ListSkills,
ListSlashCommands, ListSprints, ListTemplates, LiveAgentRegistry, LiveSessions,
@ -2207,6 +2207,8 @@ pub struct BackendCore {
/// `LaunchAgent`/`ChangeAgentProfile` ; consommé par les commandes de chat (D4)
/// pour résoudre la session vivante d'un `sessionId` et l'arrêter à la fermeture.
pub structured_sessions: Arc<StructuredSessions>,
/// Non-destructive cancellation of the current structured chat request.
pub cancel_agent_chat: Arc<CancelAgentChat>,
// --- Agents (L6) ---
/// Create a project agent from scratch.
pub create_agent: Arc<CreateAgentFromScratch>,
@ -2752,6 +2754,7 @@ impl BackendCore {
// `profile.structured_adapter`. Injectés dans LaunchAgent (routage §17.4) et
// ChangeAgentProfile (shutdown polymorphe au hot-swap).
let structured_sessions = Arc::new(StructuredSessions::new());
let cancel_agent_chat = Arc::new(CancelAgentChat::new(Arc::clone(&structured_sessions)));
let live_sessions = Arc::new(LiveSessions::new(
Arc::clone(&terminal_sessions),
Arc::clone(&structured_sessions),
@ -4462,6 +4465,7 @@ impl BackendCore {
update_agent_mcp_tool_permissions,
set_agent_mcp_tool_allowed,
structured_sessions,
cancel_agent_chat,
create_agent,
list_agents,
read_agent_context,