fix(runtime): isolate agent state by project (#101)
This commit is contained in:
@ -22,7 +22,9 @@ use async_trait::async_trait;
|
||||
|
||||
use application::{LiveAgentRegistry, LiveSessions, StructuredSessions, TerminalSessions};
|
||||
use domain::ports::{AgentSession, AgentSessionError, PtyHandle, ReplyStream};
|
||||
use domain::{AgentId, NodeId, ProjectPath, PtySize, SessionId, SessionKind, TerminalSession};
|
||||
use domain::{
|
||||
AgentId, NodeId, ProjectId, ProjectPath, PtySize, SessionId, SessionKind, TerminalSession,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
// --- petits constructeurs déterministes ------------------------------------
|
||||
@ -33,6 +35,9 @@ fn sid(n: u128) -> SessionId {
|
||||
fn aid(n: u128) -> AgentId {
|
||||
AgentId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
fn pid(n: u128) -> ProjectId {
|
||||
ProjectId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
fn nid(n: u128) -> NodeId {
|
||||
NodeId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
@ -129,7 +134,7 @@ fn structured_meta_for_session_resolves_agent_node_and_conversation() {
|
||||
|
||||
assert_eq!(
|
||||
reg.meta_for_session(&s),
|
||||
Some((a, n, Some("conv-live".to_owned())))
|
||||
Some((pid(0), a, n, Some("conv-live".to_owned())))
|
||||
);
|
||||
|
||||
// Id inconnu (ou retiré) ⇒ None (jamais de panique sur une session morte).
|
||||
@ -158,11 +163,11 @@ fn structured_one_live_session_per_agent_invariant() {
|
||||
// L'agent n'a pas de session vivante avant insertion.
|
||||
let reg = StructuredSessions::new();
|
||||
let a = aid(10);
|
||||
assert!(!reg.is_agent_live(&a));
|
||||
assert!(!reg.is_agent_live(pid(0), &a));
|
||||
assert!(reg.session_for_agent(&a).is_none());
|
||||
|
||||
reg.insert(fake(sid(1)), a, nid(100));
|
||||
assert!(reg.is_agent_live(&a));
|
||||
assert!(reg.is_agent_live(pid(0), &a));
|
||||
|
||||
// `session_for_agent` est non ambigu : il rend LA session de l'agent.
|
||||
let resolved = reg.session_for_agent(&a).unwrap().id();
|
||||
@ -200,14 +205,14 @@ fn structured_live_agent_registry_impl() {
|
||||
let a = aid(10);
|
||||
let n = nid(100);
|
||||
|
||||
assert!(!reg.is_agent_live(&a));
|
||||
assert!(!reg.is_agent_live(pid(0), &a));
|
||||
assert!(!reg.is_node_live(&n));
|
||||
|
||||
reg.insert(fake(sid(1)), a, n);
|
||||
|
||||
assert!(reg.is_agent_live(&a));
|
||||
assert!(reg.is_agent_live(pid(0), &a));
|
||||
assert!(reg.is_node_live(&n));
|
||||
assert!(!reg.is_agent_live(&aid(999)));
|
||||
assert!(!reg.is_agent_live(pid(0), &aid(999)));
|
||||
assert!(!reg.is_node_live(&nid(999)));
|
||||
|
||||
// is_node_live suit le rebind (la cellule vivante change).
|
||||
@ -233,6 +238,17 @@ fn structured_sessions_snapshot_for_global_shutdown() {
|
||||
|
||||
/// Insère un agent PTY dans `TerminalSessions`.
|
||||
fn insert_pty(pty: &TerminalSessions, s: SessionId, a: AgentId, n: NodeId) {
|
||||
insert_pty_in_project(pty, pid(0), s, a, n);
|
||||
}
|
||||
|
||||
/// Insère un agent PTY dans `TerminalSessions` pour un projet explicite.
|
||||
fn insert_pty_in_project(
|
||||
pty: &TerminalSessions,
|
||||
project_id: ProjectId,
|
||||
s: SessionId,
|
||||
a: AgentId,
|
||||
n: NodeId,
|
||||
) {
|
||||
let session = TerminalSession::starting(
|
||||
s,
|
||||
n,
|
||||
@ -240,7 +256,7 @@ fn insert_pty(pty: &TerminalSessions, s: SessionId, a: AgentId, n: NodeId) {
|
||||
SessionKind::Agent { agent_id: a },
|
||||
PtySize::new(24, 80).unwrap(),
|
||||
);
|
||||
pty.insert(PtyHandle { session_id: s }, session);
|
||||
pty.insert_in_project(project_id, PtyHandle { session_id: s }, session);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -252,10 +268,10 @@ fn aggregator_agent_live_via_structured_only() {
|
||||
let a = aid(10);
|
||||
structured.insert(fake(sid(1)), a, nid(100));
|
||||
|
||||
assert!(agg.is_agent_live(&a), "live in structured ⇒ true");
|
||||
assert!(agg.is_agent_live(pid(0), &a), "live in structured ⇒ true");
|
||||
assert!(agg.is_node_live(&nid(100)));
|
||||
assert_eq!(agg.session_id_for_agent(&a), Some(sid(1)));
|
||||
assert_eq!(agg.node_for_agent(&a), Some(nid(100)));
|
||||
assert_eq!(agg.session_id_for_agent(pid(0), &a), Some(sid(1)));
|
||||
assert_eq!(agg.node_for_agent(pid(0), &a), Some(nid(100)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -267,10 +283,10 @@ fn aggregator_agent_live_via_pty_only() {
|
||||
let a = aid(20);
|
||||
insert_pty(&pty, sid(2), a, nid(200));
|
||||
|
||||
assert!(agg.is_agent_live(&a), "live in PTY ⇒ true");
|
||||
assert!(agg.is_agent_live(pid(0), &a), "live in PTY ⇒ true");
|
||||
assert!(agg.is_node_live(&nid(200)));
|
||||
assert_eq!(agg.session_id_for_agent(&a), Some(sid(2)));
|
||||
assert_eq!(agg.node_for_agent(&a), Some(nid(200)));
|
||||
assert_eq!(agg.session_id_for_agent(pid(0), &a), Some(sid(2)));
|
||||
assert_eq!(agg.node_for_agent(pid(0), &a), Some(nid(200)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -280,11 +296,11 @@ fn aggregator_agent_absent_from_both_is_not_live() {
|
||||
let agg = LiveSessions::new(pty, structured);
|
||||
|
||||
let a = aid(30);
|
||||
assert!(!agg.is_agent_live(&a), "absent from both ⇒ false");
|
||||
assert!(!agg.is_agent_live(pid(0), &a), "absent from both ⇒ false");
|
||||
assert!(!agg.is_node_live(&nid(300)));
|
||||
assert!(agg.session_id_for_agent(&a).is_none());
|
||||
assert!(agg.node_for_agent(&a).is_none());
|
||||
assert!(agg.live_agents().is_empty());
|
||||
assert!(agg.session_id_for_agent(pid(0), &a).is_none());
|
||||
assert!(agg.node_for_agent(pid(0), &a).is_none());
|
||||
assert!(agg.live_agents(pid(0)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -296,7 +312,7 @@ fn aggregator_live_agents_concatenates_pty_then_structured() {
|
||||
insert_pty(&pty, sid(1), aid(10), nid(100));
|
||||
structured.insert(fake(sid(2)), aid(20), nid(200));
|
||||
|
||||
let all = agg.live_agents();
|
||||
let all = agg.live_agents(pid(0));
|
||||
assert_eq!(all.len(), 2, "both registries contribute");
|
||||
// PTY d'abord, structuré ensuite (ordre documenté de l'agrégateur).
|
||||
assert_eq!(all[0], (aid(10), nid(100), sid(1)));
|
||||
@ -317,14 +333,37 @@ fn aggregator_resolution_prefers_pty_then_falls_back_to_structured() {
|
||||
structured.insert(fake(sid(2)), struct_agent, nid(200));
|
||||
|
||||
// Agent PTY : résolu par le registre PTY.
|
||||
assert_eq!(agg.session_id_for_agent(&pty_agent), Some(sid(1)));
|
||||
assert_eq!(agg.node_for_agent(&pty_agent), Some(nid(100)));
|
||||
assert_eq!(agg.session_id_for_agent(pid(0), &pty_agent), Some(sid(1)));
|
||||
assert_eq!(agg.node_for_agent(pid(0), &pty_agent), Some(nid(100)));
|
||||
// Agent structuré : fallback sur le registre structuré.
|
||||
assert_eq!(agg.session_id_for_agent(&struct_agent), Some(sid(2)));
|
||||
assert_eq!(agg.node_for_agent(&struct_agent), Some(nid(200)));
|
||||
assert_eq!(
|
||||
agg.session_id_for_agent(pid(0), &struct_agent),
|
||||
Some(sid(2))
|
||||
);
|
||||
assert_eq!(agg.node_for_agent(pid(0), &struct_agent), Some(nid(200)));
|
||||
|
||||
// is_node_live : OR sur les deux.
|
||||
assert!(agg.is_node_live(&nid(100)));
|
||||
assert!(agg.is_node_live(&nid(200)));
|
||||
assert!(!agg.is_node_live(&nid(999)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_agent_id_in_distinct_projects_has_isolated_live_sessions() {
|
||||
let pty = Arc::new(TerminalSessions::new());
|
||||
let structured = Arc::new(StructuredSessions::new());
|
||||
let agg = LiveSessions::new(Arc::clone(&pty), Arc::clone(&structured));
|
||||
let a = aid(10);
|
||||
|
||||
insert_pty_in_project(&pty, pid(1), sid(1), a, nid(100));
|
||||
structured.insert_in_project(pid(2), fake(sid(2)), a, nid(200));
|
||||
|
||||
assert!(agg.is_agent_live(pid(1), &a));
|
||||
assert!(agg.is_agent_live(pid(2), &a));
|
||||
assert_eq!(agg.session_id_for_agent(pid(1), &a), Some(sid(1)));
|
||||
assert_eq!(agg.node_for_agent(pid(1), &a), Some(nid(100)));
|
||||
assert_eq!(agg.session_id_for_agent(pid(2), &a), Some(sid(2)));
|
||||
assert_eq!(agg.node_for_agent(pid(2), &a), Some(nid(200)));
|
||||
assert_eq!(agg.live_agents(pid(1)), vec![(a, nid(100), sid(1))]);
|
||||
assert_eq!(agg.live_agents(pid(2)), vec![(a, nid(200), sid(2))]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user