mirror of
https://github.com/openai/codex.git
synced 2026-10-23 06:36:44 +00:00
This PR partially rebase `unified_exec` on the `exec-server` and adapt the `exec-server` accordingly. ## What changed in `exec-server` 1. Replaced the old "broadcast-driven; process-global" event model with process-scoped session events. The goal is to be able to have dedicated handler for each process. 2. Add to protocol contract to support explicit lifecycle status and stream ordering: - `WriteResponse` now returns `WriteStatus` (Accepted, UnknownProcess, StdinClosed, Starting) instead of a bool. - Added seq fields to output/exited notifications. - Added terminal process/closed notification. 3. Demultiplexed remote notifications into per-process channels. Same as for the event sys 4. Local and remote backends now both implement ExecBackend. 5. Local backend wraps internal process ID/operations into per-process ExecProcess objects. 6. Remote backend registers a session channel before launch and unregisters on failed launch. ## What changed in `unified_exec` 1. Added unified process-state model and backend-neutral process wrapper. This will probably disappear in the future, but it makes it easier to keep the work flowing on both side. - `UnifiedExecProcess` now handles both local PTY sessions and remote exec-server processes through a shared `ProcessHandle`. - Added `ProcessState` to track has_exited, exit_code, and terminal failure message consistently across backends. 2. Routed write and lifecycle handling through process-level methods. ## Some rationals 1. The change centralizes execution transport in exec-server while preserving policy and orchestration ownership in core, avoiding duplicated launch approval logic. This comes from internal discussion. 2. Session-scoped events remove coupling/cross-talk between processes and make stream ordering and terminal state explicit (seq, closed, failed). 3. The failure-path surfacing (remote launch failures, write failures, transport disconnects) makes command tool output and cleanup behavior deterministic ## Follow-ups: * Unify the concept of thread ID behind an obfuscated struct * FD handling * Full zsh-fork compatibility * Full network sandboxing compatibility * Handle ws disconnection
78 lines
1.6 KiB
Rust
78 lines
1.6 KiB
Rust
use std::fmt;
|
|
use std::ops::Deref;
|
|
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use tokio::sync::watch;
|
|
|
|
use crate::ExecServerError;
|
|
use crate::protocol::ExecParams;
|
|
use crate::protocol::ReadResponse;
|
|
use crate::protocol::WriteResponse;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
pub struct ProcessId(String);
|
|
|
|
pub struct StartedExecProcess {
|
|
pub process: Arc<dyn ExecProcess>,
|
|
}
|
|
|
|
impl ProcessId {
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
|
|
pub fn into_inner(self) -> String {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Deref for ProcessId {
|
|
type Target = str;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.as_str()
|
|
}
|
|
}
|
|
|
|
impl AsRef<str> for ProcessId {
|
|
fn as_ref(&self) -> &str {
|
|
self.as_str()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ProcessId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl From<String> for ProcessId {
|
|
fn from(value: String) -> Self {
|
|
Self(value)
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ExecProcess: Send + Sync {
|
|
fn process_id(&self) -> &ProcessId;
|
|
|
|
fn subscribe_wake(&self) -> watch::Receiver<u64>;
|
|
|
|
async fn read(
|
|
&self,
|
|
after_seq: Option<u64>,
|
|
max_bytes: Option<usize>,
|
|
wait_ms: Option<u64>,
|
|
) -> Result<ReadResponse, ExecServerError>;
|
|
|
|
async fn write(&self, chunk: Vec<u8>) -> Result<WriteResponse, ExecServerError>;
|
|
|
|
async fn terminate(&self) -> Result<(), ExecServerError>;
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ExecBackend: Send + Sync {
|
|
async fn start(&self, params: ExecParams) -> Result<StartedExecProcess, ExecServerError>;
|
|
}
|