feat(inter-agent): backend des annonces live d'agent (B0-B3)

Redémarre le ticket #4 sur la base develop. Émission d'annonces live d'un
agent vers l'UI, distinctes du Final de délégation :

- domain: ReplyEvent::Announcement/Final et DomainEvent::AgentAnnouncement
  (events.rs), port d'émission (ports.rs), gating de readiness (readiness.rs).
- application: mapping des événements structurés en annonces
  (agent/structured.rs, agent/mod.rs, lib.rs) et relais côté orchestrateur
  (orchestrator/service.rs).
- infrastructure/session: parse des annonces + fix du Final pour Claude et
  Codex, propagé aux adaptateurs et à la conformance
  (claude.rs, codex.rs, conformance.rs, mod.rs, process.rs, sandbox_e2e.rs).
- app-tauri: relais Tauri des annonces vers le front (events.rs, chat.rs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 19:50:03 +02:00
parent ebd992e41a
commit aa5a4f30ae
15 changed files with 605 additions and 52 deletions

View File

@ -36,9 +36,9 @@ use crate::conversation::RecordTurn;
use crate::memory::HarvestMemoryFromTurn;
use crate::agent::{
drain_with_readiness, CreateAgentFromScratch, CreateAgentInput, LaunchAgent, LaunchAgentInput,
ListAgents, ListAgentsInput, McpRuntime, ReattachDecision, UpdateAgentContext,
UpdateAgentContextInput, CODEX_SUBMIT_DELAY_MS,
drain_with_readiness_and_announcements, AnnouncementPublisher, CreateAgentFromScratch,
CreateAgentInput, LaunchAgent, LaunchAgentInput, ListAgents, ListAgentsInput, McpRuntime,
ReattachDecision, UpdateAgentContext, UpdateAgentContextInput, CODEX_SUBMIT_DELAY_MS,
};
use crate::background::{SpawnBackgroundCommand, SpawnBackgroundCommandInput};
use crate::error::AppError;
@ -1815,7 +1815,21 @@ impl OrchestratorService {
// **fenêtre d'inactivité réarmable** autour de l'attente (signe de vie = sonde de
// transcript), pas par un timeout plat interne — un long tour unique qui progresse
// n'est plus coupé à `turn_timeout`. Aucun `idea_reply` ne peut résoudre ce tour.
let drain = drain_with_readiness(session, &task, None, input.as_ref(), agent_id);
let announcement_publisher = self.events.as_ref().map(|bus| AnnouncementPublisher {
bus: Arc::clone(bus),
project_id: project.id,
requester: requester.map_or(ConversationParty::User, ConversationParty::agent),
target: agent_id,
ticket: ticket_id,
});
let drain = drain_with_readiness_and_announcements(
session,
&task,
None,
input.as_ref(),
agent_id,
announcement_publisher,
);
// L'attente du rendez-vous structured, rendue comme `Result<String, AppError>`
// pour être enveloppée par le watchdog. Un flux clos sans `Final` correspond