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:
@ -16,7 +16,8 @@ use domain::{AgentId, NodeId, Project, RuntimeAgentKey, SessionId};
|
||||
use crate::error::AppError;
|
||||
use crate::orchestrator::OrchestratorService;
|
||||
use crate::terminal::{
|
||||
stop_live_agent_runtime_in_project, CloseTerminal, LiveSessionKind, LiveSessions,
|
||||
stop_live_agent_runtime_in_project, CloseTerminal, LiveRuntimeState, LiveSessionKind,
|
||||
LiveSessions,
|
||||
};
|
||||
|
||||
/// Input for [`AttachLiveAgent::execute`].
|
||||
@ -115,8 +116,12 @@ pub struct StopLiveAgentOutput {
|
||||
pub agent_id: AgentId,
|
||||
/// The id of the session that was torn down.
|
||||
pub session_id: SessionId,
|
||||
/// Last known layout node hosting the runtime view.
|
||||
pub node_id: NodeId,
|
||||
/// Runtime family that owned the session.
|
||||
pub kind: LiveSessionKind,
|
||||
/// Runtime lifecycle projection after the stop request.
|
||||
pub runtime_state: LiveRuntimeState,
|
||||
}
|
||||
|
||||
/// Best-effort stopper for execution resources owned by one live agent turn.
|
||||
@ -232,7 +237,26 @@ impl StopLiveAgent {
|
||||
.map(|stopped| StopLiveAgentOutput {
|
||||
agent_id: stopped.agent_id,
|
||||
session_id: stopped.session_id,
|
||||
node_id: stopped.node_id,
|
||||
kind: stopped.kind,
|
||||
runtime_state: stopped.state,
|
||||
})
|
||||
.or_else(|| {
|
||||
self.live
|
||||
.runtime_status_for(project.id, &agent_id)
|
||||
.filter(|status| {
|
||||
matches!(
|
||||
status.state,
|
||||
LiveRuntimeState::Stopping | LiveRuntimeState::Stopped
|
||||
)
|
||||
})
|
||||
.map(|status| StopLiveAgentOutput {
|
||||
agent_id: status.agent_id,
|
||||
session_id: status.session_id,
|
||||
node_id: status.node_id,
|
||||
kind: status.kind,
|
||||
runtime_state: status.state,
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| AppError::NotFound(format!("running session for agent {agent_id}")))
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ use domain::{
|
||||
};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::terminal::{LiveSessionKind, LiveSessionSnapshot, LiveSessions};
|
||||
use crate::terminal::{LiveRuntimeState, LiveSessionKind, LiveSessionSnapshot, LiveSessions};
|
||||
use crate::HandoffProvider;
|
||||
|
||||
/// Maximum length (in characters) of a derived [`AgentTicketState::task_preview`].
|
||||
@ -202,6 +202,8 @@ pub struct AgentWorkState {
|
||||
pub profile_id: ProfileId,
|
||||
/// Current live session, if the agent is live.
|
||||
pub live: Option<LiveWorkSession>,
|
||||
/// Last known runtime lifecycle state for the agent, distinct from `live`.
|
||||
pub runtime_state: Option<LiveRuntimeState>,
|
||||
/// Current FIFO/busy state.
|
||||
pub busy: AgentBusyState,
|
||||
/// Pending/in-progress delegation tickets, in FIFO order.
|
||||
@ -490,6 +492,10 @@ impl GetProjectWorkState {
|
||||
session_id: snapshot.session_id,
|
||||
kind: snapshot.kind,
|
||||
});
|
||||
let runtime_state = self
|
||||
.live
|
||||
.runtime_status_for(input.project.id, &agent.id)
|
||||
.map(|status| status.state);
|
||||
// Tickets are crossed with the busy state: only an agent absent from
|
||||
// the manifest is dropped (this loop only visits manifest entries), so
|
||||
// the manifest boundary is naturally preserved.
|
||||
@ -510,6 +516,7 @@ impl GetProjectWorkState {
|
||||
name: agent.name,
|
||||
profile_id: agent.profile_id,
|
||||
live,
|
||||
runtime_state,
|
||||
busy,
|
||||
tickets,
|
||||
background_tasks,
|
||||
|
||||
Reference in New Issue
Block a user