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:
@ -303,8 +303,51 @@ impl AgentSession for ClaudeSdkSession {
|
||||
}
|
||||
|
||||
async fn send(&self, prompt: &str) -> Result<ReplyStream, AgentSessionError> {
|
||||
self.send_inner(prompt, None).await
|
||||
}
|
||||
|
||||
async fn send_with_tap(
|
||||
&self,
|
||||
prompt: &str,
|
||||
tap: std::sync::mpsc::Sender<ReplyEvent>,
|
||||
) -> Result<ReplyStream, AgentSessionError> {
|
||||
self.send_inner(prompt, Some(tap)).await
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<(), AgentSessionError> {
|
||||
// Incarnation « un run par tour » : aucun process long ne survit entre les
|
||||
// tours, donc `shutdown` est intrinsèquement idempotent et sans effet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ClaudeSdkSession {
|
||||
async fn send_inner(
|
||||
&self,
|
||||
prompt: &str,
|
||||
tap: Option<std::sync::mpsc::Sender<ReplyEvent>>,
|
||||
) -> Result<ReplyStream, AgentSessionError> {
|
||||
let spec = self.build_spawn_line(prompt);
|
||||
let raw_lines = run_turn(&spec, None, self.sandbox_enforcer.as_ref()).await?;
|
||||
let (line_tap, parser_thread) = tap.map_or((None, None), |event_tap| {
|
||||
let (line_tx, line_rx) = std::sync::mpsc::channel::<String>();
|
||||
let parser = std::thread::spawn(move || {
|
||||
for line in line_rx {
|
||||
if let Ok(parsed) = parse_event(&line) {
|
||||
for event in parsed.events {
|
||||
if let ReplyEvent::TextDelta { text } = event {
|
||||
let _ = event_tap.send(ReplyEvent::Announcement { text });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
(Some(line_tx), Some(parser))
|
||||
});
|
||||
let raw_result = run_turn(&spec, None, self.sandbox_enforcer.as_ref(), line_tap).await;
|
||||
if let Some(parser) = parser_thread {
|
||||
let _ = parser.join();
|
||||
}
|
||||
let raw_lines = raw_result?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut captured_id = None;
|
||||
@ -334,10 +377,4 @@ impl AgentSession for ClaudeSdkSession {
|
||||
|
||||
Ok(Box::new(events.into_iter()))
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<(), AgentSessionError> {
|
||||
// Incarnation « un run par tour » : aucun process long ne survit entre les
|
||||
// tours, donc `shutdown` est intrinsèquement idempotent et sans effet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user