Skip to content

Ejecución de comandos y exec-server ​

源码版本rust-v0.145.0

El crate exec-server es el servicio independiente en el que codex ha extraído la ejecución de comandos: escucha una URL (local o remota), recibe ExecParams en estilo JSON-RPC, hace spawn de un subproceso dentro del sandbox y devuelve stdout/stderr por streaming al client. core::exec es la entrada del lado client: calcula el tipo de sandbox, construye el ExecRequest y se lo pasa al exec-server para que lo corra de verdad. El crate exec es un envoltorio fino del comando CLI codex exec.

Responsabilidades ​

  1. Proporcionar run_main para arrancar exec-server escuchando en una URL y exponer un telemetry span: codex-rs/exec-server/src/server.rs:17-35
  2. Definir el trait ExecBackend y el trait ExecProcess, desacoplando «arrancar proceso» de «interactuar con el proceso»: codex-rs/exec-server/src/process.rs:179-208
  3. LocalProcess es el backend por defecto; llama a codex_sandboxing::spawn_process para levantar el proceso de verdad: codex-rs/exec-server/src/local_process.rs:261-272
  4. prepare_exec_request traduce el sandbox context a un PreparedExecRequest antes del spawn: codex-rs/exec-server/src/process_sandbox.rs:69-130
  5. core::exec::execute_exec_request es la entrada de llamada del lado client; gestiona timeout, IO cap y clasificación de errores: codex-rs/core/src/exec.rs:437-460

Motivación de diseño ​

¿Por qué separar exec en un server independiente? Porque la ejecución de comandos tiene que ocurrir dentro del sandbox, y arrancar el sandbox tiene coste por plataforma (init de seatbelt, setup de bubblewrap, preparación de token de Windows). Dejar que exec-server sea un proceso persistente que reutilice esos recursos ya preparados permite al client enviar solo el RPC. Además, en entornos remotos (SSH / sandbox en la nube) se puede usar el mismo protocolo: EnvironmentProvider abstrae lo local y lo remoto, y al client no le importa en qué máquina corre el proceso.

¿Por qué ExecBackend es un trait y no un tipo concreto? Porque LocalProcess es la implementación por defecto, pero en tests se puede reemplazar por un backend mock; en entornos remotos RemoteProcess va por un noise relay. El trait ExecProcess saca «las operaciones tras arrancar el proceso» (read / write / signal / terminate) como algo separado de «cómo arrancarlo».

prepare_exec_request tiene que hacerse en el lado server y no en el client, porque el cwd y los workspace_roots del sandbox context son rutas locales del executor y necesitan conversión native_path. El permission profile además necesita materialize_project_roots_with_workspace_roots para mapear las raíces de proyecto que llegan remotas al sistema de archivos del executor.

Archivos clave ​

El trait ExecProcess saca «las operaciones tras arrancar el proceso» como algo aparte; read/write/signal/terminate son todos async, desacoplados de «cómo arrancar».

rust
pub trait ExecProcess: Send + Sync {
    fn process_id(&self) -> &ProcessId;
    fn subscribe_wake(&self) -> watch::Receiver<u64>;
    fn subscribe_events(&self) -> ExecProcessEventReceiver;
    fn read(&self, after_seq: Option<u64>, max_bytes: Option<usize>, wait_ms: Option<u64>) -> ExecProcessFuture<'_, ReadResponse>;
    fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>;
    fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()>;
    fn terminate(&self) -> ExecProcessFuture<'_, ()>;
}

LocalProcess es el backend por defecto y llama a codex_sandboxing::spawn_process para levantar el proceso:

rust
let spawned_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
    command: &prepared.command,
    cwd: prepared.cwd.as_path(),
    env: &prepared.env,
    arg0: &prepared.arg0,
    sandbox: prepared.sandbox,
    windows_sandbox: prepared.windows_sandbox_spawn_request(),
    tty: params.tty,
    stdin_open: params.tty || params.pipe_stdin,
    inherited_fds: &[],
}).await;

En el lado core, build_exec_request calcula el SandboxType y luego le pasa el SandboxCommand a SandboxManager::transform, obteniendo un SandboxExecRequest con el argv ya envuelto:

rust
let sandbox_type = select_process_exec_tool_sandbox_type(
    &file_system_sandbox_policy,
    network_sandbox_policy,
    windows_sandbox_level,
    enforce_managed_network,
);
let manager = SandboxManager::new();
let exec_req = manager
    .transform(SandboxTransformRequest {
        command, permissions: permission_profile, sandbox: sandbox_type,
        /* ... */
    })
    .map(|request| { /* ...ExecRequest::from_sandbox_exec_request */ })
    .map_err(CodexErr::from)?;

Flujo de datos ​

Bordes y fallos ​

Resumen ​

exec-server separa la ejecución de comandos de core; el trait ExecBackend hace que local y remoto compartan el mismo protocolo RPC. El lado core solo calcula el sandbox, construye el request y recoge el output; el backend por defecto LocalProcess va por codex_sandboxing::spawn_process para levantar el proceso de verdad. Para rastrear «por qué no arrancó el comando» o «por qué se truncó la salida», se lee junto con Sandbox multiplataforma y Clasificación de comandos (execpolicy).