fix(runtime): isolate agent state by project (#101)
This commit is contained in:
@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use domain::conversation::ConversationId;
|
||||
use domain::ports::{AgentSession, PtyHandle};
|
||||
use domain::{AgentId, IssueRef, NodeId, SessionId, SessionKind, TerminalSession};
|
||||
use domain::{AgentId, IssueRef, NodeId, ProjectId, SessionId, SessionKind, TerminalSession};
|
||||
|
||||
/// Runtime family of a live agent session.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -25,6 +25,8 @@ pub enum LiveSessionKind {
|
||||
/// Read-only coordinates of one live agent session.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LiveSessionSnapshot {
|
||||
/// The project owning the live session.
|
||||
pub project_id: ProjectId,
|
||||
/// The agent owning the live session.
|
||||
pub agent_id: AgentId,
|
||||
/// The layout node currently hosting the session view.
|
||||
@ -38,6 +40,7 @@ pub struct LiveSessionSnapshot {
|
||||
/// A registered, live terminal: its PTY handle plus the domain snapshot.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Entry {
|
||||
project_id: ProjectId,
|
||||
handle: PtyHandle,
|
||||
session: TerminalSession,
|
||||
}
|
||||
@ -51,7 +54,7 @@ struct Entry {
|
||||
/// implementation.
|
||||
pub trait LiveAgentRegistry: Send + Sync {
|
||||
/// Whether `agent_id` currently has a live session in the registry.
|
||||
fn is_agent_live(&self, agent_id: &AgentId) -> bool;
|
||||
fn is_agent_live(&self, project_id: ProjectId, agent_id: &AgentId) -> bool;
|
||||
|
||||
/// Whether `node_id` (a layout leaf) currently hosts a live session.
|
||||
///
|
||||
@ -75,8 +78,9 @@ pub struct TerminalSessions {
|
||||
}
|
||||
|
||||
impl LiveAgentRegistry for TerminalSessions {
|
||||
fn is_agent_live(&self, agent_id: &AgentId) -> bool {
|
||||
self.session_for_agent(agent_id).is_some()
|
||||
fn is_agent_live(&self, project_id: ProjectId, agent_id: &AgentId) -> bool {
|
||||
self.session_for_agent_in_project(project_id, agent_id)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn is_node_live(&self, node_id: &NodeId) -> bool {
|
||||
@ -130,11 +134,16 @@ impl TerminalSessions {
|
||||
/// 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> {
|
||||
pub fn sessions_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Vec<SessionId> {
|
||||
self.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.filter(|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id))
|
||||
.map(|e| e.session.id)
|
||||
.collect()
|
||||
@ -142,13 +151,38 @@ impl TerminalSessions {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::sessions_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn sessions_for_agent(&self, agent_id: &AgentId) -> Vec<SessionId> {
|
||||
self.sessions_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Inserts a freshly-opened session.
|
||||
pub fn insert(&self, handle: PtyHandle, session: TerminalSession) {
|
||||
pub fn insert_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
handle: PtyHandle,
|
||||
session: TerminalSession,
|
||||
) {
|
||||
if let Ok(mut map) = self.entries.lock() {
|
||||
map.insert(session.id, Entry { handle, session });
|
||||
map.insert(
|
||||
session.id,
|
||||
Entry {
|
||||
project_id,
|
||||
handle,
|
||||
session,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::insert_in_project`].
|
||||
pub fn insert(&self, handle: PtyHandle, session: TerminalSession) {
|
||||
self.insert_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), handle, session);
|
||||
}
|
||||
|
||||
/// Returns the [`PtyHandle`] for a session, if registered.
|
||||
#[must_use]
|
||||
pub fn handle(&self, id: &SessionId) -> Option<PtyHandle> {
|
||||
@ -179,14 +213,26 @@ impl TerminalSessions {
|
||||
/// one live session per agent, so the first match is *the* match. `find`
|
||||
/// short-circuits on it.
|
||||
#[must_use]
|
||||
pub fn session_for_agent(&self, agent_id: &AgentId) -> Option<SessionId> {
|
||||
pub fn session_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<SessionId> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.find(|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id))
|
||||
.map(|e| e.session.id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::session_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn session_for_agent(&self, agent_id: &AgentId) -> Option<SessionId> {
|
||||
self.session_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Returns the [`NodeId`] of the live cell hosting a given agent, if any.
|
||||
///
|
||||
/// Companion to [`Self::session_for_agent`]: the launch guard needs the
|
||||
@ -194,25 +240,41 @@ impl TerminalSessions {
|
||||
/// [`crate::error::AppError::AgentAlreadyRunning`]. Unambiguous by the same
|
||||
/// one-live-session-per-agent invariant.
|
||||
#[must_use]
|
||||
pub fn node_for_agent(&self, agent_id: &AgentId) -> Option<NodeId> {
|
||||
pub fn node_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<NodeId> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.find(|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id))
|
||||
.map(|e| e.session.node_id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::node_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn node_for_agent(&self, agent_id: &AgentId) -> Option<NodeId> {
|
||||
self.node_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Lists every currently-live agent, its current host cell and session id.
|
||||
///
|
||||
/// One `(AgentId, NodeId, SessionId)` tuple per session tagged [`SessionKind::Agent`].
|
||||
/// Used by the `list_live_agents` query so the UI can disable an agent that
|
||||
/// is already running elsewhere (it cannot be launched in a second cell).
|
||||
#[must_use]
|
||||
pub fn live_agents(&self) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
pub fn live_agents_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
self.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.filter_map(|e| match e.session.kind {
|
||||
SessionKind::Agent { agent_id } => {
|
||||
Some((agent_id, e.session.node_id, e.session.id))
|
||||
@ -224,6 +286,13 @@ impl TerminalSessions {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code should prefer
|
||||
/// [`Self::live_agents_in_project`] or [`LiveSessions::live_agent_snapshots`].
|
||||
#[must_use]
|
||||
pub fn live_agents(&self) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
self.live_agents_in_project(ProjectId::from_uuid(uuid::Uuid::nil()))
|
||||
}
|
||||
|
||||
/// Rebinds a live agent session to a new visible layout node without
|
||||
/// respawning the CLI process.
|
||||
///
|
||||
@ -232,20 +301,36 @@ impl TerminalSessions {
|
||||
/// in another cell updates only the view binding (`node_id`). The PTY handle,
|
||||
/// session id, scrollback and process stay untouched.
|
||||
#[must_use]
|
||||
pub fn rebind_agent_node(
|
||||
pub fn rebind_agent_node_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
node_id: NodeId,
|
||||
) -> Option<TerminalSession> {
|
||||
self.entries.lock().ok().and_then(|mut m| {
|
||||
let entry = m.values_mut().find(
|
||||
|e| matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id),
|
||||
|e| e.project_id == project_id
|
||||
&& matches!(e.session.kind, SessionKind::Agent { agent_id: a } if &a == agent_id),
|
||||
)?;
|
||||
entry.session.node_id = node_id;
|
||||
Some(entry.session.clone())
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must pass `project_id`.
|
||||
#[must_use]
|
||||
pub fn rebind_agent_node(
|
||||
&self,
|
||||
agent_id: &AgentId,
|
||||
node_id: NodeId,
|
||||
) -> Option<TerminalSession> {
|
||||
self.rebind_agent_node_in_project(
|
||||
ProjectId::from_uuid(uuid::Uuid::nil()),
|
||||
agent_id,
|
||||
node_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the [`PtyHandle`]s of every currently-registered session.
|
||||
///
|
||||
/// Used at application shutdown to kill all live PTYs cleanly (the
|
||||
@ -295,6 +380,8 @@ impl TerminalSessions {
|
||||
/// le `node_id` (cellule-vue, rebindable) pour offrir la **même** surface que
|
||||
/// [`TerminalSessions`].
|
||||
struct StructuredEntry {
|
||||
/// Projet propriétaire de la session runtime.
|
||||
project_id: ProjectId,
|
||||
/// La session vivante (ressource process/SDK), derrière le port domaine.
|
||||
session: Arc<dyn AgentSession>,
|
||||
/// L'agent IA pilotant cette session (invariant « 1 session vivante/agent »).
|
||||
@ -328,8 +415,9 @@ pub struct StructuredSessions {
|
||||
}
|
||||
|
||||
impl LiveAgentRegistry for StructuredSessions {
|
||||
fn is_agent_live(&self, agent_id: &AgentId) -> bool {
|
||||
self.session_for_agent(agent_id).is_some()
|
||||
fn is_agent_live(&self, project_id: ProjectId, agent_id: &AgentId) -> bool {
|
||||
self.session_for_agent_in_project(project_id, agent_id)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn is_node_live(&self, node_id: &NodeId) -> bool {
|
||||
@ -352,12 +440,19 @@ impl StructuredSessions {
|
||||
|
||||
/// Enregistre une session fraîchement démarrée pour `agent_id`, hébergée par
|
||||
/// la cellule `node_id`. Clé par l'id de session ([`AgentSession::id`]).
|
||||
pub fn insert(&self, session: Arc<dyn AgentSession>, agent_id: AgentId, node_id: NodeId) {
|
||||
pub fn insert_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
session: Arc<dyn AgentSession>,
|
||||
agent_id: AgentId,
|
||||
node_id: NodeId,
|
||||
) {
|
||||
if let Ok(mut map) = self.entries.lock() {
|
||||
let id = session.id();
|
||||
map.insert(
|
||||
id,
|
||||
StructuredEntry {
|
||||
project_id,
|
||||
session,
|
||||
agent_id,
|
||||
node_id,
|
||||
@ -366,6 +461,17 @@ impl StructuredSessions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::insert_in_project`].
|
||||
pub fn insert(&self, session: Arc<dyn AgentSession>, agent_id: AgentId, node_id: NodeId) {
|
||||
self.insert_in_project(
|
||||
ProjectId::from_uuid(uuid::Uuid::nil()),
|
||||
session,
|
||||
agent_id,
|
||||
node_id,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retourne la session enregistrée pour un id, si présente.
|
||||
#[must_use]
|
||||
pub fn session(&self, id: &SessionId) -> Option<Arc<dyn AgentSession>> {
|
||||
@ -434,36 +540,72 @@ impl StructuredSessions {
|
||||
/// Jumeau de [`TerminalSessions::session_for_agent`] : **non ambigu** par
|
||||
/// l'invariant « 1 session vivante/agent » — le premier match est *le* match.
|
||||
#[must_use]
|
||||
pub fn session_for_agent(&self, agent_id: &AgentId) -> Option<Arc<dyn AgentSession>> {
|
||||
pub fn session_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<Arc<dyn AgentSession>> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.find(|e| &e.agent_id == agent_id)
|
||||
.map(|e| Arc::clone(&e.session))
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::session_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn session_for_agent(&self, agent_id: &AgentId) -> Option<Arc<dyn AgentSession>> {
|
||||
self.session_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Retourne l'[`SessionId`] de la session vivante hébergeant `agent_id`, si any.
|
||||
#[must_use]
|
||||
pub fn session_id_for_agent(&self, agent_id: &AgentId) -> Option<SessionId> {
|
||||
pub fn session_id_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<SessionId> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.find(|e| &e.agent_id == agent_id)
|
||||
.map(|e| e.session.id())
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::session_id_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn session_id_for_agent(&self, agent_id: &AgentId) -> Option<SessionId> {
|
||||
self.session_id_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Retourne le [`NodeId`] de la cellule vivante hébergeant `agent_id`, si any.
|
||||
///
|
||||
/// Jumeau de [`TerminalSessions::node_for_agent`].
|
||||
#[must_use]
|
||||
pub fn node_for_agent(&self, agent_id: &AgentId) -> Option<NodeId> {
|
||||
pub fn node_for_agent_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<NodeId> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.find(|e| &e.agent_id == agent_id)
|
||||
.map(|e| e.node_id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must use
|
||||
/// [`Self::node_for_agent_in_project`].
|
||||
#[must_use]
|
||||
pub fn node_for_agent(&self, agent_id: &AgentId) -> Option<NodeId> {
|
||||
self.node_for_agent_in_project(ProjectId::from_uuid(uuid::Uuid::nil()), agent_id)
|
||||
}
|
||||
|
||||
/// Résout les coordonnées `(agent_id, node_id, conversation_id)` d'une session structurée par son
|
||||
/// [`SessionId`] (LS7, tap niveau 1 des limites de session, §21.10).
|
||||
///
|
||||
@ -473,10 +615,19 @@ impl StructuredSessions {
|
||||
/// passer à [`SessionLimitService::on_rate_limited`](crate::SessionLimitService) sur
|
||||
/// un signal `RateLimited`. `None` si l'id n'est pas (ou plus) une session vivante.
|
||||
#[must_use]
|
||||
pub fn meta_for_session(&self, id: &SessionId) -> Option<(AgentId, NodeId, Option<String>)> {
|
||||
pub fn meta_for_session(
|
||||
&self,
|
||||
id: &SessionId,
|
||||
) -> Option<(ProjectId, AgentId, NodeId, Option<String>)> {
|
||||
self.entries.lock().ok().and_then(|m| {
|
||||
m.get(id)
|
||||
.map(|e| (e.agent_id, e.node_id, e.session.conversation_id()))
|
||||
m.get(id).map(|e| {
|
||||
(
|
||||
e.project_id,
|
||||
e.agent_id,
|
||||
e.node_id,
|
||||
e.session.conversation_id(),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -485,17 +636,28 @@ impl StructuredSessions {
|
||||
/// Jumeau de [`TerminalSessions::live_agents`] : un tuple
|
||||
/// `(AgentId, NodeId, SessionId)` par session structurée vivante.
|
||||
#[must_use]
|
||||
pub fn live_agents(&self) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
pub fn live_agents_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
self.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.filter(|e| e.project_id == project_id)
|
||||
.map(|e| (e.agent_id, e.node_id, e.session.id()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code should prefer
|
||||
/// [`Self::live_agents_in_project`] or [`LiveSessions::live_agent_snapshots`].
|
||||
#[must_use]
|
||||
pub fn live_agents(&self) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
self.live_agents_in_project(ProjectId::from_uuid(uuid::Uuid::nil()))
|
||||
}
|
||||
|
||||
/// Rebinde la session vivante d'un agent vers une nouvelle cellule-vue sans
|
||||
/// redémarrer la conversation (« la cellule est une vue », §17.6).
|
||||
///
|
||||
@ -503,16 +665,33 @@ impl StructuredSessions {
|
||||
/// change ; la session, son id et sa conversation restent intacts. Retourne la
|
||||
/// session rebindée, ou `None` si l'agent n'a pas de session vivante.
|
||||
#[must_use]
|
||||
pub fn rebind_agent_node_in_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
node_id: NodeId,
|
||||
) -> Option<Arc<dyn AgentSession>> {
|
||||
self.entries.lock().ok().and_then(|mut m| {
|
||||
let entry = m
|
||||
.values_mut()
|
||||
.find(|e| e.project_id == project_id && &e.agent_id == agent_id)?;
|
||||
entry.node_id = node_id;
|
||||
Some(Arc::clone(&entry.session))
|
||||
})
|
||||
}
|
||||
|
||||
/// Legacy project-less test helper. Production code must pass `project_id`.
|
||||
#[must_use]
|
||||
pub fn rebind_agent_node(
|
||||
&self,
|
||||
agent_id: &AgentId,
|
||||
node_id: NodeId,
|
||||
) -> Option<Arc<dyn AgentSession>> {
|
||||
self.entries.lock().ok().and_then(|mut m| {
|
||||
let entry = m.values_mut().find(|e| &e.agent_id == agent_id)?;
|
||||
entry.node_id = node_id;
|
||||
Some(Arc::clone(&entry.session))
|
||||
})
|
||||
self.rebind_agent_node_in_project(
|
||||
ProjectId::from_uuid(uuid::Uuid::nil()),
|
||||
agent_id,
|
||||
node_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Retire une session du registre, retournant la session si présente (pour que
|
||||
@ -605,25 +784,35 @@ impl LiveSessions {
|
||||
|
||||
/// L'[`SessionId`] de la session vivante d'un agent, PTY **ou** structurée.
|
||||
#[must_use]
|
||||
pub fn session_id_for_agent(&self, agent_id: &AgentId) -> Option<SessionId> {
|
||||
pub fn session_id_for_agent(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
agent_id: &AgentId,
|
||||
) -> Option<SessionId> {
|
||||
self.pty
|
||||
.session_for_agent(agent_id)
|
||||
.or_else(|| self.structured.session_id_for_agent(agent_id))
|
||||
.session_for_agent_in_project(project_id, agent_id)
|
||||
.or_else(|| {
|
||||
self.structured
|
||||
.session_id_for_agent_in_project(project_id, agent_id)
|
||||
})
|
||||
}
|
||||
|
||||
/// La cellule hôte de la session vivante d'un agent, PTY **ou** structurée.
|
||||
#[must_use]
|
||||
pub fn node_for_agent(&self, agent_id: &AgentId) -> Option<NodeId> {
|
||||
pub fn node_for_agent(&self, project_id: ProjectId, agent_id: &AgentId) -> Option<NodeId> {
|
||||
self.pty
|
||||
.node_for_agent(agent_id)
|
||||
.or_else(|| self.structured.node_for_agent(agent_id))
|
||||
.node_for_agent_in_project(project_id, agent_id)
|
||||
.or_else(|| {
|
||||
self.structured
|
||||
.node_for_agent_in_project(project_id, agent_id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Tous les agents vivants des deux registres (PTY puis structurés).
|
||||
#[must_use]
|
||||
pub fn live_agents(&self) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
let mut all = self.pty.live_agents();
|
||||
all.extend(self.structured.live_agents());
|
||||
pub fn live_agents(&self, project_id: ProjectId) -> Vec<(AgentId, NodeId, SessionId)> {
|
||||
let mut all = self.pty.live_agents_in_project(project_id);
|
||||
all.extend(self.structured.live_agents_in_project(project_id));
|
||||
all
|
||||
}
|
||||
|
||||
@ -632,30 +821,48 @@ impl LiveSessions {
|
||||
pub fn live_agent_snapshots(&self) -> Vec<LiveSessionSnapshot> {
|
||||
let mut all: Vec<LiveSessionSnapshot> = self
|
||||
.pty
|
||||
.live_agents()
|
||||
.into_iter()
|
||||
.map(|(agent_id, node_id, session_id)| LiveSessionSnapshot {
|
||||
agent_id,
|
||||
node_id,
|
||||
session_id,
|
||||
kind: LiveSessionKind::Pty,
|
||||
.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.filter_map(|e| match e.session.kind {
|
||||
SessionKind::Agent { agent_id } => Some(LiveSessionSnapshot {
|
||||
project_id: e.project_id,
|
||||
agent_id,
|
||||
node_id: e.session.node_id,
|
||||
session_id: e.session.id,
|
||||
kind: LiveSessionKind::Pty,
|
||||
}),
|
||||
SessionKind::Plain => None,
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
all.extend(self.structured.live_agents().into_iter().map(
|
||||
|(agent_id, node_id, session_id)| LiveSessionSnapshot {
|
||||
agent_id,
|
||||
node_id,
|
||||
session_id,
|
||||
kind: LiveSessionKind::Structured,
|
||||
},
|
||||
));
|
||||
.unwrap_or_default();
|
||||
all.extend(
|
||||
self.structured
|
||||
.entries
|
||||
.lock()
|
||||
.map(|m| {
|
||||
m.values()
|
||||
.map(|e| LiveSessionSnapshot {
|
||||
project_id: e.project_id,
|
||||
agent_id: e.agent_id,
|
||||
node_id: e.node_id,
|
||||
session_id: e.session.id(),
|
||||
kind: LiveSessionKind::Structured,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
all
|
||||
}
|
||||
}
|
||||
|
||||
impl LiveAgentRegistry for LiveSessions {
|
||||
fn is_agent_live(&self, agent_id: &AgentId) -> bool {
|
||||
self.pty.is_agent_live(agent_id) || self.structured.is_agent_live(agent_id)
|
||||
fn is_agent_live(&self, project_id: ProjectId, agent_id: &AgentId) -> bool {
|
||||
self.pty.is_agent_live(project_id, agent_id)
|
||||
|| self.structured.is_agent_live(project_id, agent_id)
|
||||
}
|
||||
|
||||
fn is_node_live(&self, node_id: &NodeId) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user