1use harness_core::{PermissionPolicy, ToolError};
2use serde::{Deserialize, Serialize};
3use std::sync::{Arc, Mutex};
4
5use crate::executor::BashExecutor;
6
7#[derive(Clone)]
9pub struct BashPermissionPolicy {
10 pub inner: PermissionPolicy,
11 pub unsafe_allow_bash_without_hook: bool,
12}
13
14impl BashPermissionPolicy {
15 pub fn new(inner: PermissionPolicy) -> Self {
16 Self {
17 inner,
18 unsafe_allow_bash_without_hook: false,
19 }
20 }
21
22 pub fn with_unsafe_bypass(mut self, bypass: bool) -> Self {
23 self.unsafe_allow_bash_without_hook = bypass;
24 self
25 }
26}
27
28impl std::fmt::Debug for BashPermissionPolicy {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("BashPermissionPolicy")
31 .field("unsafe_allow_bash_without_hook", &self.unsafe_allow_bash_without_hook)
32 .field("inner", &self.inner)
33 .finish()
34 }
35}
36
37#[derive(Debug, Clone)]
40pub struct LogicalCwd {
41 pub inner: Arc<Mutex<String>>,
42}
43
44impl LogicalCwd {
45 pub fn new(initial: impl Into<String>) -> Self {
46 Self {
47 inner: Arc::new(Mutex::new(initial.into())),
48 }
49 }
50
51 pub fn get(&self) -> String {
52 self.inner.lock().unwrap().clone()
53 }
54
55 pub fn set(&self, v: impl Into<String>) {
56 *self.inner.lock().unwrap() = v.into();
57 }
58}
59
60#[derive(Clone)]
61pub struct BashSessionConfig {
62 pub cwd: String,
63 pub permissions: BashPermissionPolicy,
64 pub env: Option<std::collections::HashMap<String, String>>,
65 pub executor: Arc<dyn BashExecutor>,
66 pub default_inactivity_timeout_ms: Option<u64>,
67 pub wallclock_backstop_ms: Option<u64>,
68 pub max_command_length: Option<usize>,
69 pub max_output_bytes_inline: Option<usize>,
70 pub max_output_bytes_file: Option<usize>,
71 pub max_background_jobs: Option<usize>,
72 pub logical_cwd: Option<LogicalCwd>,
73}
74
75impl BashSessionConfig {
76 pub fn new(
77 cwd: impl Into<String>,
78 permissions: BashPermissionPolicy,
79 executor: Arc<dyn BashExecutor>,
80 ) -> Self {
81 let cwd = cwd.into();
82 Self {
83 cwd: cwd.clone(),
84 permissions,
85 env: None,
86 executor,
87 default_inactivity_timeout_ms: None,
88 wallclock_backstop_ms: None,
89 max_command_length: None,
90 max_output_bytes_inline: None,
91 max_output_bytes_file: None,
92 max_background_jobs: None,
93 logical_cwd: None,
94 }
95 }
96
97 pub fn with_logical_cwd_carry(mut self) -> Self {
98 self.logical_cwd = Some(LogicalCwd::new(self.cwd.clone()));
99 self
100 }
101}
102
103impl std::fmt::Debug for BashSessionConfig {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.debug_struct("BashSessionConfig")
106 .field("cwd", &self.cwd)
107 .field("permissions", &self.permissions)
108 .field("has_env", &self.env.is_some())
109 .finish()
110 }
111}
112
113#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case")]
117pub enum TimeoutReason {
118 InactivityTimeout,
119 WallClockBackstop,
120}
121
122impl TimeoutReason {
123 pub fn as_str(&self) -> &'static str {
124 match self {
125 Self::InactivityTimeout => "inactivity timeout",
126 Self::WallClockBackstop => "wall-clock backstop",
127 }
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct BashOk {
133 pub output: String,
134 pub exit_code: i32,
135 pub stdout: String,
136 pub stderr: String,
137 pub duration_ms: u64,
138 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub log_path: Option<String>,
140 pub byte_cap: bool,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct BashNonzeroExit {
145 pub output: String,
146 pub exit_code: i32,
147 pub stdout: String,
148 pub stderr: String,
149 pub duration_ms: u64,
150 #[serde(default, skip_serializing_if = "Option::is_none")]
151 pub log_path: Option<String>,
152 pub byte_cap: bool,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct BashTimeout {
157 pub output: String,
158 pub stdout: String,
159 pub stderr: String,
160 pub reason: TimeoutReason,
161 pub duration_ms: u64,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub log_path: Option<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct BashBackgroundStarted {
168 pub output: String,
169 pub job_id: String,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct BashError {
174 pub error: ToolError,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(tag = "kind", rename_all = "snake_case")]
179pub enum BashResult {
180 #[serde(rename = "ok")]
181 Ok(BashOk),
182 #[serde(rename = "nonzero_exit")]
183 NonzeroExit(BashNonzeroExit),
184 #[serde(rename = "timeout")]
185 Timeout(BashTimeout),
186 #[serde(rename = "background_started")]
187 BackgroundStarted(BashBackgroundStarted),
188 #[serde(rename = "error")]
189 Error(BashError),
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193#[serde(tag = "kind", rename_all = "snake_case")]
194pub enum BashOutputResult {
195 #[serde(rename = "output")]
196 Output {
197 output: String,
198 running: bool,
199 exit_code: Option<i32>,
200 stdout: String,
201 stderr: String,
202 total_bytes_stdout: u64,
203 total_bytes_stderr: u64,
204 next_since_byte: u64,
205 },
206 #[serde(rename = "error")]
207 Error(BashError),
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(tag = "kind", rename_all = "snake_case")]
212pub enum BashKillResult {
213 #[serde(rename = "killed")]
214 Killed {
215 output: String,
216 job_id: String,
217 signal: String,
218 },
219 #[serde(rename = "error")]
220 Error(BashError),
221}