fix(orchestrator): la délégation simple vers UX renvoie son Final structuré (#207, QA verte)

Une demande idea_ask_agent vers UX échouait avec « finished without a structured Final response » alors qu'Architect répondait. Deux causes corrigées:

- session/process: un tour CLI terminé en code non nul SANS stdout (ex. erreur 401/auth) renvoyait une sortie vide au lieu du stderr réel; l'orchestrateur concluait alors à un « no Final ». On remonte désormais le stderr effectif (drain sync + async + helper process_exit_message + test dédié).

- orchestrator/service: la branche « ask waiting on background » masquait le premier Final structuré; la réponse synchrone redevient le premier Final (suppression de active_rendezvous_has_background_tasks / mark_rendezvous_task_waiting).

Nettoyage runtime corollaire (#204): arrêt du live runtime stale (PTY + structuré) d'un agent recréé afin que la délégation route vers l'agent courant (LiveSessions, stop_live_agent_runtime_in_project, wiring backend via DeleteAgent::with_runtime_cleanup, lifecycle, workstate cascade).
This commit is contained in:
2026-08-12 14:09:06 +02:00
parent ba176e3cf2
commit 646b9d9284
11 changed files with 673 additions and 203 deletions

View File

@ -43,7 +43,10 @@ use crate::model_server::{
};
use crate::project::project_context_path;
use crate::skill::{AgentCapability, AssignedSkillResolver, ResolveAgentCapabilities};
use crate::terminal::{StructuredSessions, TerminalSessions};
use crate::terminal::{
stop_live_agent_runtime_in_project, CloseTerminal, LiveSessions, StructuredSessions,
TerminalSessions,
};
use crate::workstate::GetLiveStateLean;
/// Directory (relative to `.ideai/`) under which agent contexts are written.
@ -1033,13 +1036,29 @@ pub struct DeleteAgentInput {
pub struct DeleteAgent {
contexts: Arc<dyn AgentContextStore>,
events: Arc<dyn EventBus>,
runtime_cleanup: Option<(Arc<LiveSessions>, Arc<CloseTerminal>)>,
}
impl DeleteAgent {
/// Builds the use case.
#[must_use]
pub fn new(contexts: Arc<dyn AgentContextStore>, events: Arc<dyn EventBus>) -> Self {
Self { contexts, events }
Self {
contexts,
events,
runtime_cleanup: None,
}
}
/// Wires live-runtime cleanup performed before the manifest entry is deleted.
#[must_use]
pub fn with_runtime_cleanup(
mut self,
live: Arc<LiveSessions>,
close: Arc<CloseTerminal>,
) -> Self {
self.runtime_cleanup = Some((live, close));
self
}
/// Drops the manifest entry for the agent.
@ -1058,6 +1077,11 @@ impl DeleteAgent {
if entries.len() == before {
return Err(AppError::NotFound(format!("agent {}", input.agent_id)));
}
if let Some((live, close)) = &self.runtime_cleanup {
let _stopped =
stop_live_agent_runtime_in_project(&input.project, input.agent_id, live, close)
.await?;
}
let manifest = AgentManifest::new(manifest.version, entries)
.map_err(|e| AppError::Invalid(e.to_string()))?;
self.contexts