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 && let Ok(parsed) = v.trim().parse::<usize>()
10 {
11 return parsed;
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, extra_roots) = {
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, session.extra_roots.clone())
61 };
62
63 let jail_root_path = std::path::Path::new(&jail_root);
64 let jailed = match crate::core::pathjail::jail_path_with_roots(
65 &resolved,
66 jail_root_path,
67 &extra_roots,
68 ) {
69 Ok(p) => p,
70 Err(e) => {
71 if p.is_absolute() {
72 if let Some(new_root) = maybe_derive_project_root_from_absolute(&resolved) {
73 let cfg_allow = std::env::var("LEAN_CTX_ALLOW_REROOT").map_or_else(
74 |_| crate::core::config::Config::load().allow_auto_reroot,
75 |v| v == "1" || v == "true",
76 );
77 let candidate_under_jail = resolved.starts_with(jail_root_path);
78 let allow_reroot = if candidate_under_jail {
86 false
87 } else if is_suspicious_root(jail_root_path) {
88 true
89 } else if !cfg_allow {
90 false
91 } else if let Some(ref trusted_root) = self.startup_project_root {
92 std::path::Path::new(trusted_root) == new_root.as_path()
93 } else {
94 !has_project_marker(jail_root_path)
95 };
96
97 if allow_reroot {
98 let mut session = self.session.write().await;
99 let new_root_str = new_root.to_string_lossy().to_string();
100 session.project_root = Some(new_root_str.clone());
101 session.shell_cwd = self
102 .startup_shell_cwd
103 .as_ref()
104 .filter(|cwd| std::path::Path::new(cwd).starts_with(&new_root))
105 .cloned()
106 .or_else(|| Some(new_root_str.clone()));
107 let _ = session.save();
108
109 crate::core::pathjail::jail_path_with_roots(
110 &resolved,
111 &new_root,
112 &extra_roots,
113 )?
114 } else {
115 return Err(e);
116 }
117 } else {
118 return Err(e);
119 }
120 } else {
121 return Err(e);
122 }
123 }
124 };
125
126 crate::core::io_boundary::check_secret_path_for_tool("resolve_path", &jailed)?;
127
128 Ok(crate::core::pathutil::normalize_tool_path(
129 &jailed.to_string_lossy().replace('\\', "/"),
130 ))
131 }
132
133 pub async fn resolve_path_or_passthrough(&self, path: &str) -> String {
135 self.resolve_path(path)
136 .await
137 .unwrap_or_else(|_| path.to_string())
138 }
139}