Addresses #18860 Problem: Remote app-server clients could stop draining websocket events when their bounded local event channel filled, leaving clients stuck on stale in-progress turns after a disconnect. Solution: Use an unbounded local event channel for the remote client so the websocket reader can keep forwarding disconnect and progress events instead of blocking or dropping them. Why this is reasonable: This does not make the remote websocket itself unbounded. The changed queue lives inside the remote client, between the task that reads the remote websocket and the API consumer in the same client process. Once an event has been received from the remote server, preserving it is preferable to blocking websocket reads or dropping disconnect/lifecycle events; network-level backpressure still happens at the websocket boundary if the remote side outpaces the client.
codex-app-server-client
Shared in-process app-server client used by conversational CLI surfaces:
codex-execcodex-tui
Purpose
This crate centralizes startup and lifecycle management for an in-process
codex-app-server runtime, so CLI clients do not need to duplicate:
- app-server bootstrap and initialize handshake
- in-memory request/event transport wiring
- lifecycle orchestration around caller-provided startup identity
- graceful shutdown behavior
Startup identity
Callers pass both the app-server SessionSource and the initialize
client_info.name explicitly when starting the facade.
That keeps thread metadata (for example in thread/list and thread/read)
aligned with the originating runtime without baking TUI/exec-specific policy
into the shared client layer.
Transport model
The in-process path uses typed channels:
- client -> server:
ClientRequest/ClientNotification - server -> client:
InProcessServerEventServerRequestServerNotificationLegacyNotification
JSON serialization is still used at external transport boundaries (stdio/websocket), but the in-process hot path is typed.
Typed requests still receive app-server responses through the JSON-RPC result envelope internally. That is intentional: the in-process path is meant to preserve app-server semantics while removing the process boundary, not to introduce a second response contract.
Bootstrap behavior
The client facade starts an already-initialized in-process runtime, but thread bootstrap still follows normal app-server flow:
- caller sends
thread/startorthread/resume - app-server returns the immediate typed response
- richer session metadata may arrive later as a
SessionConfiguredlegacy event
Surfaces such as TUI and exec may therefore need a short bootstrap phase where they reconcile startup response data with later events.
Backpressure and shutdown
- Queues are bounded and use
DEFAULT_IN_PROCESS_CHANNEL_CAPACITYby default. - Full queues return explicit overload behavior instead of unbounded growth.
shutdown()performs a bounded graceful shutdown and then aborts if timeout is exceeded.
If the client falls behind on event consumption, the worker emits
InProcessServerEvent::Lagged and may reject pending server requests so
approval flows do not hang indefinitely behind a saturated queue.