atomr_agents_coding_cli_isolator/handle.rs
1//! `ProcessHandle` — the uniform handle every isolator returns.
2
3use std::time::Duration;
4
5use async_trait::async_trait;
6use tokio::sync::mpsc;
7
8use crate::error::IsolatorError;
9
10/// Per-spawn options the harness can tweak independently of the
11/// command itself.
12#[derive(Debug, Clone, Default)]
13pub struct IsolationOpts {
14 /// Capture stdout (NDJSON for headless). Always `true` in v1.
15 pub capture_stdout: bool,
16 /// Capture stderr.
17 pub capture_stderr: bool,
18 /// Wait this long after `kill` before forcing termination. None =
19 /// platform default.
20 pub grace: Option<Duration>,
21}
22
23/// Final exit status from a spawned process.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct ExitStatus {
26 pub code: Option<i32>,
27 pub success: bool,
28}
29
30impl ExitStatus {
31 pub fn ok() -> Self {
32 Self { code: Some(0), success: true }
33 }
34 pub fn from_code(code: i32) -> Self {
35 Self { code: Some(code), success: code == 0 }
36 }
37}
38
39/// Uniform handle to a running CLI process — host or container.
40///
41/// The handle owns one set of byte channels:
42///
43/// * `stdout_rx` / `stderr_rx`: lines (or chunks) emitted by the
44/// process. For PTY-allocated processes, all output flows through
45/// `stdout_rx` (stderr is multiplexed by the kernel).
46/// * `stdin_tx`: writes from the harness back into the process.
47/// `None` for processes spawned without an input channel.
48///
49/// All channels are tokio mpsc; the isolator background tasks
50/// translate platform-specific I/O to these channels.
51#[async_trait]
52pub trait ProcessHandle: Send {
53 /// Take ownership of the stdout byte stream. Returns `None` once
54 /// already taken.
55 fn take_stdout(&mut self) -> Option<mpsc::Receiver<Vec<u8>>>;
56
57 /// Take ownership of the stderr byte stream.
58 fn take_stderr(&mut self) -> Option<mpsc::Receiver<Vec<u8>>>;
59
60 /// Take ownership of the stdin sender.
61 fn take_stdin(&mut self) -> Option<mpsc::Sender<Vec<u8>>>;
62
63 /// `true` if the process was spawned with a PTY (interactive).
64 fn is_pty(&self) -> bool;
65
66 /// Resize the PTY window. Errors with `Unsupported` if not a PTY
67 /// process.
68 async fn resize_pty(&mut self, cols: u16, rows: u16) -> Result<(), IsolatorError>;
69
70 /// Send SIGTERM (and SIGKILL after grace).
71 async fn kill(&mut self) -> Result<(), IsolatorError>;
72
73 /// Wait for the process to exit. Idempotent — repeated calls
74 /// after exit return the cached status.
75 async fn wait(&mut self) -> Result<ExitStatus, IsolatorError>;
76}