mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
app-server: use permission ids and runtime workspace roots (#22611)
## Why This PR builds on [#22610](https://github.com/openai/codex/pull/22610) and is the app-server side of the migration from mutable per-turn `SandboxPolicy` replacement toward selecting immutable permission profiles by id plus mutable runtime workspace roots. Once permission profiles can carry their own immutable `workspace_roots`, app-server no longer needs to mutate the selected `PermissionProfile` just to represent thread-specific filesystem context. The mutable part now lives on the thread as explicit `runtimeWorkspaceRoots`, while `:workspace_roots` remains symbolic until the sandbox is realized for a turn. ## What Changed - Replaced the v2 permission-selection wrapper surface with plain profile ids for `thread/start`, `thread/resume`, `thread/fork`, and `turn/start`. - Removed the API surface for profile modifications (`PermissionProfileSelectionParams`, `PermissionProfileModificationParams`, `ActivePermissionProfileModification`). - Added experimental `runtimeWorkspaceRoots` fields to the thread lifecycle and turn-start APIs. - Threaded runtime workspace roots through core session/thread snapshots, turn overrides, app-server request handling, and command execution permission resolution. - Kept session permission state symbolic so later runtime root updates and cwd-only implicit-root retargeting rebind `:workspace_roots` correctly. - Updated the embedded clients just enough to send and restore the new thread state. - Refreshed the generated schema/TypeScript artifacts and the app-server README to match the new contract. ## Verification Targeted coverage for this layer lives in: - `codex-rs/app-server-protocol/src/protocol/v2/tests.rs` - `codex-rs/app-server/tests/suite/v2/thread_start.rs` - `codex-rs/app-server/tests/suite/v2/thread_resume.rs` - `codex-rs/app-server/tests/suite/v2/turn_start.rs` - `codex-rs/core/src/session/tests.rs` The key regression checks exercise that: - `runtimeWorkspaceRoots` resolve against the effective cwd on thread start. - Profile-declared workspace roots are excluded from the runtime workspace roots returned by app-server. - A turn-level runtime workspace-root update persists onto the thread and is returned by `thread/resume`. - A named permission profile selected on one turn remains symbolic so a later runtime-root-only turn update changes the actual sandbox writes. - A cwd-only turn update retargets the implicit runtime cwd root while preserving additional runtime roots. - The protocol fixtures and generated client artifacts stay in sync with the string-based permission selection contract. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22611). * #22612 * __->__ #22611
This commit is contained in:
committed by
GitHub
Unverified
parent
e6a7368810
commit
8a5306ff88
@@ -1905,9 +1905,28 @@ async fn workspace_profile_applies_rules_to_runtime_and_profile_workspace_roots(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let cwd_abs = cwd.abs();
|
||||
let runtime_root_abs = runtime_root.abs();
|
||||
let profile_root_abs = profile_root.abs();
|
||||
assert_eq!(
|
||||
config.workspace_roots,
|
||||
vec![cwd_abs.clone(), runtime_root_abs.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.workspace_roots(),
|
||||
&[cwd_abs.clone(), runtime_root_abs.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
config.effective_workspace_roots(),
|
||||
vec![
|
||||
cwd_abs.clone(),
|
||||
runtime_root_abs.clone(),
|
||||
profile_root_abs.clone()
|
||||
]
|
||||
);
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
for root in [cwd.abs(), runtime_root.abs(), profile_root_abs.clone()] {
|
||||
for root in [cwd_abs, runtime_root_abs, profile_root_abs.clone()] {
|
||||
assert!(
|
||||
policy.can_write_path_with_cwd(root.as_path(), cwd.as_path()),
|
||||
"expected workspace root to be writable, policy: {policy:?}"
|
||||
|
||||
@@ -1231,6 +1231,13 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn effective_workspace_roots(&self) -> Vec<AbsolutePathBuf> {
|
||||
let mut workspace_roots = self.workspace_roots.clone();
|
||||
workspace_roots.extend(self.permissions.profile_workspace_roots().iter().cloned());
|
||||
dedupe_absolute_paths(&mut workspace_roots);
|
||||
workspace_roots
|
||||
}
|
||||
|
||||
pub fn to_models_manager_config(&self) -> ModelsManagerConfig {
|
||||
ModelsManagerConfig {
|
||||
model_context_window: self.model_context_window,
|
||||
@@ -2671,8 +2678,6 @@ impl Config {
|
||||
configured_workspace_roots.extend(sandbox_workspace_write.writable_roots.clone());
|
||||
}
|
||||
dedupe_absolute_paths(&mut configured_workspace_roots);
|
||||
workspace_roots.extend(configured_workspace_roots.iter().cloned());
|
||||
dedupe_absolute_paths(&mut workspace_roots);
|
||||
file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_materialized_project_roots_for_workspace_roots(&configured_workspace_roots);
|
||||
let mut permission_profile = if let Some(permission_profile) =
|
||||
|
||||
@@ -101,24 +101,6 @@ impl ResolvedPermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_permission_profile(&self, permission_profile: PermissionProfile) -> Self {
|
||||
match self {
|
||||
Self::Legacy(_) => Self::legacy(permission_profile),
|
||||
Self::BuiltIn(profile) => Self::BuiltIn(BuiltInPermissionProfile {
|
||||
id: profile.id,
|
||||
extends: profile.extends.clone(),
|
||||
permission_profile,
|
||||
profile_workspace_roots: profile.profile_workspace_roots.clone(),
|
||||
}),
|
||||
Self::Named(profile) => Self::Named(NamedPermissionProfile {
|
||||
id: profile.id.clone(),
|
||||
extends: profile.extends.clone(),
|
||||
permission_profile,
|
||||
profile_workspace_roots: profile.profile_workspace_roots.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
match self {
|
||||
Self::Legacy(_) => None,
|
||||
@@ -189,19 +171,6 @@ impl PermissionProfileState {
|
||||
self.resolved_permission_profile.get().permission_profile()
|
||||
}
|
||||
|
||||
pub(crate) fn clone_with_permission_profile(
|
||||
&self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> ConstraintResult<Self> {
|
||||
let candidate = self
|
||||
.resolved_permission_profile
|
||||
.get()
|
||||
.with_permission_profile(permission_profile);
|
||||
let mut state = self.clone();
|
||||
state.resolved_permission_profile.set(candidate)?;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.resolved_permission_profile
|
||||
.get()
|
||||
|
||||
Reference in New Issue
Block a user