(
mut client: crate::tls::GrpcClient,
sandbox: &Sandbox,
command: &[String],
workdir: Option<&str>,
timeout_seconds: u32,
environment: &HashMap<String, String>,
)
| 3146 | } |
| 3147 | |
| 3148 | async fn sandbox_exec_interactive_grpc( |
| 3149 | mut client: crate::tls::GrpcClient, |
| 3150 | sandbox: &Sandbox, |
| 3151 | command: &[String], |
| 3152 | workdir: Option<&str>, |
| 3153 | timeout_seconds: u32, |
| 3154 | environment: &HashMap<String, String>, |
| 3155 | ) -> Result<i32> { |
| 3156 | use openshell_core::proto::{ExecSandboxInput, ExecSandboxWindowResize, exec_sandbox_input}; |
| 3157 | use tokio_stream::wrappers::ReceiverStream; |
| 3158 | |
| 3159 | let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24)); |
| 3160 | |
| 3161 | let (input_tx, input_rx) = tokio::sync::mpsc::channel::<ExecSandboxInput>(4096); |
| 3162 | |
| 3163 | // Send the start message with exec metadata. |
| 3164 | input_tx |
| 3165 | .send(ExecSandboxInput { |
| 3166 | payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest { |
| 3167 | sandbox_id: sandbox.object_id().to_string(), |
| 3168 | command: command.to_vec(), |
| 3169 | workdir: workdir.unwrap_or_default().to_string(), |
| 3170 | environment: environment.clone(), |
| 3171 | timeout_seconds, |
| 3172 | stdin: Vec::new(), |
| 3173 | tty: true, |
| 3174 | cols: u32::from(cols), |
| 3175 | rows: u32::from(rows), |
| 3176 | })), |
| 3177 | }) |
| 3178 | .await |
| 3179 | .into_diagnostic()?; |
| 3180 | |
| 3181 | let mut stream = client |
| 3182 | .exec_sandbox_interactive(ReceiverStream::new(input_rx)) |
| 3183 | .await |
| 3184 | .into_diagnostic()? |
| 3185 | .into_inner(); |
| 3186 | |
| 3187 | // Enable raw mode so keystrokes are forwarded immediately. |
| 3188 | crossterm::terminal::enable_raw_mode().into_diagnostic()?; |
| 3189 | let raw_guard = RawModeGuard; |
| 3190 | |
| 3191 | // Stdin reader on a detached OS thread. Using std::thread (not |
| 3192 | // spawn_blocking) so the tokio runtime shutdown doesn't wait for a |
| 3193 | // thread blocked on stdin.read(). The thread exits when the channel |
| 3194 | // closes (blocking_send returns Err) or stdin hits EOF. |
| 3195 | #[cfg(unix)] |
| 3196 | { |
| 3197 | let stdin_tx = input_tx.clone(); |
| 3198 | std::thread::spawn(move || { |
| 3199 | let mut stdin = std::io::stdin().lock(); |
| 3200 | let mut buf = [0u8; 4096]; |
| 3201 | loop { |
| 3202 | match stdin.read(&mut buf) { |
| 3203 | Ok(0) | Err(_) => break, |
| 3204 | Ok(n) => { |
| 3205 | if stdin_tx |
no test coverage detected