feat: move exec-server ownership (#16344)

This introduces session-scoped ownership for exec-server so ws
disconnects no longer immediately kill running remote exec processes,
and it prepares the protocol for reconnect-based resume.
- add session_id / resume_session_id to the exec-server initialize
handshake
  - move process ownership under a shared session registry
- detach sessions on websocket disconnect and expire them after a TTL
instead of killing processes immediately (we will resume based on this)
- allow a new connection to resume an existing session and take over
notifications/ownership
- I use UUID to make them not predictable as we don't have auth for now
- make detached-session expiry authoritative at resume time so teardown
wins at the TTL boundary
- reject long-poll process/read calls that get resumed out from under an
older attachment

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-04-10 14:11:47 +01:00
committed by GitHub
parent 7bbe3b6011
commit 085ffb4456
21 changed files with 1203 additions and 172 deletions

View File

@@ -105,18 +105,10 @@ pub struct Environment {
impl Default for Environment {
fn default() -> Self {
let local_process = LocalProcess::default();
if let Err(err) = local_process.initialize() {
panic!("default local process initialization should succeed: {err:?}");
}
if let Err(err) = local_process.initialized() {
panic!("default local process should accept initialized notification: {err}");
}
Self {
exec_server_url: None,
remote_exec_server_client: None,
exec_backend: Arc::new(local_process),
exec_backend: Arc::new(LocalProcess::default()),
}
}
}
@@ -146,6 +138,7 @@ impl Environment {
client_name: "codex-environment".to_string(),
connect_timeout: std::time::Duration::from_secs(5),
initialize_timeout: std::time::Duration::from_secs(5),
resume_session_id: None,
})
.await?,
)
@@ -153,24 +146,12 @@ impl Environment {
None
};
let exec_backend: Arc<dyn ExecBackend> = match remote_exec_server_client.clone() {
Some(client) => Arc::new(RemoteProcess::new(client)),
None if exec_server_url.is_some() => {
return Err(ExecServerError::Protocol(
"remote mode should have an exec-server client".to_string(),
));
}
None => {
let local_process = LocalProcess::default();
local_process
.initialize()
.map_err(|err| ExecServerError::Protocol(err.message))?;
local_process
.initialized()
.map_err(ExecServerError::Protocol)?;
Arc::new(local_process)
}
};
let exec_backend: Arc<dyn ExecBackend> =
if let Some(client) = remote_exec_server_client.clone() {
Arc::new(RemoteProcess::new(client))
} else {
Arc::new(LocalProcess::default())
};
Ok(Self {
exec_server_url,