fix(runtime): stop/restart d'agent visible robuste — kill réel, état stopped, relance froide (#196, QA verte)
Solidifie le cycle stop/restart d'un agent visible en cellule: - terminal/registry: kill effectif de la TUI native et de la CLI custom, arrêt idempotent du live runtime (PTY + structuré), distinction nette « agent stoppé » vs « pas de session ». - workstate + lifecycle: projection d'un état runtime explicite « stopped » (AgentRuntimeState: running/stopping/stopped/failedStart), réconcilié avec le live state; traitement des courses « agent not found ». - relance froide via freshStart: repart de zéro sans réattacher une session morte et recharge la configuration courante de l'agent (y compris changement de modèle). - contrat projeté côté adapters frontend (ports/domain/normalization/mock) pour la consommation UI future (#197/#198), sans surface UI ici. Tests QA verts: workstate_actions, orchestrator_service stop_agent, agent_lifecycle (reopen/fresh_start), workstate stopped runtime state, vitest adapter stop_live_agent.
This commit is contained in:
@ -15,8 +15,9 @@ use application::{
|
||||
ConversationPreviewStatus, ConversationTurnWorkPreview, ConversationWorkSummary,
|
||||
CreateProjectInput, CreateProjectOutput, ExecuteSlashCommandOutput, GitGraphOutput,
|
||||
HealthInput, HealthReport, LayoutKind, ListProjectsOutput, ListSlashCommandsOutput,
|
||||
LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState, SlashCommandEffect,
|
||||
StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage, TurnSource, TurnView,
|
||||
LiveRuntimeState, LiveSessionKind, LiveSessionSnapshot, OpenProjectOutput, ProjectWorkState,
|
||||
SlashCommandEffect, StopLiveAgentOutput, TicketWorkSource, TicketWorkStatus, TurnPage,
|
||||
TurnSource, TurnView,
|
||||
};
|
||||
use domain::ports::{ReplyProgress, ReplyProgressKind, ReplyProgressSource, ReplyProgressStage};
|
||||
use domain::{
|
||||
@ -2962,6 +2963,10 @@ pub struct LaunchAgentRequestDto {
|
||||
/// view sends `"chat"` to require a structured/headless session.
|
||||
#[serde(default)]
|
||||
pub cell_kind: Option<CellKind>,
|
||||
/// Cold restart request: ignore any cell conversation id and create a fresh
|
||||
/// provider/CLI conversation after the caller has stopped the previous runtime.
|
||||
#[serde(default)]
|
||||
pub fresh_start: bool,
|
||||
}
|
||||
|
||||
impl From<LaunchAgentOutput> for TerminalSessionDto {
|
||||
@ -3452,6 +3457,8 @@ pub struct LiveAgentDto {
|
||||
pub session_id: String,
|
||||
/// Runtime family that owns the session (`pty`/`structured`).
|
||||
pub kind: LiveWorkSessionKindDto,
|
||||
/// Runtime lifecycle state. Live agents are always `running`.
|
||||
pub runtime_state: LiveRuntimeStateDto,
|
||||
}
|
||||
|
||||
/// Response DTO for `list_live_agents` (transparent array on the wire).
|
||||
@ -3479,6 +3486,7 @@ impl LiveAgentListDto {
|
||||
node_id: snapshot.node_id.to_string(),
|
||||
session_id: snapshot.session_id.to_string(),
|
||||
kind: snapshot.kind.into(),
|
||||
runtime_state: LiveRuntimeStateDto::Running,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
@ -3504,6 +3512,31 @@ impl From<LiveSessionKind> for LiveWorkSessionKindDto {
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit lifecycle state for a visible agent runtime.
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum LiveRuntimeStateDto {
|
||||
/// A live session exists in one runtime registry.
|
||||
Running,
|
||||
/// A stop request is in progress.
|
||||
Stopping,
|
||||
/// The last live session was torn down.
|
||||
Stopped,
|
||||
/// The last launch attempt failed before registration.
|
||||
FailedStart,
|
||||
}
|
||||
|
||||
impl From<LiveRuntimeState> for LiveRuntimeStateDto {
|
||||
fn from(state: LiveRuntimeState) -> Self {
|
||||
match state {
|
||||
LiveRuntimeState::Running => Self::Running,
|
||||
LiveRuntimeState::Stopping => Self::Stopping,
|
||||
LiveRuntimeState::Stopped => Self::Stopped,
|
||||
LiveRuntimeState::FailedStart => Self::FailedStart,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Live session coordinates in the project work-state read model.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -3514,6 +3547,8 @@ pub struct LiveWorkSessionDto {
|
||||
pub session_id: String,
|
||||
/// Runtime family that owns the session.
|
||||
pub kind: LiveWorkSessionKindDto,
|
||||
/// Runtime lifecycle state. Live sessions are always `running`.
|
||||
pub runtime_state: LiveRuntimeStateDto,
|
||||
}
|
||||
|
||||
/// Derived processing status of a queued ticket.
|
||||
@ -3665,6 +3700,8 @@ pub struct AgentWorkStateDto {
|
||||
pub profile_id: String,
|
||||
/// Live session, if any.
|
||||
pub live: Option<LiveWorkSessionDto>,
|
||||
/// Last known runtime lifecycle state, distinct from live-session presence.
|
||||
pub runtime_state: Option<LiveRuntimeStateDto>,
|
||||
/// Current mediated-input busy state.
|
||||
pub busy: AgentBusyState,
|
||||
/// Pending/in-progress delegation tickets, in FIFO order.
|
||||
@ -3789,7 +3826,9 @@ impl From<ProjectWorkState> for ProjectWorkStateDto {
|
||||
node_id: live.node_id.to_string(),
|
||||
session_id: live.session_id.to_string(),
|
||||
kind: live.kind.into(),
|
||||
runtime_state: LiveRuntimeStateDto::Running,
|
||||
}),
|
||||
runtime_state: agent.runtime_state.map(Into::into),
|
||||
busy: agent.busy,
|
||||
tickets: agent
|
||||
.tickets
|
||||
@ -3976,6 +4015,8 @@ pub struct StopLiveAgentResponseDto {
|
||||
pub session_id: String,
|
||||
/// Runtime family that owned the session (`pty`/`structured`).
|
||||
pub kind: LiveWorkSessionKindDto,
|
||||
/// Runtime lifecycle projection after the stop request.
|
||||
pub runtime_state: LiveRuntimeStateDto,
|
||||
}
|
||||
|
||||
impl From<StopLiveAgentOutput> for StopLiveAgentResponseDto {
|
||||
@ -3984,6 +4025,7 @@ impl From<StopLiveAgentOutput> for StopLiveAgentResponseDto {
|
||||
agent_id: out.agent_id.to_string(),
|
||||
session_id: out.session_id.to_string(),
|
||||
kind: out.kind.into(),
|
||||
runtime_state: out.runtime_state.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -730,6 +730,7 @@ impl WakeSessionProvider for AppWakeSessionProvider {
|
||||
mcp_runtime: None,
|
||||
allow_structured_alongside_pty: true,
|
||||
require_structured: false,
|
||||
fresh_start: false,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| WakeError::Session(err.to_string()))?;
|
||||
@ -850,6 +851,7 @@ impl AgentResumer for AppAgentResumer {
|
||||
mcp_runtime,
|
||||
allow_structured_alongside_pty: false,
|
||||
require_structured: false,
|
||||
fresh_start: false,
|
||||
})
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user