Skip to main content

agent_code_lib/services/
session_env.rs

1//! Session environment setup.
2//!
3//! Initializes the runtime environment at session start:
4//! - Detects project root (git root or cwd)
5//! - Loads memory context
6//! - Sets up safe environment variables
7//! - Configures signal handlers
8//! - Prepares the session state
9
10use std::path::{Path, PathBuf};
11
12/// Resolved session environment.
13#[derive(Debug, Clone)]
14pub struct SessionEnvironment {
15    /// Original working directory at startup.
16    pub original_cwd: PathBuf,
17    /// Detected project root (git root or cwd).
18    pub project_root: PathBuf,
19    /// Whether we're inside a git repository.
20    pub is_git_repo: bool,
21    /// Current git branch (if in a repo).
22    pub git_branch: Option<String>,
23    /// Platform identifier.
24    pub platform: String,
25    /// Default shell.
26    pub shell: String,
27    /// Whether the session is interactive (has a TTY).
28    pub is_interactive: bool,
29    /// Terminal width in columns.
30    pub terminal_width: u16,
31}
32
33impl SessionEnvironment {
34    /// Detect and build the session environment.
35    pub async fn detect() -> Self {
36        let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
37
38        let project_root = detect_project_root(&cwd).await;
39        let is_git = crate::services::git::is_git_repo(&cwd).await;
40        let branch = if is_git {
41            crate::services::git::current_branch(&cwd).await
42        } else {
43            None
44        };
45
46        let terminal_width = crossterm::terminal::size().map(|(w, _)| w).unwrap_or(80);
47
48        Self {
49            original_cwd: cwd,
50            project_root,
51            is_git_repo: is_git,
52            git_branch: branch,
53            platform: std::env::consts::OS.to_string(),
54            shell: detect_shell(),
55            is_interactive: atty_check(),
56            terminal_width,
57        }
58    }
59}
60
61/// Walk up from cwd to find the project root (git root or first dir with config).
62async fn detect_project_root(cwd: &Path) -> PathBuf {
63    // Try git root first.
64    if let Some(root) = crate::services::git::repo_root(cwd).await {
65        return PathBuf::from(root);
66    }
67
68    // Look for project markers.
69    let markers = [
70        ".rc",
71        "Cargo.toml",
72        "package.json",
73        "pyproject.toml",
74        "go.mod",
75    ];
76    let mut dir = cwd.to_path_buf();
77    loop {
78        for marker in &markers {
79            if dir.join(marker).exists() {
80                return dir;
81            }
82        }
83        if !dir.pop() {
84            break;
85        }
86    }
87
88    cwd.to_path_buf()
89}
90
91fn detect_shell() -> String {
92    std::env::var("SHELL").unwrap_or_else(|_| "bash".to_string())
93}
94
95fn atty_check() -> bool {
96    #[cfg(unix)]
97    {
98        // SAFETY: isatty is a standard POSIX function with no preconditions.
99        unsafe { libc::isatty(0) != 0 }
100    }
101    #[cfg(not(unix))]
102    {
103        true
104    }
105}