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
@@ -16,6 +16,20 @@ pub(crate) struct TurnRequestProcessor {
|
||||
skills_watcher: Arc<SkillsWatcher>,
|
||||
}
|
||||
|
||||
fn resolve_runtime_workspace_roots(
|
||||
workspace_roots: Vec<PathBuf>,
|
||||
base_cwd: &AbsolutePathBuf,
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
let mut resolved_roots = Vec::new();
|
||||
for path in workspace_roots {
|
||||
let root = AbsolutePathBuf::resolve_path_against_base(path, base_cwd.as_path());
|
||||
if !resolved_roots.iter().any(|existing| existing == &root) {
|
||||
resolved_roots.push(root);
|
||||
}
|
||||
}
|
||||
resolved_roots
|
||||
}
|
||||
|
||||
impl TurnRequestProcessor {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
@@ -355,8 +369,16 @@ impl TurnRequestProcessor {
|
||||
.map(V2UserInput::into_core)
|
||||
.collect();
|
||||
let turn_has_input = !mapped_items.is_empty();
|
||||
let runtime_workspace_roots_request = params.runtime_workspace_roots.clone();
|
||||
let snapshot = if params.permissions.is_some() || runtime_workspace_roots_request.is_some()
|
||||
{
|
||||
Some(thread.config_snapshot().await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let has_any_overrides = params.cwd.is_some()
|
||||
|| runtime_workspace_roots_request.is_some()
|
||||
|| params.approval_policy.is_some()
|
||||
|| params.approvals_reviewer.is_some()
|
||||
|| params.sandbox_policy.is_some()
|
||||
@@ -375,16 +397,45 @@ impl TurnRequestProcessor {
|
||||
}
|
||||
|
||||
let cwd = params.cwd;
|
||||
let runtime_workspace_roots = if let Some(workspace_roots) =
|
||||
runtime_workspace_roots_request.clone()
|
||||
{
|
||||
let Some(snapshot) = snapshot.as_ref() else {
|
||||
return Err(internal_error(
|
||||
"turn/start runtime workspace roots missing thread snapshot",
|
||||
));
|
||||
};
|
||||
let base_cwd = cwd
|
||||
.as_ref()
|
||||
.map(|cwd| AbsolutePathBuf::resolve_path_against_base(cwd, snapshot.cwd.as_path()))
|
||||
.unwrap_or_else(|| snapshot.cwd.clone());
|
||||
Some(resolve_runtime_workspace_roots(workspace_roots, &base_cwd))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let approval_policy = params.approval_policy.map(AskForApproval::to_core);
|
||||
let approvals_reviewer = params
|
||||
.approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core);
|
||||
let sandbox_policy = params.sandbox_policy.map(|p| p.to_core());
|
||||
let (permission_profile, active_permission_profile) =
|
||||
let (permission_profile, active_permission_profile, profile_workspace_roots) =
|
||||
if let Some(permissions) = params.permissions {
|
||||
let snapshot = thread.config_snapshot().await;
|
||||
let Some(snapshot) = snapshot.as_ref() else {
|
||||
return Err(internal_error(
|
||||
"turn/start permission selection missing thread snapshot",
|
||||
));
|
||||
};
|
||||
let mut overrides = ConfigOverrides {
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: Some(runtime_workspace_roots_request.clone().unwrap_or_else(
|
||||
|| {
|
||||
snapshot
|
||||
.workspace_roots
|
||||
.iter()
|
||||
.map(AbsolutePathBuf::to_path_buf)
|
||||
.collect()
|
||||
},
|
||||
)),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
..Default::default()
|
||||
@@ -413,11 +464,12 @@ impl TurnRequestProcessor {
|
||||
)));
|
||||
}
|
||||
(
|
||||
Some(config.permissions.effective_permission_profile()),
|
||||
Some(config.permissions.permission_profile().clone()),
|
||||
config.permissions.active_permission_profile(),
|
||||
Some(config.permissions.profile_workspace_roots().to_vec()),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
(None, None, None)
|
||||
};
|
||||
let model = params.model;
|
||||
let effort = params.effort.map(Some);
|
||||
@@ -432,11 +484,13 @@ impl TurnRequestProcessor {
|
||||
thread
|
||||
.validate_turn_context_overrides(CodexThreadTurnContextOverrides {
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: runtime_workspace_roots.clone(),
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy: sandbox_policy.clone(),
|
||||
permission_profile: permission_profile.clone(),
|
||||
active_permission_profile: active_permission_profile.clone(),
|
||||
profile_workspace_roots: profile_workspace_roots.clone(),
|
||||
windows_sandbox_level: None,
|
||||
model: model.clone(),
|
||||
effort,
|
||||
@@ -457,6 +511,8 @@ impl TurnRequestProcessor {
|
||||
final_output_json_schema: params.output_schema,
|
||||
responsesapi_client_metadata: params.responsesapi_client_metadata,
|
||||
cwd,
|
||||
workspace_roots: runtime_workspace_roots,
|
||||
profile_workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
|
||||
Reference in New Issue
Block a user