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:
@@ -655,6 +655,61 @@ fn permissions_request_approval_response_accepts_strict_auto_review() {
|
||||
assert_eq!(response.strict_auto_review, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_profile_selection_accepts_legacy_object_shape() {
|
||||
let additional_root = absolute_path("additional-root");
|
||||
let params = json!({
|
||||
"permissions": {
|
||||
"type": "profile",
|
||||
"id": ":workspace",
|
||||
"modifications": [
|
||||
{
|
||||
"type": "additionalWritableRoot",
|
||||
"path": additional_root,
|
||||
}
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
let start: ThreadStartParams =
|
||||
serde_json::from_value(params.clone()).expect("thread/start params deserialize");
|
||||
assert_legacy_permission_profile_selection(start.permissions, &additional_root);
|
||||
|
||||
let resume: ThreadResumeParams = serde_json::from_value(json!({
|
||||
"threadId": "thread-1",
|
||||
"permissions": params["permissions"].clone(),
|
||||
}))
|
||||
.expect("thread/resume params deserialize");
|
||||
assert_legacy_permission_profile_selection(resume.permissions, &additional_root);
|
||||
|
||||
let fork: ThreadForkParams = serde_json::from_value(json!({
|
||||
"threadId": "thread-1",
|
||||
"permissions": params["permissions"].clone(),
|
||||
}))
|
||||
.expect("thread/fork params deserialize");
|
||||
assert_legacy_permission_profile_selection(fork.permissions, &additional_root);
|
||||
|
||||
let turn: TurnStartParams = serde_json::from_value(json!({
|
||||
"threadId": "thread-1",
|
||||
"input": [],
|
||||
"permissions": params["permissions"].clone(),
|
||||
}))
|
||||
.expect("turn/start params deserialize");
|
||||
assert_legacy_permission_profile_selection(turn.permissions, &additional_root);
|
||||
}
|
||||
|
||||
fn assert_legacy_permission_profile_selection(
|
||||
selection: Option<PermissionProfileSelectionParams>,
|
||||
additional_root: &AbsolutePathBuf,
|
||||
) {
|
||||
let selection = selection.expect("permissions should be present");
|
||||
assert_eq!(selection.id(), ":workspace");
|
||||
assert_eq!(
|
||||
selection.legacy_additional_writable_roots(),
|
||||
std::slice::from_ref(additional_root)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fs_get_metadata_response_round_trips_minimal_fields() {
|
||||
let response = FsGetMetadataResponse {
|
||||
@@ -3473,6 +3528,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
|
||||
responsesapi_client_metadata: None,
|
||||
environments: None,
|
||||
cwd: None,
|
||||
runtime_workspace_roots: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
|
||||
Reference in New Issue
Block a user