Pair thread environment settings (#26687)

## Why

Thread cwd and environment selections are a single logical setting in
core: updating one without the other can silently desynchronize the
next-turn execution context. This change makes that relationship
explicit in the internal thread settings flow while preserving the
existing app-server public API shape.

## What changed

- Moved the cwd/environment pair through internal
`ThreadSettingsOverrides.environment_settings` instead of a top-level
internal `cwd` field.
- Kept `thread/settings/update` public params unchanged, with app-server
translating top-level `cwd` into the paired internal settings shape.
- Moved `Op::UserInput` environment overrides into thread settings so
user turns and settings updates use the same core path.
- Updated core, app-server, MCP, memories, sample, and test callsites to
construct the paired settings shape.

## Verification

- `git diff --check`
- Local test run starting after PR creation.
This commit is contained in:
pakrym-oai
2026-06-08 13:55:15 -07:00
committed by GitHub
parent f9a680b907
commit f3c1283411
90 changed files with 521 additions and 820 deletions
+30 -5
View File
@@ -112,6 +112,34 @@ pub struct TurnEnvironmentSelection {
pub cwd: AbsolutePathBuf,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TurnEnvironmentSelections {
pub legacy_fallback_cwd: AbsolutePathBuf,
pub environments: Vec<TurnEnvironmentSelection>,
}
impl TurnEnvironmentSelections {
pub fn new(
legacy_fallback_cwd: AbsolutePathBuf,
environments: Vec<TurnEnvironmentSelection>,
) -> Self {
let mut settings = Self {
legacy_fallback_cwd,
environments,
};
settings.sync_primary_environment_cwd();
settings
}
fn sync_primary_environment_cwd(&mut self) {
if let Some(turn_environment) = self.environments.first_mut()
&& turn_environment.cwd != self.legacy_fallback_cwd
{
turn_environment.cwd = self.legacy_fallback_cwd.clone();
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema, TS)]
#[serde(transparent)]
#[ts(type = "string")]
@@ -370,8 +398,8 @@ pub struct ConversationTextParams {
/// on their own.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ThreadSettingsOverrides {
/// Updated `cwd` for sandbox/tool calls.
pub cwd: Option<AbsolutePathBuf>,
/// Updated fallback `cwd` and environments supplied together as a complete pair.
pub environments: Option<TurnEnvironmentSelections>,
/// Updated runtime workspace roots used to materialize symbolic
/// `:workspace_roots` filesystem permissions.
@@ -472,8 +500,6 @@ pub enum Op {
UserInput {
/// User input items, see `InputItem`
items: Vec<UserInput>,
/// Optional turn-scoped environments.
environments: Option<Vec<TurnEnvironmentSelection>>,
/// Optional JSON Schema used to constrain the final assistant message for this turn.
final_output_json_schema: Option<Value>,
/// Optional turn-scoped Responses API `client_metadata`.
@@ -612,7 +638,6 @@ pub enum ThreadMemoryMode {
impl From<Vec<UserInput>> for Op {
fn from(value: Vec<UserInput>) -> Self {
Op::UserInput {
environments: None,
items: value,
final_output_json_schema: None,
responsesapi_client_metadata: None,