mod fs;
mod home;
mod instructions;
mod path;
mod rg;
mod session_dirs;
mod settings;
mod shell;
mod trace;
mod walk;
pub use fs::{DirEntry, FileRead, FileStat, FsError};
pub use home::{default_locode_home, locode_home};
pub use instructions::{
InstructionEntry, InstructionsConfig, ProjectInstructions, load_project_instructions,
};
pub use path::PathError;
pub use rg::rg_program;
pub use session_dirs::{decode_cwd_dirname, encode_cwd_dirname};
pub use settings::{Settings, SettingsLoad, SkillsExtraEntry, load_settings, load_settings_from};
pub use shell::{
ExecError, ExecOutput, ExecRequest, FrontBackCapture, FrontBackSpec, SPILL_RETAIN_MAX,
ShellSpec,
};
pub use trace::{
GitMeta, RolloutContents, SessionMeta, TRACE_SCHEMA_VERSION, TraceExtras, TraceWriter,
find_latest_rollout, find_rollout_by_id, read_rollout,
};
pub use walk::{WalkEntry, WalkOptions};
use std::path::{Path, PathBuf};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PathPolicy {
#[default]
Jailed,
Unrestricted,
}
#[derive(Debug, Clone)]
pub struct ExecLimits {
pub default_timeout: Duration,
pub max_timeout: Duration,
pub max_output_bytes: usize,
pub kill_grace: Duration,
}
impl Default for ExecLimits {
fn default() -> Self {
Self {
default_timeout: Duration::from_secs(10),
max_timeout: Duration::from_mins(10),
max_output_bytes: 30_000,
kill_grace: Duration::from_secs(2),
}
}
}
#[derive(Debug, Clone)]
pub struct HostConfig {
pub workspace_root: PathBuf,
pub path_policy: PathPolicy,
pub exec: ExecLimits,
pub shell_program: String,
pub login_shell: bool,
}
impl HostConfig {
pub fn new(workspace_root: impl Into<PathBuf>) -> Self {
Self {
workspace_root: workspace_root.into(),
path_policy: PathPolicy::default(),
exec: ExecLimits::default(),
shell_program: "bash".to_string(),
login_shell: true,
}
}
}
#[derive(Debug, Clone)]
pub struct Host {
pub(crate) workspace_root: PathBuf,
pub(crate) path_policy: PathPolicy,
pub(crate) limits: ExecLimits,
pub(crate) shell_program: String,
pub(crate) login_shell: bool,
pub(crate) gitignore: walk::GitignoreCache,
pub(crate) login_path: std::sync::Arc<tokio::sync::OnceCell<Option<String>>>,
}
impl Host {
pub fn new(config: HostConfig) -> Result<Self, PathError> {
let workspace_root = std::fs::canonicalize(&config.workspace_root).map_err(|e| {
PathError::InvalidRoot(format!("{}: {e}", config.workspace_root.display()))
})?;
Ok(Self {
workspace_root,
path_policy: config.path_policy,
limits: config.exec,
shell_program: config.shell_program,
login_shell: config.login_shell,
gitignore: walk::GitignoreCache::new(),
login_path: std::sync::Arc::new(tokio::sync::OnceCell::new()),
})
}
#[must_use]
pub fn workspace_root(&self) -> &Path {
&self.workspace_root
}
#[must_use]
pub fn path_policy(&self) -> PathPolicy {
self.path_policy
}
#[must_use]
pub fn limits(&self) -> &ExecLimits {
&self.limits
}
}
#[cfg(test)]
pub(crate) fn test_host(root: &Path, policy: PathPolicy, login: bool) -> Host {
let mut config = HostConfig::new(root);
config.path_policy = policy;
config.login_shell = login;
Host::new(config).expect("test host")
}