feat(infra): temp IdeA launch-scoped alloué par lancement d'agent, injecté via TMPDIR/TMP/TEMP, writable_roots + release au stop, fail-closed — #200 (QA verte)

This commit is contained in:
2026-08-14 23:55:54 +02:00
parent 1d10a6ee80
commit 1157de1dda
5 changed files with 558 additions and 45 deletions

View File

@ -15,6 +15,22 @@ use domain::{AgentId, IssueRef, NodeId, ProjectId, SessionId, SessionKind, Termi
use crate::model_server::ModelServerUseGuard;
/// Runtime resources attached to a live PTY session and released when it stops.
pub struct RemovedPtySession {
/// Live PTY handle to kill.
pub handle: PtyHandle,
/// Launch-scoped managed-temp handle retained for release.
pub managed_temp_handle: Option<String>,
}
/// Runtime resources attached to a live structured session and released when it stops.
pub struct RemovedStructuredSession {
/// Live structured session to shut down.
pub session: Arc<dyn AgentSession>,
/// Launch-scoped managed-temp handle retained for release.
pub managed_temp_handle: Option<String>,
}
/// Runtime family of a live agent session.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LiveSessionKind {
@ -103,6 +119,7 @@ struct Entry {
project_id: ProjectId,
handle: PtyHandle,
session: TerminalSession,
managed_temp_handle: Option<String>,
_model_server_guard: Option<ModelServerUseGuard>,
}
@ -262,6 +279,24 @@ impl TerminalSessions {
handle: PtyHandle,
session: TerminalSession,
model_server_guard: Option<ModelServerUseGuard>,
) {
self.insert_in_project_with_runtime_resources(
project_id,
handle,
session,
model_server_guard,
None,
);
}
/// Inserts a freshly-opened session and retains runtime resources until removal.
pub fn insert_in_project_with_runtime_resources(
&self,
project_id: ProjectId,
handle: PtyHandle,
session: TerminalSession,
model_server_guard: Option<ModelServerUseGuard>,
managed_temp_handle: Option<String>,
) {
if let Ok(mut map) = self.entries.lock() {
map.insert(
@ -270,6 +305,7 @@ impl TerminalSessions {
project_id,
handle,
session,
managed_temp_handle,
_model_server_guard: model_server_guard,
},
);
@ -445,13 +481,20 @@ impl TerminalSessions {
/// 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> {
self.remove_runtime(id).map(|removed| removed.handle)
}
/// Removes a session and returns all runtime resources retained for it.
pub fn remove_runtime(&self, id: &SessionId) -> Option<RemovedPtySession> {
if let Ok(mut c) = self.conversations.lock() {
c.retain(|_, sid| sid != id);
}
self.entries
.lock()
.ok()
.and_then(|mut m| m.remove(id).map(|e| e.handle))
self.entries.lock().ok().and_then(|mut m| {
m.remove(id).map(|e| RemovedPtySession {
handle: e.handle,
managed_temp_handle: e.managed_temp_handle,
})
})
}
/// Number of currently-registered sessions.
@ -487,6 +530,8 @@ struct StructuredEntry {
agent_id: AgentId,
/// La cellule (feuille de layout) qui héberge actuellement la vue.
node_id: NodeId,
/// Launch-scoped managed-temp handle retained for release.
managed_temp_handle: Option<String>,
/// Optional local-model-server usage guard retained for the live session.
_model_server_guard: Option<ModelServerUseGuard>,
}
@ -579,6 +624,26 @@ impl StructuredSessions {
agent_id: AgentId,
node_id: NodeId,
model_server_guard: Option<ModelServerUseGuard>,
) {
self.insert_in_project_with_runtime_resources(
project_id,
session,
agent_id,
node_id,
model_server_guard,
None,
);
}
/// Enregistre une session structurée et retient ses ressources runtime.
pub fn insert_in_project_with_runtime_resources(
&self,
project_id: ProjectId,
session: Arc<dyn AgentSession>,
agent_id: AgentId,
node_id: NodeId,
model_server_guard: Option<ModelServerUseGuard>,
managed_temp_handle: Option<String>,
) {
if let Ok(mut map) = self.entries.lock() {
let id = session.id();
@ -589,6 +654,7 @@ impl StructuredSessions {
session,
agent_id,
node_id,
managed_temp_handle,
_model_server_guard: model_server_guard,
},
);
@ -831,17 +897,30 @@ impl StructuredSessions {
/// Retire une session du registre, retournant la session si présente (pour que
/// l'appelant la `shutdown` hors du verrou).
pub fn remove(&self, id: &SessionId) -> Option<Arc<dyn AgentSession>> {
self.remove_runtime(id).map(|removed| removed.session)
}
/// Retire une session du registre, retournant ses ressources runtime.
pub fn remove_runtime(&self, id: &SessionId) -> Option<RemovedStructuredSession> {
self.entries
.lock()
.ok()
.and_then(|mut m| m.remove(id).map(|e| e.session))
.and_then(|mut m| {
m.remove(id).map(|e| RemovedStructuredSession {
session: e.session,
managed_temp_handle: e.managed_temp_handle,
})
})
.or_else(|| {
self.ticket_assistants.lock().ok().and_then(|mut m| {
let issue_ref = m
.iter()
.find(|(_, e)| e.session.id() == *id)
.map(|(issue_ref, _)| *issue_ref)?;
m.remove(&issue_ref).map(|e| e.session)
m.remove(&issue_ref).map(|e| RemovedStructuredSession {
session: e.session,
managed_temp_handle: None,
})
})
})
}