feat(agent): conversation par paire + entrée médiée + pivot terminal/MCP
Coeur inter-agents consolidé et surface front réalignée sur la décision "terminal natif PTY, pas d'UI chat" (Option 1). Domaine - nouveaux modules conversation, mailbox, input, fileguard (ports + types) - orchestrator/profile/events étendus (conversation par paire, FIFO) Application / Infrastructure - orchestrator/service + context_guard : sérialisation FIFO par agent, garde RW mémoire/contexte, dispatch ask/reply - adapters in-memory conversation / mailbox / input / fileguard - registry session + lifecycle agent durcis (1 agent = 1 session vivante) - outils MCP idea_* alignés sur le nouveau dispatch Frontend - MediatedInput + useAgentBusy : entrée utilisateur médiée par IdeA, terminal = vue sortie inchangée - suppression de la vue chat structurée (AgentChatView) — abandonnée - adapter input + ports mis à jour Divers - .ideai/ : mémoire projet + briefs de cadrage versionnés ; requests/ runtime ignoré ; agents projet réels (DevBackend/DevFrontend/QA) Tests : Rust (domain/application/infrastructure/app-tauri) + front (346) verts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -9,6 +9,7 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use domain::conversation::ConversationId;
|
||||
use domain::ports::{AgentSession, PtyHandle};
|
||||
use domain::{AgentId, NodeId, SessionId, SessionKind, TerminalSession};
|
||||
|
||||
@ -44,6 +45,11 @@ pub trait LiveAgentRegistry: Send + Sync {
|
||||
#[derive(Default)]
|
||||
pub struct TerminalSessions {
|
||||
entries: Mutex<HashMap<SessionId, Entry>>,
|
||||
/// Conversation → live session binding (cadrage C3 §5.2): «1 session vivante /
|
||||
/// **conversation**» (remplace «1 / agent»). Populated by the orchestrator when an
|
||||
/// ask resolves/launches the session for a given thread. Separate from `entries`
|
||||
/// (the domain [`TerminalSession`] does not carry a conversation id).
|
||||
conversations: Mutex<HashMap<ConversationId, SessionId>>,
|
||||
}
|
||||
|
||||
impl LiveAgentRegistry for TerminalSessions {
|
||||
@ -65,9 +71,50 @@ impl TerminalSessions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: Mutex::new(HashMap::new()),
|
||||
conversations: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds `conversation` to the live `session` (cadrage C3 §5.2). Idempotent:
|
||||
/// re-binding the same conversation overwrites the target session.
|
||||
pub fn bind_conversation(&self, conversation: ConversationId, session: SessionId) {
|
||||
if let Ok(mut m) = self.conversations.lock() {
|
||||
m.insert(conversation, session);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the live [`SessionId`] bound to `conversation`, if any **and** still
|
||||
/// registered (a stale binding to a closed session resolves to `None`).
|
||||
///
|
||||
/// «1 session vivante / conversation» — deterministic, replacing the ambiguous
|
||||
/// per-agent lookup for the orchestrator's ask path.
|
||||
#[must_use]
|
||||
pub fn session_for(&self, conversation: ConversationId) -> Option<SessionId> {
|
||||
let sid = self.conversations.lock().ok()?.get(&conversation).copied()?;
|
||||
// Only return it if the session is still live in `entries`.
|
||||
self.entries
|
||||
.lock()
|
||||
.ok()
|
||||
.filter(|m| m.contains_key(&sid))
|
||||
.map(|_| sid)
|
||||
}
|
||||
|
||||
/// Lists every live [`SessionId`] hosting `agent_id` (cadrage C3 §1.2 — an agent
|
||||
/// may take part in several threads, so this is the **plural** of
|
||||
/// [`Self::session_for_agent`]).
|
||||
#[must_use]
|
||||
pub fn sessions_for_agent(&self, agent_id: &AgentId) -> Vec<SessionId> {
|
||||
self.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.filter(|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id))
|
||||
.map(|e| e.session.id)
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Inserts a freshly-opened session.
|
||||
pub fn insert(&self, handle: PtyHandle, session: TerminalSession) {
|
||||
if let Ok(mut map) = self.entries.lock() {
|
||||
@ -184,8 +231,12 @@ impl TerminalSessions {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Removes a session from the registry, returning its handle if present.
|
||||
/// Removes a session from the registry, returning its handle if present. Also
|
||||
/// drops any conversation binding pointing at it (no stale `session_for`).
|
||||
pub fn remove(&self, id: &SessionId) -> Option<PtyHandle> {
|
||||
if let Ok(mut c) = self.conversations.lock() {
|
||||
c.retain(|_, sid| sid != id);
|
||||
}
|
||||
self.entries
|
||||
.lock()
|
||||
.ok()
|
||||
|
||||
Reference in New Issue
Block a user