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:
2026-08-12 14:56:02 +02:00
parent 4e93fa2f20
commit bad7b4c2f3
23 changed files with 440 additions and 46 deletions

View File

@ -772,6 +772,7 @@ fn launch_input(agent_id: AgentId) -> LaunchAgentInput {
mcp_runtime: None,
allow_structured_alongside_pty: false,
require_structured: false,
fresh_start: false,
}
}
@ -1462,6 +1463,28 @@ async fn structured_profile_with_cell_conversation_resolves_to_resume() {
);
}
/// Une relance froide ignore explicitement le `conversation_id` encore porté par
/// la cellule : le runtime structuré doit recevoir un plan neuf, pas une reprise.
#[tokio::test]
async fn structured_profile_fresh_start_ignores_cell_conversation() {
let factory = FakeFactory::new(500, Some("engine-fresh"));
let f = launch_fixture(structured_profile(pid(9)), factory);
let mut input = launch_input(f.agent.id);
input.conversation_id = Some("conv-existing".to_owned());
input.fresh_start = true;
f.launch.execute(input).await.expect("launch starts fresh");
assert_eq!(
*f.session_probe.lock().unwrap(),
Some(SessionPlan::None),
"fresh start must not pass the old cell conversation to the runtime"
);
let starts = f.factory.starts();
assert_eq!(starts.len(), 1);
assert_eq!(starts[0].1, SessionPlan::None);
}
/// Une cellule structurée **neuve** (pas de conversation) sur un profil sans bloc
/// `session` ⇒ `SessionPlan::None` (rien à reprendre ; l'id moteur sera capté au 1er
/// tour).