fix(runtime): isolation d'usage multi-projet #107

Implémentation du garde ModelServerProjectUseGuard pour séparer
les retours des agents entre projets concurrents.

- Garde RAII par LocalModelServerId partagé entre projets
- Refus inter-projets concurrent via model_server_in_use
- Partage intra-projet conservé avec refcount
- Libération automatique à la fermeture/erreur de session

Tests de validation: rejet projet distinct, compteur refs, libération retrait
This commit is contained in:
2026-07-27 09:47:23 +02:00
parent c807a70fea
commit cf074a7d61
6 changed files with 265 additions and 15 deletions

View File

@ -13,6 +13,8 @@ use domain::conversation::ConversationId;
use domain::ports::{AgentSession, PtyHandle};
use domain::{AgentId, IssueRef, NodeId, ProjectId, SessionId, SessionKind, TerminalSession};
use crate::model_server::ModelServerProjectUseGuard;
/// Runtime family of a live agent session.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LiveSessionKind {
@ -38,11 +40,12 @@ pub struct LiveSessionSnapshot {
}
/// A registered, live terminal: its PTY handle plus the domain snapshot.
#[derive(Debug, Clone)]
#[derive(Debug)]
struct Entry {
project_id: ProjectId,
handle: PtyHandle,
session: TerminalSession,
_model_server_guard: Option<ModelServerProjectUseGuard>,
}
/// Read-only liveness query over the agents that currently own a live PTY.
@ -164,6 +167,18 @@ impl TerminalSessions {
project_id: ProjectId,
handle: PtyHandle,
session: TerminalSession,
) {
self.insert_in_project_with_model_server_guard(project_id, handle, session, None);
}
/// Inserts a freshly-opened session and retains a model-server usage guard
/// until the session is removed.
pub fn insert_in_project_with_model_server_guard(
&self,
project_id: ProjectId,
handle: PtyHandle,
session: TerminalSession,
model_server_guard: Option<ModelServerProjectUseGuard>,
) {
if let Ok(mut map) = self.entries.lock() {
map.insert(
@ -172,6 +187,7 @@ impl TerminalSessions {
project_id,
handle,
session,
_model_server_guard: model_server_guard,
},
);
}
@ -388,6 +404,8 @@ struct StructuredEntry {
agent_id: AgentId,
/// La cellule (feuille de layout) qui héberge actuellement la vue.
node_id: NodeId,
/// Optional local-model-server usage guard retained for the live session.
_model_server_guard: Option<ModelServerProjectUseGuard>,
}
#[derive(Clone)]
@ -446,6 +464,21 @@ impl StructuredSessions {
session: Arc<dyn AgentSession>,
agent_id: AgentId,
node_id: NodeId,
) {
self.insert_in_project_with_model_server_guard(
project_id, session, agent_id, node_id, None,
);
}
/// Enregistre une session structurée et retient un garde d'usage de serveur
/// local jusqu'au retrait de la session.
pub fn insert_in_project_with_model_server_guard(
&self,
project_id: ProjectId,
session: Arc<dyn AgentSession>,
agent_id: AgentId,
node_id: NodeId,
model_server_guard: Option<ModelServerProjectUseGuard>,
) {
if let Ok(mut map) = self.entries.lock() {
let id = session.id();
@ -456,6 +489,7 @@ impl StructuredSessions {
session,
agent_id,
node_id,
_model_server_guard: model_server_guard,
},
);
}