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::{Settings, SettingsLoad, SkillsExtraEntry, load_settings, load_settings_from};
28pub use shell::{
29 ExecError, ExecOutput, ExecRequest, FrontBackCapture, FrontBackSpec, SPILL_RETAIN_MAX,
30 ShellSpec,
31};
32pub use trace::{
33 GitMeta, RolloutContents, SessionMeta, TRACE_SCHEMA_VERSION, TraceExtras, TraceWriter,
34 find_latest_rollout, find_rollout_by_id, read_rollout,
35};
36pub use walk::{WalkEntry, WalkOptions};
37
38use std::path::{Path, PathBuf};
39use std::time::Duration;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub enum PathPolicy {
49 #[default]
51 Jailed,
52 Unrestricted,
55}
56
57#[derive(Debug, Clone)]
59pub struct ExecLimits {
60 pub default_timeout: Duration,
62 pub max_timeout: Duration,
64 pub max_output_bytes: usize,
66 pub kill_grace: Duration,
68}
69
70impl Default for ExecLimits {
71 fn default() -> Self {
72 Self {
73 default_timeout: Duration::from_secs(10),
74 max_timeout: Duration::from_mins(10),
75 max_output_bytes: 30_000,
76 kill_grace: Duration::from_secs(2),
77 }
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct HostConfig {
84 pub workspace_root: PathBuf,
86 pub path_policy: PathPolicy,
88 pub exec: ExecLimits,
90 pub shell_program: String,
93 pub login_shell: bool,
96}
97
98impl HostConfig {
99 pub fn new(workspace_root: impl Into<PathBuf>) -> Self {
101 Self {
102 workspace_root: workspace_root.into(),
103 path_policy: PathPolicy::default(),
104 exec: ExecLimits::default(),
105 shell_program: "bash".to_string(),
106 login_shell: true,
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
114pub struct Host {
115 pub(crate) workspace_root: PathBuf,
116 pub(crate) path_policy: PathPolicy,
117 pub(crate) limits: ExecLimits,
118 pub(crate) shell_program: String,
119 pub(crate) login_shell: bool,
120 pub(crate) gitignore: walk::GitignoreCache,
122 pub(crate) login_path: std::sync::Arc<tokio::sync::OnceCell<Option<String>>>,
124}
125
126impl Host {
127 pub fn new(config: HostConfig) -> Result<Self, PathError> {
134 let workspace_root = std::fs::canonicalize(&config.workspace_root).map_err(|e| {
135 PathError::InvalidRoot(format!("{}: {e}", config.workspace_root.display()))
136 })?;
137 Ok(Self {
138 workspace_root,
139 path_policy: config.path_policy,
140 limits: config.exec,
141 shell_program: config.shell_program,
142 login_shell: config.login_shell,
143 gitignore: walk::GitignoreCache::new(),
144 login_path: std::sync::Arc::new(tokio::sync::OnceCell::new()),
145 })
146 }
147
148 #[must_use]
150 pub fn workspace_root(&self) -> &Path {
151 &self.workspace_root
152 }
153
154 #[must_use]
156 pub fn path_policy(&self) -> PathPolicy {
157 self.path_policy
158 }
159
160 #[must_use]
162 pub fn limits(&self) -> &ExecLimits {
163 &self.limits
164 }
165}
166
167#[cfg(test)]
168pub(crate) fn test_host(root: &Path, policy: PathPolicy, login: bool) -> Host {
169 let mut config = HostConfig::new(root);
170 config.path_policy = policy;
171 config.login_shell = login;
172 Host::new(config).expect("test host")
173}