feat(workstate): actions contrôlées sur le work-state (Lot D backend)

Ajoute un module d'actions contrôlées (`workstate/actions.rs`) côté application
et l'expose via des commandes Tauri : DTO camelCase, câblage commands/state/lib.
Les actions valident leurs invariants avant d'agir sur le read-model.

- application : use cases d'actions contrôlées + intégration au work-state.
- app-tauri : commandes dédiées, DTO et enregistrement dans le handler.

Tests verts : application workstate_actions (7), workstate (21),
app-tauri dto_agents (25), list_live_agents_r0b (5), cargo check OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 19:51:46 +02:00
parent 7c71544692
commit 3408c96974
9 changed files with 766 additions and 53 deletions

View File

@ -9,10 +9,10 @@
use serde::{Deserialize, Serialize};
use application::{
AgentTicketState, AppError, ConversationPreviewStatus, ConversationTurnWorkPreview,
ConversationWorkSummary, CreateProjectInput, CreateProjectOutput, GitGraphOutput, HealthInput,
HealthReport, LayoutKind, ListProjectsOutput, LiveSessionKind, OpenProjectOutput,
ProjectWorkState, TicketWorkSource, TicketWorkStatus,
AgentTicketState, AppError, AttachLiveAgentOutput, ConversationPreviewStatus,
ConversationTurnWorkPreview, ConversationWorkSummary, CreateProjectInput, CreateProjectOutput,
GitGraphOutput, HealthInput, HealthReport, LayoutKind, ListProjectsOutput, LiveSessionKind,
OpenProjectOutput, ProjectWorkState, StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus,
};
use domain::{AgentBusyState, Project, ProjectId, TurnRole};
@ -1751,6 +1751,64 @@ pub struct AttachLiveAgentRequestDto {
pub node_id: String,
}
/// Response DTO for `attach_live_agent`: the rebound live session's coordinates.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AttachLiveAgentResponseDto {
/// The rebound agent's id.
pub agent_id: String,
/// The node now hosting the session view.
pub node_id: String,
/// The (unchanged) live session id.
pub session_id: String,
/// Runtime family that owns the session (`pty`/`structured`).
pub kind: LiveWorkSessionKindDto,
}
impl From<AttachLiveAgentOutput> for AttachLiveAgentResponseDto {
fn from(out: AttachLiveAgentOutput) -> Self {
Self {
agent_id: out.agent_id.to_string(),
node_id: out.node_id.to_string(),
session_id: out.session_id.to_string(),
kind: out.kind.into(),
}
}
}
/// Request DTO for `stop_live_agent`: tear down an already-running agent's live
/// session by agent id (no spawn, agent entity preserved).
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StopLiveAgentRequestDto {
/// Id of the owning project (validated for symmetry and future scoping).
pub project_id: String,
/// Id of the running agent to stop.
pub agent_id: String,
}
/// Response DTO for `stop_live_agent`: the torn-down session's coordinates.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StopLiveAgentResponseDto {
/// The stopped agent's id.
pub agent_id: String,
/// The id of the session that was torn down.
pub session_id: String,
/// Runtime family that owned the session (`pty`/`structured`).
pub kind: LiveWorkSessionKindDto,
}
impl From<StopLiveAgentOutput> for StopLiveAgentResponseDto {
fn from(out: StopLiveAgentOutput) -> Self {
Self {
agent_id: out.agent_id.to_string(),
session_id: out.session_id.to_string(),
kind: out.kind.into(),
}
}
}
/// Parses an agent-id string (UUID) coming from the frontend.
///
/// # Errors