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:
@ -47,10 +47,11 @@ use domain::{PtySize, SessionId, SkillId, SkillRef};
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::{
|
||||
CreateAgentFromScratch, CreateAgentInput, DeleteAgent, DeleteAgentInput, LaunchAgent,
|
||||
LaunchAgentInput, ListAgents, ListAgentsInput, PermissionProjectorRegistry, ReadAgentContext,
|
||||
ReadAgentContextInput, StructuredRoutingMode, StructuredSessions, TerminalSessions,
|
||||
UpdateAgentContext, UpdateAgentContextInput, UpdateAgentEffort, UpdateAgentEffortInput,
|
||||
CloseTerminal, CreateAgentFromScratch, CreateAgentInput, DeleteAgent, DeleteAgentInput,
|
||||
LaunchAgent, LaunchAgentInput, ListAgents, ListAgentsInput, LiveSessions,
|
||||
PermissionProjectorRegistry, ReadAgentContext, ReadAgentContextInput, StructuredRoutingMode,
|
||||
StructuredSessions, TerminalSessions, UpdateAgentContext, UpdateAgentContextInput,
|
||||
UpdateAgentEffort, UpdateAgentEffortInput,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -585,6 +586,7 @@ impl PtyPort for FakePty {
|
||||
struct FakeSession {
|
||||
id: SessionId,
|
||||
conversation_id: Option<String>,
|
||||
shutdowns: Arc<Mutex<Vec<SessionId>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -602,6 +604,7 @@ impl AgentSession for FakeSession {
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<(), AgentSessionError> {
|
||||
self.shutdowns.lock().unwrap().push(self.id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -613,6 +616,7 @@ struct FakeStructuredFactory {
|
||||
efforts: Arc<Mutex<Vec<Option<String>>>>,
|
||||
envs: Arc<Mutex<Vec<Vec<(String, String)>>>>,
|
||||
policies: Arc<Mutex<Vec<Option<domain::ports::StructuredProviderLaunchPolicy>>>>,
|
||||
shutdowns: Arc<Mutex<Vec<SessionId>>>,
|
||||
next_session: SessionId,
|
||||
}
|
||||
|
||||
@ -624,6 +628,7 @@ impl FakeStructuredFactory {
|
||||
efforts: Arc::new(Mutex::new(Vec::new())),
|
||||
envs: Arc::new(Mutex::new(Vec::new())),
|
||||
policies: Arc::new(Mutex::new(Vec::new())),
|
||||
shutdowns: Arc::new(Mutex::new(Vec::new())),
|
||||
next_session,
|
||||
}
|
||||
}
|
||||
@ -679,6 +684,7 @@ impl AgentSessionFactory for FakeStructuredFactory {
|
||||
Ok(Arc::new(FakeSession {
|
||||
id: self.next_session,
|
||||
conversation_id: Some("engine-session-1".to_owned()),
|
||||
shutdowns: Arc::clone(&self.shutdowns),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -1048,6 +1054,56 @@ async fn delete_removes_entry_then_unknown_is_not_found() {
|
||||
assert_eq!(err.code(), "NOT_FOUND", "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_agent_cleans_structured_then_pty_runtime_before_manifest_removal() {
|
||||
let a = scratch_agent(aid(1), "Backend", "agents/backend.md", pid(9));
|
||||
let contexts = FakeContexts::with_agent(&a, "ctx");
|
||||
let sessions = Arc::new(TerminalSessions::new());
|
||||
let structured = Arc::new(StructuredSessions::new());
|
||||
let live = Arc::new(LiveSessions::new(
|
||||
Arc::clone(&sessions),
|
||||
Arc::clone(&structured),
|
||||
));
|
||||
let pty = FakePty::new(trace(), sid(42));
|
||||
let close = Arc::new(CloseTerminal::new(
|
||||
Arc::new(pty.clone()),
|
||||
Arc::clone(&sessions),
|
||||
));
|
||||
let shutdowns = Arc::new(Mutex::new(Vec::new()));
|
||||
structured.insert_in_project(
|
||||
project().id,
|
||||
Arc::new(FakeSession {
|
||||
id: sid(555),
|
||||
conversation_id: Some("engine-session-stale".to_owned()),
|
||||
shutdowns: Arc::clone(&shutdowns),
|
||||
}),
|
||||
a.id,
|
||||
nid(7),
|
||||
);
|
||||
seed_live_agent_session(&sessions, a.id, nid(8), sid(42));
|
||||
|
||||
let delete = DeleteAgent::new(Arc::new(contexts.clone()), Arc::new(SpyBus::default()))
|
||||
.with_runtime_cleanup(live, close);
|
||||
|
||||
delete
|
||||
.execute(DeleteAgentInput {
|
||||
project: project(),
|
||||
agent_id: a.id,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(*shutdowns.lock().unwrap(), vec![sid(555)]);
|
||||
assert_eq!(pty.kills(), vec![sid(42)]);
|
||||
assert!(structured
|
||||
.session_for_agent_in_project(project().id, &a.id)
|
||||
.is_none());
|
||||
assert!(sessions
|
||||
.session_for_agent_in_project(project().id, &a.id)
|
||||
.is_none());
|
||||
assert!(contexts.manifest().entries.is_empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LaunchAgent
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1369,6 +1425,7 @@ async fn chat_intent_reattaches_existing_structured_session_after_stale_pty_tran
|
||||
Arc::new(FakeSession {
|
||||
id: sid(555),
|
||||
conversation_id: Some("engine-session-stale".to_owned()),
|
||||
shutdowns: Arc::new(Mutex::new(Vec::new())),
|
||||
}),
|
||||
agent.id,
|
||||
stale_custom_node,
|
||||
|
||||
Reference in New Issue
Block a user