Skip to content

Befehlsausführung und exec-server

源码版本rust-v0.145.0

Der exec-server-Crate ist der von codex ausgelagerte eigenständige Dienst für Befehlsausführung: Er lauscht an einer URL (lokal oder remote), nimmt JSON-RPC-artige ExecParams entgegen, spawned in der Sandbox einen Kindprozess und streamt stdout/stderr an den Client zurück. core::exec ist der clientseitige Einstieg, der den Sandbox-Typ berechnet, eine ExecRequest aufbaut und dem exec-server die eigentliche Ausführung übergibt. Der exec-Crate ist der dünne Wrapper für das CLI-Kommando codex exec.

Verantwortlichkeiten

  1. run_main bietet den Start des exec-server an, der an einer URL lauscht und einen Telemetrie-Span exponiert: codex-rs/exec-server/src/server.rs:17-35
  2. ExecBackend-Trait und ExecProcess-Trait entkoppeln „Prozess starten" und „mit dem Prozess interagieren": codex-rs/exec-server/src/process.rs:179-208
  3. LocalProcess ist der Standard-Backend und ruft codex_sandboxing::spawn_process auf, um den Prozess zu starten: codex-rs/exec-server/src/local_process.rs:261-272
  4. prepare_exec_request übersetzt den Sandbox-Kontext vor dem Spawn in einen PreparedExecRequest: codex-rs/exec-server/src/process_sandbox.rs:69-130
  5. core::exec::execute_exec_request ist der clientseitige Aufrufeinstieg für Timeout, IO-Cap und Fehlerklassifikation: codex-rs/core/src/exec.rs:437-460

Entwurfsbeweggründe

Warum exec als eigener Server auskoppeln? Weil die Befehlsausführung innerhalb der Sandbox stattfinden muss und das Starten der Sandbox plattformspezifische Kosten verursacht (seatbelt init, bubblewrap setup, Windows token preparation). Lässt man exec-server als langlebigen Prozess diese vorbereiteten Ressourcen wiederverwenden, schickt der Client nur ein RPC. Zugleich können Remote-Umgebungen (SSH / Cloud-Sandbox) dasselbe Protokoll nutzen — EnvironmentProvider abstrahiert lokal/remote, und der Client muss nicht wissen, auf welcher Maschine der Prozess tatsächlich läuft.

Warum ist ExecBackend ein Trait und kein konkreter Typ? Weil LocalProcess die Standard-Implementierung ist, aber im Test ein Mock-Backend eingesetzt werden kann; in Remote-Umgebungen läuft RemoteProcess über ein Noise-Relay. Das ExecProcess-Trait koppelt „Operationen nach dem Start des Prozesses" (read / write / signal / terminate) von „wie starten" ab.

prepare_exec_request muss server- und nicht clientseitig laufen, weil cwd und workspace_roots im Sandbox-Kontext lokale Pfade des Executors sind und über native_path übersetzt werden müssen. Das permission profile muss außerdem mit materialize_project_roots_with_workspace_roots die remote hereinkommenden Projektwurzeln auf das Dateisystem des Executors mappen.

Wichtige Dateien

Das ExecProcess-Trait koppelt „Operationen nach dem Start des Prozesses" ab; read/write/signal/terminate sind alles asynchrone Operationen, unabhängig von „wie starten".

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 ist der Standard-Backend und ruft codex_sandboxing::spawn_process auf, um den Prozess zu starten:

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;

Auf core-Seite berechnet build_exec_request den SandboxType und übergibt die SandboxCommand an SandboxManager::transform, um den SandboxExecRequest mit Wrapper-argv zu erhalten:

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)?;

Datenfluss

Grenzen und Fehler

Zusammenfassung

exec-server koppelt die Befehlsausführung aus core aus; der ExecBackend-Trait teilt sich eine RPC-Protokoll-Schicht zwischen lokaler und Remote-Umgebung. Auf core-Seite wird nur die Sandbox berechnet, der Request gebaut und der Output eingesammelt; der Standard-Backend LocalProcess ruft codex_sandboxing::spawn_process auf, um den Prozess zu starten. Um „warum ein Befehl nicht anlief" oder „warum der Output abgeschnitten ist" zu verfolgen, siehe gemeinsam mit Plattformübergreifende Sandbox und Befehlsklassifikation execpolicy.