Expose loaded thread environments in app-server responses (#42386)

## What changed

- Add the experimental `Thread.environments` field with each selected environment's ID, working directory, and runtime workspace roots.
- Populate the field from live thread state in start, resume, read, list, and unarchive responses. Use `null` for unloaded threads and an empty list when a loaded thread has no environment selection.
- Document that the field reports selection rather than connection status and does not affect executor selection or resume behavior.

## Testing

- Cover selection updates, unloaded and resumed threads, loaded pathless unarchives, and clients that subscribe after environment connection notifications.

GitOrigin-RevId: a6daa06aff7089d7c5a1e4c7d03109b41f12fa3c
This commit is contained in:
Anton Panasenko
2026-09-02 23:41:14 +00:00
committed by copyberry
parent cff76fa96f
commit 2b554fd3f9
42 changed files with 681 additions and 11 deletions
@@ -1,4 +1,5 @@
use super::CodexErrorInfo;
use super::ThreadEnvironment;
use super::ThreadItem;
use super::ThreadStatus;
use super::TurnStatus;
@@ -203,6 +204,12 @@ pub struct ThreadSectionAppearance {
pub struct Thread {
/// Identifier for this thread. Codex-generated thread IDs are UUIDv7.
pub id: String,
/// Current environments for a loaded thread, in priority order, primary first.
/// `null` means the thread is not loaded or the server does not expose its selection.
/// An empty list means no environments are selected. This does not report connection status.
#[experimental("thread.environments")]
#[serde(default)]
pub environments: Option<Vec<ThreadEnvironment>>,
/// Optional implementation-specific thread data.
#[experimental("thread.extra")]
pub extra: Option<ThreadExtra>,
@@ -286,6 +293,8 @@ pub struct Thread {
#[serde(rename_all = "camelCase")]
struct ThreadCompatibility {
id: String,
#[serde(default)]
environments: Option<Vec<ThreadEnvironment>>,
extra: Option<ThreadExtra>,
session_id: String,
forked_from_id: Option<String>,
@@ -328,6 +337,7 @@ impl<'de> Deserialize<'de> for Thread {
let thread = ThreadCompatibility::deserialize(deserializer)?;
Ok(Self {
id: thread.id,
environments: thread.environments,
extra: thread.extra,
session_id: thread.session_id,
forked_from_id: thread.forked_from_id,