Skip to main content

algocline_app/service/
mod.rs

1mod config;
2mod engine_api_impl;
3mod eval;
4mod eval_store;
5mod logging;
6pub(crate) mod manifest;
7pub(crate) mod path;
8mod pkg;
9pub mod resolve;
10mod run;
11mod scenario;
12mod status;
13mod transcript;
14
15#[cfg(test)]
16mod tests;
17
18use std::path::Path;
19use std::sync::Arc;
20
21use algocline_engine::{Executor, SessionRegistry};
22
23pub use algocline_core::{EngineApi, TokenUsage};
24pub use config::{AppConfig, LogDirSource};
25pub use resolve::{QueryResponse, SearchPath};
26
27// ─── Application Service ────────────────────────────────────────
28
29/// Tracks which sessions are eval sessions and their strategy name.
30///
31/// `std::sync::Mutex` is used (not tokio) because all operations are
32/// single HashMap insert/remove/get completing in microseconds, and no
33/// `.await` is held across the lock. Called from async context but never
34/// held across yield points. Poison is silently skipped — eval tracking
35/// is non-critical metadata.
36type EvalSessions = std::sync::Mutex<std::collections::HashMap<String, String>>;
37
38/// Tracks session_id → strategy name for all strategy-based sessions (advice, eval).
39///
40/// Same locking rationale as `EvalSessions`. Used by `alc_status` and
41/// transcript logging. Poison is silently skipped — strategy name is
42/// non-critical metadata for observability.
43type SessionStrategies = std::sync::Mutex<std::collections::HashMap<String, String>>;
44
45#[derive(Clone)]
46pub struct AppService {
47    executor: Arc<Executor>,
48    registry: Arc<SessionRegistry>,
49    log_config: AppConfig,
50    /// Package search paths in priority order (first = highest).
51    search_paths: Vec<resolve::SearchPath>,
52    /// session_id → strategy name for eval sessions (cleared on completion).
53    eval_sessions: Arc<EvalSessions>,
54    /// session_id → strategy name for log/stats tracking (cleared on session completion).
55    session_strategies: Arc<SessionStrategies>,
56}
57
58impl AppService {
59    pub fn new(
60        executor: Arc<Executor>,
61        log_config: AppConfig,
62        search_paths: Vec<resolve::SearchPath>,
63    ) -> Self {
64        let registry = Arc::new(SessionRegistry::new());
65        // TTL = 3 hours. Complex strategies may run 30–60 min; 3h covers
66        // legitimate paused sessions while eventually reclaiming abandoned ones.
67        registry.spawn_gc_task(std::time::Duration::from_secs(10800));
68        Self {
69            executor,
70            registry,
71            log_config,
72            search_paths,
73            eval_sessions: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
74            session_strategies: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
75        }
76    }
77
78    /// Returns the log directory, or an error if file logging is unavailable.
79    fn require_log_dir(&self) -> Result<&Path, String> {
80        self.log_config
81            .log_dir
82            .as_deref()
83            .ok_or_else(|| "File logging is not available (no writable log directory)".to_string())
84    }
85}