1mod fs;
11mod home;
12mod path;
13mod rg;
14mod root;
15mod session_dirs;
16mod settings;
17mod shell;
18mod trace;
19mod walk;
20
21pub use fs::{DirEntry, FileRead, FileStat, FsError};
22pub use home::{default_locode_home, locode_home, resolve_home_from};
23pub use path::PathError;
24pub use rg::rg_program;
25pub use root::find_root_from_markers;
26pub use session_dirs::{decode_cwd_dirname, encode_cwd_dirname};
27pub use settings::{
28 Settings, SettingsLoad, SkillsExtraEntry, load_settings, load_settings_from,
29 update_user_setting,
30};
31pub use shell::{
32 ExecError, ExecOutput, ExecRequest, FrontBackCapture, FrontBackSpec, SPILL_RETAIN_MAX,
33 ShellSpec,
34};
35pub use trace::{
36 GitMeta, RolloutContents, SessionMeta, TRACE_SCHEMA_VERSION, TraceExtras, TraceWriter,
37 find_latest_rollout, find_rollout_by_id, read_rollout,
38};
39pub use walk::{WalkEntry, WalkOptions};
40
41use std::path::{Path, PathBuf};
42use std::time::Duration;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum PathPolicy {
52 #[default]
54 Jailed,
55 Unrestricted,
58}
59
60#[derive(Debug, Clone)]
62pub struct ExecLimits {
63 pub default_timeout: Duration,
65 pub max_timeout: Duration,
67 pub max_output_bytes: usize,
69 pub kill_grace: Duration,
71}
72
73impl Default for ExecLimits {
74 fn default() -> Self {
75 Self {
76 default_timeout: Duration::from_secs(10),
77 max_timeout: Duration::from_mins(10),
78 max_output_bytes: 30_000,
79 kill_grace: Duration::from_secs(2),
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct HostConfig {
87 pub workspace_root: PathBuf,
89 pub path_policy: PathPolicy,
91 pub exec: ExecLimits,
93 pub shell_program: String,
96 pub login_shell: bool,
99}
100
101impl HostConfig {
102 pub fn new(workspace_root: impl Into<PathBuf>) -> Self {
104 Self {
105 workspace_root: workspace_root.into(),
106 path_policy: PathPolicy::default(),
107 exec: ExecLimits::default(),
108 shell_program: "bash".to_string(),
109 login_shell: true,
110 }
111 }
112}
113
114#[derive(Debug, Clone)]
117pub struct Host {
118 pub(crate) workspace_root: PathBuf,
119 pub(crate) path_policy: PathPolicy,
120 pub(crate) limits: ExecLimits,
121 pub(crate) shell_program: String,
122 pub(crate) login_shell: bool,
123 pub(crate) gitignore: walk::GitignoreCache,
125 pub(crate) login_path: std::sync::Arc<tokio::sync::OnceCell<Option<String>>>,
127}
128
129impl Host {
130 pub fn new(config: HostConfig) -> Result<Self, PathError> {
137 let workspace_root = std::fs::canonicalize(&config.workspace_root).map_err(|e| {
138 PathError::InvalidRoot(format!("{}: {e}", config.workspace_root.display()))
139 })?;
140 Ok(Self {
141 workspace_root,
142 path_policy: config.path_policy,
143 limits: config.exec,
144 shell_program: config.shell_program,
145 login_shell: config.login_shell,
146 gitignore: walk::GitignoreCache::new(),
147 login_path: std::sync::Arc::new(tokio::sync::OnceCell::new()),
148 })
149 }
150
151 #[must_use]
153 pub fn workspace_root(&self) -> &Path {
154 &self.workspace_root
155 }
156
157 #[must_use]
159 pub fn path_policy(&self) -> PathPolicy {
160 self.path_policy
161 }
162
163 #[must_use]
165 pub fn limits(&self) -> &ExecLimits {
166 &self.limits
167 }
168}
169
170#[cfg(test)]
171pub(crate) fn test_host(root: &Path, policy: PathPolicy, login: bool) -> Host {
172 let mut config = HostConfig::new(root);
173 config.path_policy = policy;
174 config.login_shell = login;
175 Host::new(config).expect("test host")
176}