feat(orchestrator): backstop no-reply du rendez-vous inter-agents

Remédiation du wedge persistant après échec live du fix Finding A (77e62e5).
Détecte la fin de tour d'un agent sollicité qui n'a pas appelé idea_reply et
débloque l'agent demandeur au lieu de le laisser en attente indéfinie.

Ajoute le suivi de tour côté inspector Claude (claude_turn_watcher) et la
résolution des chemins de session (claude_paths), câblés dans le rendez-vous
idea_ask_agent ⇄ idea_reply.

Build workspace vert, suite complète verte, zéro warning.
Backstop NON prouvé levé en live : merge develop interdit tant que la levée
du wedge n'est pas validée en conditions réelles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 15:51:59 +02:00
parent 2ef5628c72
commit 744de20f4b
20 changed files with 867 additions and 1093 deletions

View File

@ -1103,6 +1103,9 @@ pub async fn stop_live_agent(
) -> Result<StopLiveAgentResponseDto, ErrorDto> {
let project = resolve_project(&request.project_id, &state).await?;
let agent_id = parse_agent_id(&request.agent_id)?;
// Backstop no-reply : arrêter l'observateur de fin de tour de l'agent (le handle est
// droppé ⇒ polling stoppé) avant de démonter sa session.
state.stop_turn_watch(agent_id);
state
.stop_live_agent
.execute(StopLiveAgentInput { project, agent_id })
@ -1260,6 +1263,9 @@ pub async fn launch_agent(
// Lot LS6 — project root captured before `project` moves, for the off-hot-path log
// rotation triggered at thread (re)open below.
let rotation_root = project.root.clone();
// Backstop no-reply — project root captured before `project` moves, to arm the
// end-of-turn transcript watcher for the launched agent (cf. `AppState::arm_turn_watch`).
let watch_root = project.root.clone();
let output = state
.launch_agent
@ -1288,6 +1294,19 @@ pub async fn launch_agent(
);
}
// Backstop no-reply : armer l'observateur de fin de tour transcript pour la cible si
// son profil est supporté (Claude). Idempotent par remplacement à chaque (re)lancement
// effectif (profil résolu présent) ; no-op sur reattach (profil `None`) — le handle
// posé au 1er lancement persiste. cwd = run dir isolé de l'agent.
if let Some(profile) = output.profile.as_ref() {
state.arm_turn_watch(
&watch_root,
agent_id,
profile,
output.assigned_conversation_id.clone(),
);
}
// Lot LS6 — rotation **best-effort** du log, déclenchée à la (re)ouverture du fil et
// **hors chemin chaud** : détachée (`tokio::spawn`) pour n'ajouter aucune latence au
// launch, erreurs **avalées** (la rotation ne doit jamais casser une reprise). Un