fix(runtime): isolate agent state by project (#101)
This commit is contained in:
@ -555,7 +555,7 @@ impl AgentSessionFactory for FakeFactory {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use application::ProviderSessionProvider;
|
||||
use domain::{ConversationId, ProviderSessionStore};
|
||||
use domain::{ConversationId, ConversationParty, ProviderSessionStore};
|
||||
|
||||
/// In-memory [`ProviderSessionStore`] for the P8b launch tests: a
|
||||
/// `(conversation, provider_id) → resumable_id` map, observable after the launch.
|
||||
@ -659,9 +659,13 @@ fn nid(n: u128) -> NodeId {
|
||||
NodeId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
fn project_id() -> ProjectId {
|
||||
ProjectId::from_uuid(Uuid::from_u128(1000))
|
||||
}
|
||||
|
||||
fn project() -> Project {
|
||||
Project::new(
|
||||
ProjectId::from_uuid(Uuid::from_u128(1000)),
|
||||
project_id(),
|
||||
"demo",
|
||||
ProjectPath::new(ROOT).unwrap(),
|
||||
RemoteRef::local(),
|
||||
@ -734,7 +738,7 @@ fn seed_live_pty_session(
|
||||
size,
|
||||
);
|
||||
session.status = domain::SessionStatus::Running;
|
||||
sessions.insert(PtyHandle { session_id }, session);
|
||||
sessions.insert_in_project(project_id(), PtyHandle { session_id }, session);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -815,12 +819,19 @@ async fn structured_launch_starts_session_registers_no_pty_spawn() {
|
||||
// La session est enregistrée dans le registre structuré, retrouvable par agent.
|
||||
let registered = f
|
||||
.structured
|
||||
.session_for_agent(&f.agent.id)
|
||||
.session_for_agent_in_project(project_id(), &f.agent.id)
|
||||
.expect("structured session registered");
|
||||
assert_eq!(registered.id(), sid(500), "session id is the factory's");
|
||||
assert_eq!(f.structured.node_for_agent(&f.agent.id), Some(nid(3)));
|
||||
assert_eq!(
|
||||
f.structured
|
||||
.node_for_agent_in_project(project_id(), &f.agent.id),
|
||||
Some(nid(3))
|
||||
);
|
||||
// Rien côté registre PTY.
|
||||
assert!(f.sessions.session_for_agent(&f.agent.id).is_none());
|
||||
assert!(f
|
||||
.sessions
|
||||
.session_for_agent_in_project(project_id(), &f.agent.id)
|
||||
.is_none());
|
||||
|
||||
// AgentLaunched publié avec l'id de session structurée.
|
||||
assert_eq!(
|
||||
@ -845,11 +856,15 @@ async fn structured_launch_starts_session_registers_no_pty_spawn() {
|
||||
SessionKind::Agent { agent_id } if agent_id == f.agent.id
|
||||
));
|
||||
// P8a (ARCHITECTURE §19.7) : la CELLULE porte l'**id de paire IdeA**, pas l'id
|
||||
// moteur. Cellule neuve, lancement direct (aucun requester) ⇒ `pair(User, agent)`
|
||||
// dérivé via `ConversationId::for_pair` = l'UUID de l'agent (`aid(1)`).
|
||||
// moteur. Cellule neuve, lancement direct (aucun requester) ⇒ `pair(project, User, agent)`.
|
||||
let expected_pair = ConversationId::for_project_pair(
|
||||
project_id(),
|
||||
ConversationParty::User,
|
||||
ConversationParty::agent(f.agent.id),
|
||||
);
|
||||
assert_eq!(
|
||||
out.assigned_conversation_id.as_deref(),
|
||||
Some("00000000-0000-0000-0000-000000000001"),
|
||||
Some(expected_pair.to_string().as_str()),
|
||||
"cell carries the IdeA pair id (pivot logique), not the engine resumable"
|
||||
);
|
||||
// L'id de session MOTEUR (resumable provider) part dans le cache séparé.
|
||||
@ -882,8 +897,15 @@ async fn non_structured_profile_takes_pty_path_unchanged() {
|
||||
);
|
||||
|
||||
// Session côté registre PTY, rien côté structuré.
|
||||
assert_eq!(f.sessions.session_for_agent(&f.agent.id), Some(sid(777)));
|
||||
assert!(f.structured.session_for_agent(&f.agent.id).is_none());
|
||||
assert_eq!(
|
||||
f.sessions
|
||||
.session_for_agent_in_project(project_id(), &f.agent.id),
|
||||
Some(sid(777))
|
||||
);
|
||||
assert!(f
|
||||
.structured
|
||||
.session_for_agent_in_project(project_id(), &f.agent.id)
|
||||
.is_none());
|
||||
|
||||
// output.structured = None ; session PTY classique.
|
||||
assert!(
|
||||
@ -937,7 +959,8 @@ async fn structured_launch_new_in_other_cell_refuses_when_live_elsewhere() {
|
||||
"still a single live structured session"
|
||||
);
|
||||
assert_eq!(
|
||||
f.structured.node_for_agent(&f.agent.id),
|
||||
f.structured
|
||||
.node_for_agent_in_project(project_id(), &f.agent.id),
|
||||
Some(host),
|
||||
"session stays pinned on its host node A"
|
||||
);
|
||||
@ -964,7 +987,11 @@ async fn structured_relaunch_same_node_rebinds_no_second_start() {
|
||||
assert_eq!(f.factory.start_count(), 1, "no second factory.start");
|
||||
assert_eq!(f.pty.spawn_count(), 0, "still no pty spawn");
|
||||
assert_eq!(f.structured.len(), 1, "single live structured session");
|
||||
assert_eq!(f.structured.node_for_agent(&f.agent.id), Some(host));
|
||||
assert_eq!(
|
||||
f.structured
|
||||
.node_for_agent_in_project(project_id(), &f.agent.id),
|
||||
Some(host)
|
||||
);
|
||||
let desc = out.structured.expect("descriptor on rebind");
|
||||
assert_eq!(desc.session_id, sid(500), "same live session id");
|
||||
assert_eq!(desc.node_id, host);
|
||||
@ -1002,7 +1029,11 @@ async fn structured_relaunch_other_cell_with_conversation_id_rebinds() {
|
||||
1,
|
||||
"still a single live structured session"
|
||||
);
|
||||
assert_eq!(f.structured.node_for_agent(&f.agent.id), Some(target));
|
||||
assert_eq!(
|
||||
f.structured
|
||||
.node_for_agent_in_project(project_id(), &f.agent.id),
|
||||
Some(target)
|
||||
);
|
||||
let desc = out.structured.expect("descriptor on rebind");
|
||||
assert_eq!(desc.session_id, sid(500), "same live session id");
|
||||
assert_eq!(desc.node_id, target);
|
||||
@ -1130,7 +1161,8 @@ async fn swap_structured_live_session_shuts_down_then_relaunches() {
|
||||
.start(&profile, &ctx, &cwd, &SessionPlan::None, None, &[], None)
|
||||
.await
|
||||
.expect("seed structured session");
|
||||
f.structured.insert(session, agent.id, host);
|
||||
f.structured
|
||||
.insert_in_project(project_id(), session, agent.id, host);
|
||||
}
|
||||
// La factory a maintenant été appelée 1 fois (le seed) ; reset logique : on
|
||||
// comptera les start APRÈS, donc on mémorise la base.
|
||||
@ -1182,8 +1214,16 @@ async fn swap_structured_live_session_shuts_down_then_relaunches() {
|
||||
relaunched.node_id, host,
|
||||
"relaunch reopens in the same cell"
|
||||
);
|
||||
assert_eq!(f.structured.session_id_for_agent(&agent.id), Some(sid(601)));
|
||||
assert_eq!(f.structured.node_for_agent(&agent.id), Some(host));
|
||||
assert_eq!(
|
||||
f.structured
|
||||
.session_id_for_agent_in_project(project_id(), &agent.id),
|
||||
Some(sid(601))
|
||||
);
|
||||
assert_eq!(
|
||||
f.structured
|
||||
.node_for_agent_in_project(project_id(), &agent.id),
|
||||
Some(host)
|
||||
);
|
||||
assert_eq!(
|
||||
f.structured.len(),
|
||||
1,
|
||||
@ -1230,8 +1270,15 @@ async fn swap_pty_live_session_keeps_a1_kill_behaviour() {
|
||||
);
|
||||
assert_eq!(relaunched.id, sid(777));
|
||||
// The relaunched session lives in the PTY registry, not the structured one.
|
||||
assert_eq!(f.sessions.session_for_agent(&agent.id), Some(sid(777)));
|
||||
assert!(f.structured.session_for_agent(&agent.id).is_none());
|
||||
assert_eq!(
|
||||
f.sessions
|
||||
.session_for_agent_in_project(project_id(), &agent.id),
|
||||
Some(sid(777))
|
||||
);
|
||||
assert!(f
|
||||
.structured
|
||||
.session_for_agent_in_project(project_id(), &agent.id)
|
||||
.is_none());
|
||||
assert_eq!(f.contexts.profile_of(&agent.id), Some(pid(2)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user