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:
@ -24,6 +24,19 @@ pub enum LiveSessionKind {
|
||||
Structured,
|
||||
}
|
||||
|
||||
/// Explicit lifecycle state of an agent runtime in a visible cell.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LiveRuntimeState {
|
||||
/// The runtime has a live session in one of the registries.
|
||||
Running,
|
||||
/// A stop request is currently tearing the runtime down.
|
||||
Stopping,
|
||||
/// The runtime was torn down and no live session remains.
|
||||
Stopped,
|
||||
/// The last launch attempt failed before a live session was registered.
|
||||
FailedStart,
|
||||
}
|
||||
|
||||
/// Read-only coordinates of one live agent session.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LiveSessionSnapshot {
|
||||
@ -39,6 +52,51 @@ pub struct LiveSessionSnapshot {
|
||||
pub kind: LiveSessionKind,
|
||||
}
|
||||
|
||||
/// Last-known lifecycle projection for one agent runtime.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LiveRuntimeStatus {
|
||||
/// The project owning the runtime.
|
||||
pub project_id: ProjectId,
|
||||
/// The agent owning the runtime.
|
||||
pub agent_id: AgentId,
|
||||
/// Last known layout node hosting the runtime view.
|
||||
pub node_id: NodeId,
|
||||
/// Last known session id.
|
||||
pub session_id: SessionId,
|
||||
/// Runtime family that owns or owned the session.
|
||||
pub kind: LiveSessionKind,
|
||||
/// Current lifecycle state.
|
||||
pub state: LiveRuntimeState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct LiveAgentKey {
|
||||
project_id: ProjectId,
|
||||
agent_id: AgentId,
|
||||
}
|
||||
|
||||
impl LiveAgentKey {
|
||||
fn new(project_id: ProjectId, agent_id: AgentId) -> Self {
|
||||
Self {
|
||||
project_id,
|
||||
agent_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LiveSessionSnapshot> for LiveRuntimeStatus {
|
||||
fn from(snapshot: LiveSessionSnapshot) -> Self {
|
||||
Self {
|
||||
project_id: snapshot.project_id,
|
||||
agent_id: snapshot.agent_id,
|
||||
node_id: snapshot.node_id,
|
||||
session_id: snapshot.session_id,
|
||||
kind: snapshot.kind,
|
||||
state: LiveRuntimeState::Running,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A registered, live terminal: its PTY handle plus the domain snapshot.
|
||||
#[derive(Debug)]
|
||||
struct Entry {
|
||||
@ -849,13 +907,18 @@ pub struct LiveSessions {
|
||||
pub pty: Arc<TerminalSessions>,
|
||||
/// Registre des sessions IA structurées.
|
||||
pub structured: Arc<StructuredSessions>,
|
||||
runtime: Mutex<HashMap<LiveAgentKey, LiveRuntimeStatus>>,
|
||||
}
|
||||
|
||||
impl LiveSessions {
|
||||
/// Construit l'agrégateur à partir des deux registres partagés.
|
||||
#[must_use]
|
||||
pub fn new(pty: Arc<TerminalSessions>, structured: Arc<StructuredSessions>) -> Self {
|
||||
Self { pty, structured }
|
||||
Self {
|
||||
pty,
|
||||
structured,
|
||||
runtime: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// L'[`SessionId`] de la session vivante d'un agent, PTY **ou** structurée.
|
||||
@ -899,6 +962,62 @@ impl LiveSessions {
|
||||
all.extend(self.structured.live_agent_snapshots());
|
||||
all
|
||||
}
|
||||
|
||||
/// All live runtime snapshots for one `(project, agent)` canonical key.
|
||||
#[must_use]
|
||||
pub fn live_agent_snapshots_for(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Vec<LiveSessionSnapshot> {
|
||||
self.live_agent_snapshots()
|
||||
.into_iter()
|
||||
.filter(|snapshot| snapshot.project_id == project_id && &snapshot.agent_id == agent_id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Current runtime projection for one canonical `(project, agent)` key.
|
||||
#[must_use]
|
||||
pub fn runtime_status_for(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<LiveRuntimeStatus> {
|
||||
self.live_agent_snapshots_for(project_id, agent_id)
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(LiveRuntimeStatus::from)
|
||||
.or_else(|| {
|
||||
self.runtime
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|m| m.get(&LiveAgentKey::new(project_id, *agent_id)).copied())
|
||||
})
|
||||
}
|
||||
|
||||
/// Records a non-running runtime state for the last known session coordinates.
|
||||
pub fn mark_runtime_state(&self, snapshot: LiveSessionSnapshot, state: LiveRuntimeState) {
|
||||
if let Ok(mut map) = self.runtime.lock() {
|
||||
map.insert(
|
||||
LiveAgentKey::new(snapshot.project_id, snapshot.agent_id),
|
||||
LiveRuntimeStatus {
|
||||
project_id: snapshot.project_id,
|
||||
agent_id: snapshot.agent_id,
|
||||
node_id: snapshot.node_id,
|
||||
session_id: snapshot.session_id,
|
||||
kind: snapshot.kind,
|
||||
state,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops stale stopped/failed state when a caller needs a clean runtime boundary.
|
||||
pub fn clear_runtime_state(&self, project_id: ProjectId, agent_id: &AgentId) {
|
||||
if let Ok(mut map) = self.runtime.lock() {
|
||||
map.remove(&LiveAgentKey::new(project_id, *agent_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiveAgentRegistry for LiveSessions {
|
||||
|
||||
Reference in New Issue
Block a user