@@ -24,6 +24,7 @@ use super::ids::SessionId;
2424use super :: provider:: ExecutionProvider ;
2525use super :: repl_framing:: { self , string_from_utf8_lossy_trim_cap, PYTHON_REPL_BOOTSTRAP } ;
2626use super :: run:: { CwdPolicy , RunResult , RunSpec , SessionCreateRequest , SessionHandle } ;
27+ use super :: run_history_dir;
2728use crate :: tool_runtime:: emit_tool_progress_message;
2829use crate :: tools:: builtin:: resolve_path;
2930
@@ -66,10 +67,12 @@ pub struct LocalExecutionConfig {
6667 pub uv_requirements : Vec < String > ,
6768 /// Root for UV-managed runtime cache (e.g. workspace `.system_generated/uv/envs`).
6869 pub uv_env_root : PathBuf ,
70+ /// Workspace root for log files.
71+ pub workspace_dir : PathBuf ,
6972}
7073
7174impl LocalExecutionConfig {
72- pub fn new ( sandbox_dir : PathBuf , restrict_to_workspace : bool ) -> Self {
75+ pub fn new ( sandbox_dir : PathBuf , workspace_dir : PathBuf , restrict_to_workspace : bool ) -> Self {
7376 let uv_env_root = sandbox_dir
7477 . join ( ".system_generated" )
7578 . join ( "uv" )
@@ -87,6 +90,7 @@ impl LocalExecutionConfig {
8790 uv_python : "3.11" . to_string ( ) ,
8891 uv_requirements : Vec :: new ( ) ,
8992 uv_env_root,
93+ workspace_dir,
9094 }
9195 }
9296}
@@ -691,10 +695,21 @@ impl ExecutionProvider for LocalExecutionProvider {
691695 let repl = g
692696 . as_mut ( )
693697 . ok_or_else ( || ExecutionError :: Provider ( "local repl unavailable" . into ( ) ) ) ?;
698+ let mut stdout_path = None ;
699+ let mut stderr_path = None ;
700+ let hd;
701+ if let Some ( rid) = & spec. run_id {
702+ hd = run_history_dir ( & self . config . workspace_dir , "local" , session_id, rid) ;
703+ let _ = tokio:: fs:: create_dir_all ( & hd) . await ;
704+ stdout_path = Some ( hd. join ( "stdout.txt" ) ) ;
705+ stderr_path = Some ( hd. join ( "stderr.txt" ) ) ;
706+ }
694707 let ( stdout, stderr, st) = repl_framing:: repl_round_trip (
695708 & mut repl. stdin ,
696709 & mut repl. stdout ,
697710 & code,
711+ stdout_path. as_deref ( ) ,
712+ stderr_path. as_deref ( ) ,
698713 max_each,
699714 )
700715 . await ?;
@@ -1027,6 +1042,7 @@ mod tests {
10271042 #[ tokio:: test]
10281043 async fn rejects_non_dir_sandbox ( ) {
10291044 let cfg = LocalExecutionConfig :: new (
1045+ PathBuf :: from ( "/nonexistent/path/that/should/not/exist" ) ,
10301046 PathBuf :: from ( "/nonexistent/path/that/should/not/exist" ) ,
10311047 true ,
10321048 ) ;
@@ -1062,7 +1078,7 @@ mod tests {
10621078 async fn local_echo_stdout ( ) {
10631079 let ( lang, code) = echo_hello_case ( ) ;
10641080 let dir = temp_sandbox ( ) ;
1065- let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , true ) ;
1081+ let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , dir . clone ( ) , true ) ;
10661082 let prov = LocalExecutionProvider :: new ( cfg) . unwrap ( ) ;
10671083 let h = prov
10681084 . create_session ( SessionCreateRequest {
@@ -1093,7 +1109,7 @@ mod tests {
10931109 let sub = dir. join ( "pkg" ) ;
10941110 fs:: create_dir_all ( & sub) . unwrap ( ) ;
10951111 fs:: write ( sub. join ( "marker.txt" ) , "x" ) . unwrap ( ) ;
1096- let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , true ) ;
1112+ let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , dir . clone ( ) , true ) ;
10971113 let prov = LocalExecutionProvider :: new ( cfg) . unwrap ( ) ;
10981114 let h = prov
10991115 . create_session ( SessionCreateRequest {
@@ -1119,7 +1135,7 @@ mod tests {
11191135 async fn timeout_returns_timeout_error ( ) {
11201136 let ( lang, code) = timeout_probe_case ( ) ;
11211137 let dir = temp_sandbox ( ) ;
1122- let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , true ) ;
1138+ let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , dir . clone ( ) , true ) ;
11231139 let prov = LocalExecutionProvider :: new ( cfg) . unwrap ( ) ;
11241140 let h = prov
11251141 . create_session ( SessionCreateRequest {
@@ -1141,7 +1157,7 @@ mod tests {
11411157 async fn cancel_mid_run ( ) {
11421158 let ( lang, code) = long_running_case ( ) ;
11431159 let dir = temp_sandbox ( ) ;
1144- let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , true ) ;
1160+ let cfg = LocalExecutionConfig :: new ( dir. clone ( ) , dir . clone ( ) , true ) ;
11451161 let prov = Arc :: new ( LocalExecutionProvider :: new ( cfg) . unwrap ( ) ) ;
11461162 let h = prov
11471163 . create_session ( SessionCreateRequest {
@@ -1228,7 +1244,7 @@ mod tests {
12281244 #[ test]
12291245 fn uv_env_key_changes_with_python_or_requirements ( ) {
12301246 let dir = temp_sandbox ( ) ;
1231- let mut cfg1 = LocalExecutionConfig :: new ( dir. clone ( ) , true ) ;
1247+ let mut cfg1 = LocalExecutionConfig :: new ( dir. clone ( ) , dir . clone ( ) , true ) ;
12321248 cfg1. python_runtime = LocalPythonRuntime :: UvManaged ;
12331249 cfg1. uv_python = "3.11" . into ( ) ;
12341250 cfg1. uv_requirements = vec ! [ "numpy" . into( ) ] ;
0 commit comments