gestura_core/agent_sessions/
mod.rs

1//! Agent session model + persistence.
2//!
3//! Session types and storage logic are defined in `gestura-core-sessions` and
4//! re-exported here.  This module adds config-dependent extensions
5//! ([`SessionToolSettingsConfigExt`]) and migration wrappers that depend on
6//! [`crate::config::AppConfig`].
7
8// Session types from the domain crate are part of this module's public API.
9pub use gestura_core_sessions::agent_sessions::*;
10
11use crate::config::{AppConfig, GlobalPermissionLevel, GlobalPermissionSettings};
12use std::path::Path;
13
14// ---------------------------------------------------------------------------
15// Extension trait: config-dependent constructors for SessionToolSettings
16// ---------------------------------------------------------------------------
17
18/// Config-dependent constructors for [`SessionToolSettings`].
19///
20/// These live here (not in the sessions crate) because they depend on
21/// [`GlobalPermissionSettings`] and [`AppConfig`] which remain in core's config
22/// module.
23pub trait SessionToolSettingsConfigExt {
24    /// Create session tool settings from the global permission settings.
25    fn from_global_permissions(settings: &GlobalPermissionSettings) -> SessionToolSettings;
26
27    /// Convenience helper to derive session tool settings from the full app config.
28    fn from_global_config(config: &AppConfig) -> SessionToolSettings;
29}
30
31impl SessionToolSettingsConfigExt for SessionToolSettings {
32    fn from_global_permissions(settings: &GlobalPermissionSettings) -> SessionToolSettings {
33        let permission_level = match settings.default_level {
34            GlobalPermissionLevel::Sandbox => SessionPermissionLevel::Sandbox,
35            GlobalPermissionLevel::Restricted => SessionPermissionLevel::Restricted,
36            GlobalPermissionLevel::Full => SessionPermissionLevel::Full,
37        };
38
39        SessionToolSettings {
40            permission_level,
41            enabled_tools: settings.default_enabled_tools.clone(),
42        }
43    }
44
45    fn from_global_config(config: &AppConfig) -> SessionToolSettings {
46        Self::from_global_permissions(&config.permissions)
47    }
48}
49
50/// Resolve whether experiential reflection is enabled for a session.
51///
52/// Sessions use sparse override semantics: when `state.reflection_settings` is
53/// absent or its `enabled` field is `None`, the current global configuration is
54/// used as the effective default.
55pub fn effective_session_reflection_enabled(state: &SessionState, config: &AppConfig) -> bool {
56    state
57        .reflection_settings
58        .as_ref()
59        .and_then(|settings| settings.enabled)
60        .unwrap_or(config.pipeline.reflection.enabled)
61}
62
63// ---------------------------------------------------------------------------
64// Wrapper: sanitize_session_llm_override (injects concrete validator)
65// ---------------------------------------------------------------------------
66
67/// Sanitize potentially invalid persisted (provider, model) overrides.
68///
69/// This wrapper injects [`crate::llm_validation::is_model_compatible_with_provider`]
70/// as the concrete model-compatibility validator.
71///
72/// Returns `true` if any repair was applied.
73pub fn sanitize_session_llm_override(
74    session_id: &str,
75    state: &mut SessionState,
76    global_llm_provider: &str,
77) -> bool {
78    gestura_core_sessions::agent_sessions::sanitize_session_llm_override(
79        session_id,
80        state,
81        global_llm_provider,
82        crate::llm_validation::is_model_compatible_with_provider,
83    )
84}
85
86// ---------------------------------------------------------------------------
87// Wrapper: migrate_legacy_gui_sessions_to_core (injects concrete validator)
88// ---------------------------------------------------------------------------
89
90/// One-time migration from the legacy `gui_sessions.json` file into the unified
91/// core store.
92///
93/// This wrapper injects the concrete model-compatibility validator.
94pub fn migrate_legacy_gui_sessions_to_core<S: AgentSessionStore>(
95    store: &S,
96    global_llm_provider: &str,
97) -> Vec<AgentSession> {
98    gestura_core_sessions::agent_sessions::migrate_legacy_gui_sessions_to_core(
99        store,
100        global_llm_provider,
101        crate::llm_validation::is_model_compatible_with_provider,
102    )
103}
104
105/// One-time migration from a legacy GUI sessions file at a specific path.
106///
107/// This wrapper injects the concrete model-compatibility validator.
108pub fn migrate_legacy_gui_sessions_to_core_at_path<S: AgentSessionStore>(
109    store: &S,
110    global_llm_provider: &str,
111    path: &Path,
112) -> Vec<AgentSession> {
113    gestura_core_sessions::agent_sessions::migrate_legacy_gui_sessions_to_core_at_path(
114        store,
115        global_llm_provider,
116        path,
117        &crate::llm_validation::is_model_compatible_with_provider,
118    )
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::config::{GlobalPermissionLevel, GlobalPermissionSettings};
125    use std::collections::HashMap;
126
127    #[test]
128    fn session_tool_settings_from_global_permissions_maps_level_and_tools() {
129        let mut tools = HashMap::new();
130        tools.insert("file".to_string(), true);
131        tools.insert("shell".to_string(), false);
132
133        let settings = GlobalPermissionSettings {
134            default_level: GlobalPermissionLevel::Sandbox,
135            default_enabled_tools: tools.clone(),
136        };
137
138        let session_tools = SessionToolSettings::from_global_permissions(&settings);
139        assert_eq!(
140            session_tools.permission_level,
141            SessionPermissionLevel::Sandbox
142        );
143        assert_eq!(session_tools.enabled_tools, tools);
144    }
145
146    #[test]
147    fn effective_session_reflection_enabled_falls_back_to_global_default() {
148        let mut config = AppConfig::default();
149        config.pipeline.reflection.enabled = true;
150
151        let state = SessionState::default();
152        assert!(effective_session_reflection_enabled(&state, &config));
153
154        config.pipeline.reflection.enabled = false;
155        assert!(!effective_session_reflection_enabled(&state, &config));
156    }
157
158    #[test]
159    fn effective_session_reflection_enabled_honors_session_override() {
160        let mut config = AppConfig::default();
161        config.pipeline.reflection.enabled = true;
162
163        let state = SessionState {
164            reflection_settings: Some(SessionReflectionSettings {
165                enabled: Some(false),
166            }),
167            ..SessionState::default()
168        };
169
170        assert!(!effective_session_reflection_enabled(&state, &config));
171    }
172}