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).