1mod fs;
11mod home;
12mod instructions;
13mod path;
14mod rg;
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};
23pub use instructions::{
24 InstructionEntry, InstructionsConfig, ProjectInstructions, load_project_instructions,
25};
26pub use path::PathError;
27pub use rg::rg_program;
28pub use session_dirs::{decode_cwd_dirname, encode_cwd_dirname};
29pub use settings::{Settings, SettingsLoad, SkillsExtraEntry, load_settings, load_settings_from};
30pub use shell::{
31 ExecError, ExecOutput, ExecRequest, FrontBackCapture, FrontBackSpec, SPILL_RETAIN_MAX,
32 ShellSpec,
33};
34pub use trace::{
35 GitMeta, RolloutContents, SessionMeta, TRACE_SCHEMA_VERSION, TraceExtras, TraceWriter,
36 find_latest_rollout, find_rollout_by_id, read_rollout,
37};
38pub use walk::{WalkEntry, WalkOptions};
39
40use std::path::{Path, PathBuf};
41use std::time::Duration;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
50pub enum PathPolicy {
51 #[default]
53 Jailed,
54 Unrestricted,
57}
58
59#[derive(Debug, Clone)]
61pub struct ExecLimits {
62 pub default_timeout: Duration,
64 pub max_timeout: Duration,
66 pub max_output_bytes: usize,
68 pub kill_grace: Duration,
70}
71
72impl Default for ExecLimits {
73 fn default() -> Self {
74 Self {
75 default_timeout: Duration::from_secs(10),
76 max_timeout: Duration::from_mins(10),
77 max_output_bytes: 30_000,
78 kill_grace: Duration::from_secs(2),
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
85pub struct HostConfig {
86 pub workspace_root: PathBuf,
88 pub path_policy: PathPolicy,
90 pub exec: ExecLimits,
92 pub shell_program: String,
95 pub login_shell: bool,
98}
99
100impl HostConfig {
101 pub fn new(workspace_root: impl Into<PathBuf>) -> Self {
103 Self {
104 workspace_root: workspace_root.into(),
105 path_policy: PathPolicy::default(),
106 exec: ExecLimits::default(),
107 shell_program: "bash".to_string(),
108 login_shell: true,
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
116pub struct Host {
117 pub(crate) workspace_root: PathBuf,
118 pub(crate) path_policy: PathPolicy,
119 pub(crate) limits: ExecLimits,
120 pub(crate) shell_program: String,
121 pub(crate) login_shell: bool,
122 pub(crate) gitignore: walk::GitignoreCache,
124 pub(crate) login_path: std::sync::Arc<tokio::sync::OnceCell<Option<String>>>,
126}
127
128impl Host {
129 pub fn new(config: HostConfig) -> Result<Self, PathError> {
136 let workspace_root = std::fs::canonicalize(&config.workspace_root).map_err(|e| {
137 PathError::InvalidRoot(format!("{}: {e}", config.workspace_root.display()))
138 })?;
139 Ok(Self {
140 workspace_root,
141 path_policy: config.path_policy,
142 limits: config.exec,
143 shell_program: config.shell_program,
144 login_shell: config.login_shell,
145 gitignore: walk::GitignoreCache::new(),
146 login_path: std::sync::Arc::new(tokio::sync::OnceCell::new()),
147 })
148 }
149
150 #[must_use]
152 pub fn workspace_root(&self) -> &Path {
153 &self.workspace_root
154 }
155
156 #[must_use]
158 pub fn path_policy(&self) -> PathPolicy {
159 self.path_policy
160 }
161
162 #[must_use]
164 pub fn limits(&self) -> &ExecLimits {
165 &self.limits
166 }
167}
168
169#[cfg(test)]
170pub(crate) fn test_host(root: &Path, policy: PathPolicy, login: bool) -> Host {
171 let mut config = HostConfig::new(root);
172 config.path_policy = policy;
173 config.login_shell = login;
174 Host::new(config).expect("test host")
175}