lean_ctx/tools/
server_paths.rs1use super::server::LeanCtxServer;
2use super::startup::{
3 has_project_marker, is_suspicious_root, maybe_derive_project_root_from_absolute,
4};
5
6impl LeanCtxServer {
7 pub fn checkpoint_interval_effective() -> usize {
8 if let Ok(v) = std::env::var("LEAN_CTX_CHECKPOINT_INTERVAL") {
9 if let Ok(parsed) = v.trim().parse::<usize>() {
10 return parsed;
11 }
12 }
13 let profile_interval = crate::core::profiles::active_profile()
14 .autonomy
15 .checkpoint_interval_effective();
16 if profile_interval > 0 {
17 return profile_interval as usize;
18 }
19 crate::core::config::Config::load().checkpoint_interval as usize
20 }
21
22 pub async fn resolve_path(&self, path: &str) -> Result<String, String> {
26 let normalized = crate::core::pathutil::normalize_tool_path(path);
27 if normalized.is_empty() || normalized == "." {
28 return Ok(normalized);
29 }
30 let p = std::path::Path::new(&normalized);
31
32 let (resolved, jail_root) = {
33 let session = self.session.read().await;
34 let jail_root = session
35 .project_root
36 .as_deref()
37 .or(session.shell_cwd.as_deref())
38 .unwrap_or(".")
39 .to_string();
40
41 let resolved = if p.is_absolute() || p.exists() {
42 std::path::PathBuf::from(&normalized)
43 } else if let Some(ref root) = session.project_root {
44 let joined = std::path::Path::new(root).join(&normalized);
45 if joined.exists() {
46 joined
47 } else if let Some(ref cwd) = session.shell_cwd {
48 std::path::Path::new(cwd).join(&normalized)
49 } else {
50 std::path::Path::new(&jail_root).join(&normalized)
51 }
52 } else if let Some(ref cwd) = session.shell_cwd {
53 std::path::Path::new(cwd).join(&normalized)
54 } else {
55 std::path::Path::new(&jail_root).join(&normalized)
56 };
57
58 (resolved, jail_root)
59 };
60
61 let jail_root_path = std::path::Path::new(&jail_root);
62 let jailed = match crate::core::pathjail::jail_path(&resolved, jail_root_path) {
63 Ok(p) => p,
64 Err(e) => {
65 if p.is_absolute() {
66 if let Some(new_root) = maybe_derive_project_root_from_absolute(&resolved) {
67 let cfg_allow = std::env::var("LEAN_CTX_ALLOW_REROOT").map_or_else(
68 |_| crate::core::config::Config::load().allow_auto_reroot,
69 |v| v == "1" || v == "true",
70 );
71 let candidate_under_jail = resolved.starts_with(jail_root_path);
72 let allow_reroot = if !cfg_allow || candidate_under_jail {
73 false
74 } else if let Some(ref trusted_root) = self.startup_project_root {
75 std::path::Path::new(trusted_root) == new_root.as_path()
76 } else {
77 !has_project_marker(jail_root_path)
78 || is_suspicious_root(jail_root_path)
79 };
80
81 if allow_reroot {
82 let mut session = self.session.write().await;
83 let new_root_str = new_root.to_string_lossy().to_string();
84 session.project_root = Some(new_root_str.clone());
85 session.shell_cwd = self
86 .startup_shell_cwd
87 .as_ref()
88 .filter(|cwd| std::path::Path::new(cwd).starts_with(&new_root))
89 .cloned()
90 .or_else(|| Some(new_root_str.clone()));
91 let _ = session.save();
92
93 crate::core::pathjail::jail_path(&resolved, &new_root)?
94 } else {
95 return Err(e);
96 }
97 } else {
98 return Err(e);
99 }
100 } else {
101 return Err(e);
102 }
103 }
104 };
105
106 crate::core::io_boundary::check_secret_path_for_tool("resolve_path", &jailed)?;
107
108 Ok(crate::core::pathutil::normalize_tool_path(
109 &jailed.to_string_lossy().replace('\\', "/"),
110 ))
111 }
112
113 pub async fn resolve_path_or_passthrough(&self, path: &str) -> String {
115 self.resolve_path(path)
116 .await
117 .unwrap_or_else(|_| path.to_string())
118 }
119}