From f68960302a1403ba01ea55ac882c87d8c1e2df61 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Tue, 12 May 2026 17:15:12 -0700 Subject: [PATCH 1/2] permissions: move workspace roots onto thread state --- codex-rs/Cargo.lock | 3 + .../analytics/src/analytics_client_tests.rs | 4 +- codex-rs/analytics/src/client_tests.rs | 12 +- .../schema/json/ClientRequest.json | 74 +--- .../codex_app_server_protocol.schemas.json | 113 +----- .../codex_app_server_protocol.v2.schemas.json | 113 +----- .../schema/json/v2/CommandExecParams.json | 7 - .../schema/json/v2/ThreadForkParams.json | 62 +-- .../schema/json/v2/ThreadForkResponse.json | 367 +----------------- .../schema/json/v2/ThreadResumeParams.json | 62 +-- .../schema/json/v2/ThreadResumeResponse.json | 367 +----------------- .../schema/json/v2/ThreadStartParams.json | 59 --- .../schema/json/v2/ThreadStartResponse.json | 367 +----------------- .../schema/json/v2/TurnStartParams.json | 68 +--- .../typescript/v2/ActivePermissionProfile.ts | 8 +- .../v2/ActivePermissionProfileModification.ts | 6 - .../v2/PermissionProfileModificationParams.ts | 6 - .../v2/PermissionProfileSelectionParams.ts | 6 - .../schema/typescript/v2/SandboxPolicy.ts | 3 +- .../schema/typescript/v2/ThreadForkParams.ts | 7 +- .../typescript/v2/ThreadForkResponse.ts | 3 +- .../typescript/v2/ThreadResumeParams.ts | 7 +- .../typescript/v2/ThreadResumeResponse.ts | 3 +- .../typescript/v2/ThreadStartResponse.ts | 3 +- .../schema/typescript/v2/TurnStartParams.ts | 4 +- .../schema/typescript/v2/index.ts | 3 - .../src/protocol/common.rs | 4 +- .../src/protocol/v2/permissions.rs | 130 +++---- .../src/protocol/v2/tests.rs | 50 ++- .../src/protocol/v2/thread.rs | 83 ++-- .../src/protocol/v2/turn.rs | 18 +- codex-rs/app-server/src/lib.rs | 2 +- .../command_exec_processor.rs | 31 +- codex-rs/cli/src/debug_sandbox.rs | 20 +- codex-rs/config/src/config_requirements.rs | 4 - codex-rs/config/src/config_toml.rs | 4 +- codex-rs/core/src/codex_thread.rs | 36 +- .../core/src/config/config_loader_tests.rs | 6 +- codex-rs/core/src/config/config_tests.rs | 105 +++-- codex-rs/core/src/config/mod.rs | 196 ++++++---- codex-rs/core/src/config/permissions.rs | 4 +- .../src/context/permissions_instructions.rs | 33 +- .../context/permissions_instructions_tests.rs | 42 +- .../sandbox_mode/workspace_write.md | 2 +- .../core/src/context_manager/history_tests.rs | 2 + codex-rs/core/src/context_manager/updates.rs | 11 +- codex-rs/core/src/exec.rs | 62 +-- codex-rs/core/src/exec_tests.rs | 62 ++- codex-rs/core/src/guardian/review_session.rs | 10 +- codex-rs/core/src/guardian/tests.rs | 8 +- .../core/src/personality_migration_tests.rs | 1 + codex-rs/core/src/rollout.rs | 4 + codex-rs/core/src/safety_tests.rs | 21 +- codex-rs/core/src/session/handlers.rs | 4 + codex-rs/core/src/session/mod.rs | 9 +- codex-rs/core/src/session/review.rs | 1 + .../session/rollout_reconstruction_tests.rs | 16 + codex-rs/core/src/session/session.rs | 91 +++-- codex-rs/core/src/session/tests.rs | 122 ++++-- codex-rs/core/src/session/turn_context.rs | 20 +- .../src/tools/handlers/apply_patch_tests.rs | 2 - .../src/tools/handlers/multi_agents_common.rs | 1 + .../src/tools/handlers/multi_agents_tests.rs | 2 +- codex-rs/core/src/tools/orchestrator.rs | 3 + .../core/src/tools/runtimes/apply_patch.rs | 1 + .../src/tools/runtimes/apply_patch_tests.rs | 2 + codex-rs/core/src/tools/runtimes/mod_tests.rs | 1 + .../tools/runtimes/shell/unix_escalation.rs | 6 +- codex-rs/core/src/tools/sandboxing.rs | 2 + codex-rs/core/tests/common/test_codex.rs | 2 + codex-rs/core/tests/suite/approvals.rs | 4 - codex-rs/core/tests/suite/exec.rs | 1 + .../core/tests/suite/permissions_messages.rs | 10 +- .../core/tests/suite/personality_migration.rs | 2 + codex-rs/core/tests/suite/prompt_caching.rs | 9 +- .../core/tests/suite/request_permissions.rs | 1 - codex-rs/core/tests/suite/resume_warning.rs | 2 + codex-rs/core/tests/suite/sqlite_state.rs | 1 + codex-rs/core/tests/suite/unified_exec.rs | 19 +- codex-rs/core/tests/suite/user_shell_cmd.rs | 5 +- codex-rs/core/tests/suite/windows_sandbox.rs | 2 + codex-rs/exec-server/src/fs_sandbox.rs | 1 + codex-rs/exec/Cargo.toml | 1 + .../src/event_processor_with_human_output.rs | 90 +---- ...event_processor_with_human_output_tests.rs | 81 ---- codex-rs/exec/src/lib.rs | 120 ++++-- codex-rs/exec/src/lib_tests.rs | 183 ++++++++- .../tests/event_processor_with_json_output.rs | 1 + codex-rs/exec/tests/suite/sandbox.rs | 20 +- codex-rs/file-system/src/lib.rs | 22 +- .../linux-sandbox/tests/suite/landlock.rs | 2 + codex-rs/mcp-server/src/outgoing_message.rs | 5 + codex-rs/memories/write/src/phase2.rs | 33 +- codex-rs/protocol/src/models.rs | 38 +- codex-rs/protocol/src/permissions.rs | 140 ++++--- codex-rs/protocol/src/protocol.rs | 312 ++++++++++++++- codex-rs/rollout/Cargo.toml | 1 + codex-rs/rollout/src/config.rs | 17 + codex-rs/rollout/src/metadata_tests.rs | 3 + codex-rs/rollout/src/recorder.rs | 1 + codex-rs/rollout/src/recorder_tests.rs | 4 + codex-rs/rollout/src/session_index_tests.rs | 1 + codex-rs/rollout/src/tests.rs | 1 + codex-rs/sandboxing/src/manager.rs | 49 ++- codex-rs/sandboxing/src/manager_tests.rs | 42 ++ codex-rs/sandboxing/src/seatbelt_tests.rs | 118 +++--- codex-rs/state/src/extract.rs | 8 + codex-rs/state/src/runtime/threads.rs | 2 + codex-rs/thread-manager-sample/src/main.rs | 16 +- codex-rs/thread-store/Cargo.toml | 1 + .../thread-store/src/local/create_thread.rs | 1 + .../thread-store/src/local/list_threads.rs | 1 + .../thread-store/src/local/live_writer.rs | 1 + codex-rs/thread-store/src/local/mod.rs | 2 + .../src/local/update_thread_metadata.rs | 1 + codex-rs/thread-store/src/types.rs | 4 + codex-rs/tui/src/app/startup_prompts.rs | 2 +- codex-rs/tui/src/chatwidget.rs | 10 +- .../tui/src/chatwidget/status_surfaces.rs | 4 +- codex-rs/tui/src/history_cell.rs | 2 +- codex-rs/tui/src/status/card.rs | 88 +++-- codex-rs/tui/src/status/tests.rs | 131 +++++-- codex-rs/utils/sandbox-summary/Cargo.toml | 2 +- .../sandbox-summary/src/config_summary.rs | 26 +- codex-rs/utils/sandbox-summary/src/lib.rs | 2 +- .../sandbox-summary/src/sandbox_summary.rs | 308 +++++++++++---- codex-rs/windows-sandbox-rs/Cargo.toml | 2 +- codex-rs/windows-sandbox-rs/src/allow.rs | 78 ++-- codex-rs/windows-sandbox-rs/src/audit.rs | 9 +- .../windows-sandbox-rs/src/elevated_impl.rs | 1 - codex-rs/windows-sandbox-rs/src/lib.rs | 18 +- codex-rs/windows-sandbox-rs/src/setup.rs | 58 +-- codex-rs/windows-sandbox-rs/src/spawn_prep.rs | 1 - 133 files changed, 2307 insertions(+), 2787 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts delete mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f1d78b7479..15aa056f85 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2697,6 +2697,7 @@ dependencies = [ "codex-utils-cargo-bin", "codex-utils-cli", "codex-utils-oss", + "codex-utils-sandbox-summary", "core_test_support", "libc", "opentelemetry", @@ -3495,6 +3496,7 @@ dependencies = [ "codex-otel", "codex-protocol", "codex-state", + "codex-utils-absolute-path", "codex-utils-path", "codex-utils-string", "pretty_assertions", @@ -3687,6 +3689,7 @@ dependencies = [ "codex-protocol", "codex-rollout", "codex-state", + "codex-utils-absolute-path", "pretty_assertions", "serde", "serde_json", diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 5b4a72229b..e51e2bbeb8 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -201,11 +201,11 @@ fn sample_thread_start_response( model_provider: "openai".to_string(), service_tier: None, cwd: test_path_buf("/tmp").abs(), + workspace_roots: Vec::new(), instruction_sources: Vec::new(), approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, - permission_profile: None, active_permission_profile: None, reasoning_effort: None, }) @@ -257,11 +257,11 @@ fn sample_thread_resume_response_with_source( model_provider: "openai".to_string(), service_tier: None, cwd: test_path_buf("/tmp").abs(), + workspace_roots: Vec::new(), instruction_sources: Vec::new(), approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, - permission_profile: None, active_permission_profile: None, reasoning_effort: None, }) diff --git a/codex-rs/analytics/src/client_tests.rs b/codex-rs/analytics/src/client_tests.rs index 71c46d808a..c06372d1e7 100644 --- a/codex-rs/analytics/src/client_tests.rs +++ b/codex-rs/analytics/src/client_tests.rs @@ -12,7 +12,6 @@ use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer; use codex_app_server_protocol::AskForApproval as AppServerAskForApproval; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::ClientResponsePayload; -use codex_app_server_protocol::PermissionProfile as AppServerPermissionProfile; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy; use codex_app_server_protocol::SessionSource as AppServerSessionSource; @@ -29,7 +28,6 @@ use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::TurnStatus as AppServerTurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; -use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_absolute_path::test_support::test_path_buf; use std::collections::HashSet; @@ -142,10 +140,6 @@ fn sample_thread(thread_id: &str) -> Thread { } } -fn sample_permission_profile() -> AppServerPermissionProfile { - CorePermissionProfile::Disabled.into() -} - fn sample_thread_start_response() -> ClientResponsePayload { ClientResponsePayload::ThreadStart(ThreadStartResponse { thread: sample_thread("thread-1"), @@ -153,11 +147,11 @@ fn sample_thread_start_response() -> ClientResponsePayload { model_provider: "openai".to_string(), service_tier: None, cwd: test_path_buf("/tmp").abs(), + workspace_roots: Vec::new(), instruction_sources: Vec::new(), approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, - permission_profile: Some(sample_permission_profile()), active_permission_profile: None, reasoning_effort: None, }) @@ -170,11 +164,11 @@ fn sample_thread_resume_response() -> ClientResponsePayload { model_provider: "openai".to_string(), service_tier: None, cwd: test_path_buf("/tmp").abs(), + workspace_roots: Vec::new(), instruction_sources: Vec::new(), approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, - permission_profile: Some(sample_permission_profile()), active_permission_profile: None, reasoning_effort: None, }) @@ -187,11 +181,11 @@ fn sample_thread_fork_response() -> ClientResponsePayload { model_provider: "openai".to_string(), service_tier: None, cwd: test_path_buf("/tmp").abs(), + workspace_roots: Vec::new(), instruction_sources: Vec::new(), approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, - permission_profile: Some(sample_permission_profile()), active_permission_profile: None, reasoning_effort: None, }) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 6351993046..ebf0656cd7 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1850,31 +1850,6 @@ } ] }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, "PermissionProfileNetworkPermissions": { "properties": { "enabled": { @@ -1886,40 +1861,6 @@ ], "type": "object" }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", @@ -3133,13 +3074,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -3406,7 +3340,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for fork. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -3817,7 +3752,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for resume. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -4197,7 +4133,7 @@ "type": "null" } ], - "description": "Override the sandbox policy for this turn and subsequent turns." + "description": "Deprecated for turns. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "description": "Override the service tier for this turn and subsequent turns.", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 9934e36940..c15c195d9b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -5583,14 +5583,6 @@ "id": { "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", "type": "string" - }, - "modifications": { - "default": [], - "description": "Bounded user-requested modifications applied on top of the named profile, if any.", - "items": { - "$ref": "#/definitions/v2/ActivePermissionProfileModification" - }, - "type": "array" } }, "required": [ @@ -5598,31 +5590,6 @@ ], "type": "object" }, - "ActivePermissionProfileModification": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/v2/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootActivePermissionProfileModificationType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootActivePermissionProfileModification", - "type": "object" - } - ] - }, "AddCreditsNudgeCreditType": { "enum": [ "credits", @@ -11710,31 +11677,6 @@ } ] }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/v2/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, "PermissionProfileNetworkPermissions": { "properties": { "enabled": { @@ -11746,40 +11688,6 @@ ], "type": "object" }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/v2/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", @@ -14403,13 +14311,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/v2/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -15397,7 +15298,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for fork. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -15473,7 +15375,7 @@ "$ref": "#/definitions/v2/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -16900,7 +16802,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for resume. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -16965,7 +16868,7 @@ "$ref": "#/definitions/v2/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -17273,7 +17176,7 @@ "$ref": "#/definitions/v2/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -17942,7 +17845,7 @@ "type": "null" } ], - "description": "Override the sandbox policy for this turn and subsequent turns." + "description": "Deprecated for turns. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "description": "Override the service tier for this turn and subsequent turns.", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 1926b22091..7dcc773714 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -143,14 +143,6 @@ "id": { "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", "type": "string" - }, - "modifications": { - "default": [], - "description": "Bounded user-requested modifications applied on top of the named profile, if any.", - "items": { - "$ref": "#/definitions/ActivePermissionProfileModification" - }, - "type": "array" } }, "required": [ @@ -158,31 +150,6 @@ ], "type": "object" }, - "ActivePermissionProfileModification": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootActivePermissionProfileModificationType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootActivePermissionProfileModification", - "type": "object" - } - ] - }, "AddCreditsNudgeCreditType": { "enum": [ "credits", @@ -8259,31 +8226,6 @@ } ] }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, "PermissionProfileNetworkPermissions": { "properties": { "enabled": { @@ -8295,40 +8237,6 @@ ], "type": "object" }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", @@ -10952,13 +10860,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -13221,7 +13122,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for fork. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -13297,7 +13199,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -14724,7 +14626,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for resume. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ @@ -14789,7 +14692,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -15097,7 +15000,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ @@ -15766,7 +15669,7 @@ "type": "null" } ], - "description": "Override the sandbox policy for this turn and subsequent turns." + "description": "Deprecated for turns. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "description": "Override the service tier for this turn and subsequent turns.", diff --git a/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json b/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json index f29483862c..6a1098fda4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/CommandExecParams.json @@ -441,13 +441,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json index 29d67403cd..3b3a81bb92 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkParams.json @@ -64,65 +64,6 @@ } ] }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "SandboxMode": { "enum": [ "read-only", @@ -212,7 +153,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for fork. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 6e74ab4ac8..98e00a1a1a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -18,14 +18,6 @@ "id": { "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", "type": "string" - }, - "modifications": { - "default": [], - "description": "Bounded user-requested modifications applied on top of the named profile, if any.", - "items": { - "$ref": "#/definitions/ActivePermissionProfileModification" - }, - "type": "array" } }, "required": [ @@ -33,31 +25,6 @@ ], "type": "object" }, - "ActivePermissionProfileModification": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootActivePermissionProfileModificationType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootActivePermissionProfileModification", - "type": "object" - } - ] - }, "AgentPath": { "type": "string" }, @@ -503,202 +470,6 @@ ], "type": "string" }, - "FileSystemAccessMode": { - "enum": [ - "read", - "write", - "none" - ], - "type": "string" - }, - "FileSystemPath": { - "oneOf": [ - { - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "path" - ], - "title": "PathFileSystemPathType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "PathFileSystemPath", - "type": "object" - }, - { - "properties": { - "pattern": { - "type": "string" - }, - "type": { - "enum": [ - "glob_pattern" - ], - "title": "GlobPatternFileSystemPathType", - "type": "string" - } - }, - "required": [ - "pattern", - "type" - ], - "title": "GlobPatternFileSystemPath", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "special" - ], - "title": "SpecialFileSystemPathType", - "type": "string" - }, - "value": { - "$ref": "#/definitions/FileSystemSpecialPath" - } - }, - "required": [ - "type", - "value" - ], - "title": "SpecialFileSystemPath", - "type": "object" - } - ] - }, - "FileSystemSandboxEntry": { - "properties": { - "access": { - "$ref": "#/definitions/FileSystemAccessMode" - }, - "path": { - "$ref": "#/definitions/FileSystemPath" - } - }, - "required": [ - "access", - "path" - ], - "type": "object" - }, - "FileSystemSpecialPath": { - "oneOf": [ - { - "properties": { - "kind": { - "enum": [ - "root" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "RootFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "minimal" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "MinimalFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "project_roots" - ], - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind" - ], - "title": "KindFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "tmpdir" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "TmpdirFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "slash_tmp" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "SlashTmpFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "unknown" - ], - "type": "string" - }, - "path": { - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind", - "path" - ], - "type": "object" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -937,135 +708,6 @@ } ] }, - "PermissionProfile": { - "oneOf": [ - { - "description": "Codex owns sandbox construction for this profile.", - "properties": { - "fileSystem": { - "$ref": "#/definitions/PermissionProfileFileSystemPermissions" - }, - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "managed" - ], - "title": "ManagedPermissionProfileType", - "type": "string" - } - }, - "required": [ - "fileSystem", - "network", - "type" - ], - "title": "ManagedPermissionProfile", - "type": "object" - }, - { - "description": "Do not apply an outer sandbox.", - "properties": { - "type": { - "enum": [ - "disabled" - ], - "title": "DisabledPermissionProfileType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "DisabledPermissionProfile", - "type": "object" - }, - { - "description": "Filesystem isolation is enforced by an external caller.", - "properties": { - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "external" - ], - "title": "ExternalPermissionProfileType", - "type": "string" - } - }, - "required": [ - "network", - "type" - ], - "title": "ExternalPermissionProfile", - "type": "object" - } - ] - }, - "PermissionProfileFileSystemPermissions": { - "oneOf": [ - { - "properties": { - "entries": { - "items": { - "$ref": "#/definitions/FileSystemSandboxEntry" - }, - "type": "array" - }, - "globScanMaxDepth": { - "format": "uint", - "minimum": 1.0, - "type": [ - "integer", - "null" - ] - }, - "type": { - "enum": [ - "restricted" - ], - "title": "RestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "entries", - "type" - ], - "title": "RestrictedPermissionProfileFileSystemPermissions", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "unrestricted" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissions", - "type": "object" - } - ] - }, - "PermissionProfileNetworkPermissions": { - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "type": "object" - }, "ReasoningEffort": { "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", "enum": [ @@ -1160,13 +802,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -2605,7 +2240,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json index 5f07fe0149..69b12a365c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeParams.json @@ -298,65 +298,6 @@ } ] }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", @@ -1091,7 +1032,8 @@ { "type": "null" } - ] + ], + "description": "Deprecated for resume. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 727b7a3fb2..49c7402bbf 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -18,14 +18,6 @@ "id": { "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", "type": "string" - }, - "modifications": { - "default": [], - "description": "Bounded user-requested modifications applied on top of the named profile, if any.", - "items": { - "$ref": "#/definitions/ActivePermissionProfileModification" - }, - "type": "array" } }, "required": [ @@ -33,31 +25,6 @@ ], "type": "object" }, - "ActivePermissionProfileModification": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootActivePermissionProfileModificationType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootActivePermissionProfileModification", - "type": "object" - } - ] - }, "AgentPath": { "type": "string" }, @@ -503,202 +470,6 @@ ], "type": "string" }, - "FileSystemAccessMode": { - "enum": [ - "read", - "write", - "none" - ], - "type": "string" - }, - "FileSystemPath": { - "oneOf": [ - { - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "path" - ], - "title": "PathFileSystemPathType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "PathFileSystemPath", - "type": "object" - }, - { - "properties": { - "pattern": { - "type": "string" - }, - "type": { - "enum": [ - "glob_pattern" - ], - "title": "GlobPatternFileSystemPathType", - "type": "string" - } - }, - "required": [ - "pattern", - "type" - ], - "title": "GlobPatternFileSystemPath", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "special" - ], - "title": "SpecialFileSystemPathType", - "type": "string" - }, - "value": { - "$ref": "#/definitions/FileSystemSpecialPath" - } - }, - "required": [ - "type", - "value" - ], - "title": "SpecialFileSystemPath", - "type": "object" - } - ] - }, - "FileSystemSandboxEntry": { - "properties": { - "access": { - "$ref": "#/definitions/FileSystemAccessMode" - }, - "path": { - "$ref": "#/definitions/FileSystemPath" - } - }, - "required": [ - "access", - "path" - ], - "type": "object" - }, - "FileSystemSpecialPath": { - "oneOf": [ - { - "properties": { - "kind": { - "enum": [ - "root" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "RootFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "minimal" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "MinimalFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "project_roots" - ], - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind" - ], - "title": "KindFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "tmpdir" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "TmpdirFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "slash_tmp" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "SlashTmpFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "unknown" - ], - "type": "string" - }, - "path": { - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind", - "path" - ], - "type": "object" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -937,135 +708,6 @@ } ] }, - "PermissionProfile": { - "oneOf": [ - { - "description": "Codex owns sandbox construction for this profile.", - "properties": { - "fileSystem": { - "$ref": "#/definitions/PermissionProfileFileSystemPermissions" - }, - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "managed" - ], - "title": "ManagedPermissionProfileType", - "type": "string" - } - }, - "required": [ - "fileSystem", - "network", - "type" - ], - "title": "ManagedPermissionProfile", - "type": "object" - }, - { - "description": "Do not apply an outer sandbox.", - "properties": { - "type": { - "enum": [ - "disabled" - ], - "title": "DisabledPermissionProfileType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "DisabledPermissionProfile", - "type": "object" - }, - { - "description": "Filesystem isolation is enforced by an external caller.", - "properties": { - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "external" - ], - "title": "ExternalPermissionProfileType", - "type": "string" - } - }, - "required": [ - "network", - "type" - ], - "title": "ExternalPermissionProfile", - "type": "object" - } - ] - }, - "PermissionProfileFileSystemPermissions": { - "oneOf": [ - { - "properties": { - "entries": { - "items": { - "$ref": "#/definitions/FileSystemSandboxEntry" - }, - "type": "array" - }, - "globScanMaxDepth": { - "format": "uint", - "minimum": 1.0, - "type": [ - "integer", - "null" - ] - }, - "type": { - "enum": [ - "restricted" - ], - "title": "RestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "entries", - "type" - ], - "title": "RestrictedPermissionProfileFileSystemPermissions", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "unrestricted" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissions", - "type": "object" - } - ] - }, - "PermissionProfileNetworkPermissions": { - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "type": "object" - }, "ReasoningEffort": { "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", "enum": [ @@ -1160,13 +802,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -2605,7 +2240,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index 9a60049a61..99b25490ab 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -90,65 +90,6 @@ ], "type": "object" }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index bf03f0fb55..7c246aa92f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -18,14 +18,6 @@ "id": { "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", "type": "string" - }, - "modifications": { - "default": [], - "description": "Bounded user-requested modifications applied on top of the named profile, if any.", - "items": { - "$ref": "#/definitions/ActivePermissionProfileModification" - }, - "type": "array" } }, "required": [ @@ -33,31 +25,6 @@ ], "type": "object" }, - "ActivePermissionProfileModification": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootActivePermissionProfileModificationType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootActivePermissionProfileModification", - "type": "object" - } - ] - }, "AgentPath": { "type": "string" }, @@ -503,202 +470,6 @@ ], "type": "string" }, - "FileSystemAccessMode": { - "enum": [ - "read", - "write", - "none" - ], - "type": "string" - }, - "FileSystemPath": { - "oneOf": [ - { - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "path" - ], - "title": "PathFileSystemPathType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "PathFileSystemPath", - "type": "object" - }, - { - "properties": { - "pattern": { - "type": "string" - }, - "type": { - "enum": [ - "glob_pattern" - ], - "title": "GlobPatternFileSystemPathType", - "type": "string" - } - }, - "required": [ - "pattern", - "type" - ], - "title": "GlobPatternFileSystemPath", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "special" - ], - "title": "SpecialFileSystemPathType", - "type": "string" - }, - "value": { - "$ref": "#/definitions/FileSystemSpecialPath" - } - }, - "required": [ - "type", - "value" - ], - "title": "SpecialFileSystemPath", - "type": "object" - } - ] - }, - "FileSystemSandboxEntry": { - "properties": { - "access": { - "$ref": "#/definitions/FileSystemAccessMode" - }, - "path": { - "$ref": "#/definitions/FileSystemPath" - } - }, - "required": [ - "access", - "path" - ], - "type": "object" - }, - "FileSystemSpecialPath": { - "oneOf": [ - { - "properties": { - "kind": { - "enum": [ - "root" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "RootFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "minimal" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "MinimalFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "project_roots" - ], - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind" - ], - "title": "KindFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "tmpdir" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "TmpdirFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "slash_tmp" - ], - "type": "string" - } - }, - "required": [ - "kind" - ], - "title": "SlashTmpFileSystemSpecialPath", - "type": "object" - }, - { - "properties": { - "kind": { - "enum": [ - "unknown" - ], - "type": "string" - }, - "path": { - "type": "string" - }, - "subpath": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "kind", - "path" - ], - "type": "object" - } - ] - }, "FileUpdateChange": { "properties": { "diff": { @@ -937,135 +708,6 @@ } ] }, - "PermissionProfile": { - "oneOf": [ - { - "description": "Codex owns sandbox construction for this profile.", - "properties": { - "fileSystem": { - "$ref": "#/definitions/PermissionProfileFileSystemPermissions" - }, - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "managed" - ], - "title": "ManagedPermissionProfileType", - "type": "string" - } - }, - "required": [ - "fileSystem", - "network", - "type" - ], - "title": "ManagedPermissionProfile", - "type": "object" - }, - { - "description": "Do not apply an outer sandbox.", - "properties": { - "type": { - "enum": [ - "disabled" - ], - "title": "DisabledPermissionProfileType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "DisabledPermissionProfile", - "type": "object" - }, - { - "description": "Filesystem isolation is enforced by an external caller.", - "properties": { - "network": { - "$ref": "#/definitions/PermissionProfileNetworkPermissions" - }, - "type": { - "enum": [ - "external" - ], - "title": "ExternalPermissionProfileType", - "type": "string" - } - }, - "required": [ - "network", - "type" - ], - "title": "ExternalPermissionProfile", - "type": "object" - } - ] - }, - "PermissionProfileFileSystemPermissions": { - "oneOf": [ - { - "properties": { - "entries": { - "items": { - "$ref": "#/definitions/FileSystemSandboxEntry" - }, - "type": "array" - }, - "globScanMaxDepth": { - "format": "uint", - "minimum": 1.0, - "type": [ - "integer", - "null" - ] - }, - "type": { - "enum": [ - "restricted" - ], - "title": "RestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "entries", - "type" - ], - "title": "RestrictedPermissionProfileFileSystemPermissions", - "type": "object" - }, - { - "properties": { - "type": { - "enum": [ - "unrestricted" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissionsType", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "UnrestrictedPermissionProfileFileSystemPermissions", - "type": "object" - } - ] - }, - "PermissionProfileNetworkPermissions": { - "properties": { - "enabled": { - "type": "boolean" - } - }, - "required": [ - "enabled" - ], - "type": "object" - }, "ReasoningEffort": { "description": "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", "enum": [ @@ -1160,13 +802,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -2605,7 +2240,7 @@ "$ref": "#/definitions/SandboxPolicy" } ], - "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions." + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` and `workspaceRoots`." }, "serviceTier": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 1ef33d4301..7d8ba1e0b4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -114,65 +114,6 @@ ], "type": "string" }, - "PermissionProfileModificationParams": { - "oneOf": [ - { - "description": "Additional concrete directory that should be writable.", - "properties": { - "path": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": { - "enum": [ - "additionalWritableRoot" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParamsType", - "type": "string" - } - }, - "required": [ - "path", - "type" - ], - "title": "AdditionalWritableRootPermissionProfileModificationParams", - "type": "object" - } - ] - }, - "PermissionProfileSelectionParams": { - "oneOf": [ - { - "description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.", - "properties": { - "id": { - "type": "string" - }, - "modifications": { - "items": { - "$ref": "#/definitions/PermissionProfileModificationParams" - }, - "type": [ - "array", - "null" - ] - }, - "type": { - "enum": [ - "profile" - ], - "title": "ProfilePermissionProfileSelectionParamsType", - "type": "string" - } - }, - "required": [ - "id", - "type" - ], - "title": "ProfilePermissionProfileSelectionParams", - "type": "object" - } - ] - }, "Personality": { "enum": [ "none", @@ -295,13 +236,6 @@ ], "title": "WorkspaceWriteSandboxPolicyType", "type": "string" - }, - "writableRoots": { - "default": [], - "items": { - "$ref": "#/definitions/AbsolutePathBuf" - }, - "type": "array" } }, "required": [ @@ -576,7 +510,7 @@ "type": "null" } ], - "description": "Override the sandbox policy for this turn and subsequent turns." + "description": "Deprecated for turns. When present, the server treats this as a compatibility spelling for selecting a matching named permissions profile." }, "serviceTier": { "description": "Override the service tier for this turn and subsequent turns.", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts index cbc8c6ef0a..73f9efcab5 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfile.ts @@ -1,7 +1,6 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ActivePermissionProfileModification } from "./ActivePermissionProfileModification"; export type ActivePermissionProfile = { /** @@ -13,9 +12,4 @@ id: string, * Parent profile identifier once permissions profiles support * inheritance. This is currently always `null`. */ -extends: string | null, -/** - * Bounded user-requested modifications applied on top of the named - * profile, if any. - */ -modifications: Array, }; +extends: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts deleted file mode 100644 index 1cbee6868a..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ActivePermissionProfileModification.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "../AbsolutePathBuf"; - -export type ActivePermissionProfileModification = { "type": "additionalWritableRoot", path: AbsolutePathBuf, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts deleted file mode 100644 index c619edcea8..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileModificationParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "../AbsolutePathBuf"; - -export type PermissionProfileModificationParams = { "type": "additionalWritableRoot", path: AbsolutePathBuf, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts deleted file mode 100644 index a415bd0028..0000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileSelectionParams.ts +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { PermissionProfileModificationParams } from "./PermissionProfileModificationParams"; - -export type PermissionProfileSelectionParams = { "type": "profile", id: string, modifications?: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts index 5575701ff2..1715d7710e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SandboxPolicy.ts @@ -1,7 +1,6 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { AbsolutePathBuf } from "../AbsolutePathBuf"; import type { NetworkAccess } from "./NetworkAccess"; -export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", networkAccess: boolean, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", writableRoots: Array, networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, }; +export type SandboxPolicy = { "type": "dangerFullAccess" } | { "type": "readOnly", networkAccess: boolean, } | { "type": "externalSandbox", networkAccess: NetworkAccess, } | { "type": "workspaceWrite", networkAccess: boolean, excludeTmpdirEnvVar: boolean, excludeSlashTmp: boolean, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts index 6076a4bb14..167a6f1465 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkParams.ts @@ -23,7 +23,12 @@ model?: string | null, modelProvider?: string | null, serviceTier?: string | nul * Override where approval requests are routed for review on this thread * and subsequent turns. */ -approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /** +approvalsReviewer?: ApprovalsReviewer | null, /** + * Deprecated for fork. When present, the server treats this as a + * compatibility spelling for selecting a matching named permissions + * profile. + */ +sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /** * Optional client-supplied analytics source classification for this forked thread. */ threadSource?: ThreadSource | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts index c44533ec1a..acada51444 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts @@ -16,7 +16,6 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** */ approvalsReviewer: ApprovalsReviewer, /** * Legacy sandbox policy retained for compatibility. Experimental clients - * should prefer `permissionProfile` when they need exact runtime - * permissions. + * should prefer `activePermissionProfile` and `workspaceRoots`. */ sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts index 6d1dbdca4f..feae3906ba 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeParams.ts @@ -25,4 +25,9 @@ model?: string | null, modelProvider?: string | null, serviceTier?: string | nul * Override where approval requests are routed for review on this thread * and subsequent turns. */ -approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null}; +approvalsReviewer?: ApprovalsReviewer | null, /** + * Deprecated for resume. When present, the server treats this as a + * compatibility spelling for selecting a matching named permissions + * profile. + */ +sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts index f91756c7c6..fc9a2008f3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts @@ -16,7 +16,6 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** */ approvalsReviewer: ApprovalsReviewer, /** * Legacy sandbox policy retained for compatibility. Experimental clients - * should prefer `permissionProfile` when they need exact runtime - * permissions. + * should prefer `activePermissionProfile` and `workspaceRoots`. */ sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts index 9573bd7dee..ea219decbd 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts @@ -16,7 +16,6 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** */ approvalsReviewer: ApprovalsReviewer, /** * Legacy sandbox policy retained for compatibility. Experimental clients - * should prefer `permissionProfile` when they need exact runtime - * permissions. + * should prefer `activePermissionProfile` and `workspaceRoots`. */ sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts index b04919d86b..e6daf0b557 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/TurnStartParams.ts @@ -21,7 +21,9 @@ approvalPolicy?: AskForApproval | null, /** * subsequent turns. */ approvalsReviewer?: ApprovalsReviewer | null, /** - * Override the sandbox policy for this turn and subsequent turns. + * Deprecated for turns. When present, the server treats this as a + * compatibility spelling for selecting a matching named permissions + * profile. */ sandboxPolicy?: SandboxPolicy | null, /** * Override the model for this turn and subsequent turns. diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index a6b961366e..de91837849 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -5,7 +5,6 @@ export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedN export type { AccountRateLimitsUpdatedNotification } from "./AccountRateLimitsUpdatedNotification"; export type { AccountUpdatedNotification } from "./AccountUpdatedNotification"; export type { ActivePermissionProfile } from "./ActivePermissionProfile"; -export type { ActivePermissionProfileModification } from "./ActivePermissionProfileModification"; export type { AddCreditsNudgeCreditType } from "./AddCreditsNudgeCreditType"; export type { AddCreditsNudgeEmailStatus } from "./AddCreditsNudgeEmailStatus"; export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; @@ -257,9 +256,7 @@ export type { PatchChangeKind } from "./PatchChangeKind"; export type { PermissionGrantScope } from "./PermissionGrantScope"; export type { PermissionProfile } from "./PermissionProfile"; export type { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions"; -export type { PermissionProfileModificationParams } from "./PermissionProfileModificationParams"; export type { PermissionProfileNetworkPermissions } from "./PermissionProfileNetworkPermissions"; -export type { PermissionProfileSelectionParams } from "./PermissionProfileSelectionParams"; export type { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams"; export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse"; export type { PlanDeltaNotification } from "./PlanDeltaNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index ae00b08b73..636f37f95e 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -2272,11 +2272,11 @@ mod tests { model_provider: "openai".to_string(), service_tier: None, cwd, + workspace_roots: vec![], instruction_sources: vec![absolute_path("/tmp/AGENTS.md")], approval_policy: v2::AskForApproval::OnFailure, approvals_reviewer: v2::ApprovalsReviewer::User, sandbox: v2::SandboxPolicy::DangerFullAccess, - permission_profile: None, active_permission_profile: None, reasoning_effort: None, }, @@ -2316,13 +2316,13 @@ mod tests { "modelProvider": "openai", "serviceTier": null, "cwd": absolute_path_string("tmp"), + "workspaceRoots": [], "instructionSources": [absolute_path_string("tmp/AGENTS.md")], "approvalPolicy": "on-failure", "approvalsReviewer": "user", "sandbox": { "type": "dangerFullAccess" }, - "permissionProfile": null, "activePermissionProfile": null, "reasoningEffort": null } diff --git a/codex-rs/app-server-protocol/src/protocol/v2/permissions.rs b/codex-rs/app-server-protocol/src/protocol/v2/permissions.rs index 86614a6aeb..919b2542cd 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/permissions.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/permissions.rs @@ -5,7 +5,6 @@ use codex_protocol::approvals::NetworkApprovalProtocol as CoreNetworkApprovalPro use codex_protocol::approvals::NetworkPolicyAmendment as CoreNetworkPolicyAmendment; use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleAction; use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile; -use codex_protocol::models::ActivePermissionProfileModification as CoreActivePermissionProfileModification; use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile; use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions; use codex_protocol::models::ManagedFileSystemPermissions as CoreManagedFileSystemPermissions; @@ -437,41 +436,6 @@ pub struct ActivePermissionProfile { /// inheritance. This is currently always `null`. #[serde(default)] pub extends: Option, - /// Bounded user-requested modifications applied on top of the named - /// profile, if any. - #[serde(default)] - pub modifications: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(tag = "type", rename_all = "camelCase")] -#[ts(tag = "type")] -#[ts(export_to = "v2/")] -pub enum ActivePermissionProfileModification { - /// Additional concrete directory that should be writable. - #[serde(rename_all = "camelCase")] - #[ts(rename_all = "camelCase")] - AdditionalWritableRoot { path: AbsolutePathBuf }, -} - -impl From for ActivePermissionProfileModification { - fn from(value: CoreActivePermissionProfileModification) -> Self { - match value { - CoreActivePermissionProfileModification::AdditionalWritableRoot { path } => { - Self::AdditionalWritableRoot { path } - } - } - } -} - -impl From for CoreActivePermissionProfileModification { - fn from(value: ActivePermissionProfileModification) -> Self { - match value { - ActivePermissionProfileModification::AdditionalWritableRoot { path } => { - Self::AdditionalWritableRoot { path } - } - } - } } impl From for ActivePermissionProfile { @@ -479,11 +443,6 @@ impl From for ActivePermissionProfile { Self { id: value.id, extends: value.extends, - modifications: value - .modifications - .into_iter() - .map(ActivePermissionProfileModification::from) - .collect(), } } } @@ -493,42 +452,10 @@ impl From for CoreActivePermissionProfile { Self { id: value.id, extends: value.extends, - modifications: value - .modifications - .into_iter() - .map(CoreActivePermissionProfileModification::from) - .collect(), } } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(tag = "type", rename_all = "camelCase")] -#[ts(tag = "type")] -#[ts(export_to = "v2/")] -pub enum PermissionProfileSelectionParams { - /// Select a named built-in or user-defined profile and optionally apply - /// bounded modifications that Codex knows how to validate. - #[serde(rename_all = "camelCase")] - #[ts(rename_all = "camelCase")] - Profile { - id: String, - #[ts(optional = nullable)] - modifications: Option>, - }, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] -#[serde(tag = "type", rename_all = "camelCase")] -#[ts(tag = "type")] -#[ts(export_to = "v2/")] -pub enum PermissionProfileModificationParams { - /// Additional concrete directory that should be writable. - #[serde(rename_all = "camelCase")] - #[ts(rename_all = "camelCase")] - AdditionalWritableRoot { path: AbsolutePathBuf }, -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -607,14 +534,16 @@ pub enum SandboxPolicy { #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] WorkspaceWrite { - #[serde(default)] - writable_roots: Vec, #[serde(default)] network_access: bool, #[serde(default)] exclude_tmpdir_env_var: bool, #[serde(default)] exclude_slash_tmp: bool, + #[serde(default, skip_serializing)] + #[schemars(skip)] + #[ts(skip)] + legacy_writable_roots: Vec, }, } @@ -690,10 +619,10 @@ impl<'de> Deserialize<'de> for SandboxPolicy { )); } Ok(SandboxPolicy::WorkspaceWrite { - writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, + legacy_writable_roots: writable_roots, }) } } @@ -720,18 +649,60 @@ impl SandboxPolicy { } } SandboxPolicy::WorkspaceWrite { - writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, + legacy_writable_roots: _, } => codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { - writable_roots: writable_roots.clone(), network_access: *network_access, exclude_tmpdir_env_var: *exclude_tmpdir_env_var, exclude_slash_tmp: *exclude_slash_tmp, }, } } + + pub fn legacy_writable_roots(&self) -> &[AbsolutePathBuf] { + match self { + SandboxPolicy::WorkspaceWrite { + legacy_writable_roots, + .. + } => legacy_writable_roots, + SandboxPolicy::DangerFullAccess + | SandboxPolicy::ReadOnly { .. } + | SandboxPolicy::ExternalSandbox { .. } => &[], + } + } + + pub fn to_permission_profile_for_cwd(&self, cwd: &std::path::Path) -> CorePermissionProfile { + match self { + SandboxPolicy::WorkspaceWrite { + legacy_writable_roots, + .. + } if legacy_writable_roots.is_empty() => { + CorePermissionProfile::from_legacy_sandbox_policy_for_cwd(&self.to_core(), cwd) + } + SandboxPolicy::WorkspaceWrite { + network_access, + exclude_tmpdir_env_var, + exclude_slash_tmp, + legacy_writable_roots, + } => CorePermissionProfile::workspace_write_with( + legacy_writable_roots, + if *network_access { + CoreNetworkSandboxPolicy::Enabled + } else { + CoreNetworkSandboxPolicy::Restricted + }, + *exclude_tmpdir_env_var, + *exclude_slash_tmp, + ), + SandboxPolicy::DangerFullAccess + | SandboxPolicy::ReadOnly { .. } + | SandboxPolicy::ExternalSandbox { .. } => { + CorePermissionProfile::from_legacy_sandbox_policy_for_cwd(&self.to_core(), cwd) + } + } + } } impl From for SandboxPolicy { @@ -752,15 +723,14 @@ impl From for SandboxPolicy { } } codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { - writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => SandboxPolicy::WorkspaceWrite { - writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, + legacy_writable_roots: Vec::new(), }, } } diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 73b22cecb4..7091e1808d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -2019,17 +2019,16 @@ fn mcp_server_elicitation_response_serializes_nullable_content() { #[test] fn sandbox_policy_round_trips_workspace_write_access() { let v2_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], network_access: true, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, + legacy_writable_roots: Vec::new(), }; let core_policy = v2_policy.to_core(); assert_eq!( core_policy, codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { - writable_roots: vec![], network_access: true, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, @@ -2060,10 +2059,9 @@ fn sandbox_policy_deserializes_legacy_read_only_full_access_field() { #[test] fn sandbox_policy_deserializes_legacy_workspace_write_full_access_field() { - let writable_root = absolute_path("/workspace"); let policy = serde_json::from_value::(json!({ "type": "workspaceWrite", - "writableRoots": [writable_root], + "writableRoots": [], "readOnlyAccess": { "type": "fullAccess" }, @@ -2075,14 +2073,38 @@ fn sandbox_policy_deserializes_legacy_workspace_write_full_access_field() { assert_eq!( policy, SandboxPolicy::WorkspaceWrite { - writable_roots: vec![absolute_path("/workspace")], network_access: true, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, + legacy_writable_roots: Vec::new(), } ); } +#[test] +fn sandbox_policy_deserializes_legacy_workspace_write_writable_roots_field() { + let writable_root = absolute_path("/workspace"); + let policy = serde_json::from_value::(json!({ + "type": "workspaceWrite", + "writableRoots": [writable_root], + "networkAccess": false, + "excludeTmpdirEnvVar": false, + "excludeSlashTmp": false + })) + .expect("workspace-write policy should accept legacy writableRoots field"); + assert_eq!(policy.legacy_writable_roots(), &[writable_root]); + + assert_eq!( + serde_json::to_value(policy).expect("policy should serialize"), + json!({ + "type": "workspaceWrite", + "networkAccess": false, + "excludeTmpdirEnvVar": false, + "excludeSlashTmp": false + }) + ); +} + #[test] fn sandbox_policy_rejects_legacy_read_only_restricted_access_field() { let err = serde_json::from_value::(json!({ @@ -3389,9 +3411,6 @@ fn thread_lifecycle_responses_default_missing_optional_fields() { assert_eq!(start.instruction_sources, Vec::::new()); assert_eq!(resume.instruction_sources, Vec::::new()); assert_eq!(fork.instruction_sources, Vec::::new()); - assert_eq!(start.permission_profile, None); - assert_eq!(resume.permission_profile, None); - assert_eq!(fork.permission_profile, None); assert_eq!(start.active_permission_profile, None); assert_eq!(resume.active_permission_profile, None); assert_eq!(fork.active_permission_profile, None); @@ -3419,6 +3438,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() { responsesapi_client_metadata: None, environments: None, cwd: None, + workspace_roots: None, approval_policy: None, approvals_reviewer: None, sandbox_policy: None, @@ -3436,6 +3456,20 @@ fn turn_start_params_preserve_explicit_null_service_tier() { assert_eq!(serialized_without_override.get("serviceTier"), None); } +#[test] +fn turn_start_permissions_uses_profile_id_string_shape() { + let params: TurnStartParams = serde_json::from_value(json!({ + "threadId": "thread-1", + "input": [], + "permissions": ":workspace" + })) + .expect("turn start params should deserialize"); + assert_eq!(params.permissions, Some(":workspace".to_string())); + + let serialized = serde_json::to_value(¶ms).expect("params should serialize"); + assert_eq!(serialized["permissions"], json!(":workspace")); +} + #[test] fn turn_start_params_round_trip_environments() { let cwd = test_absolute_path(); diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 458722b3a2..726ec0ac80 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -1,8 +1,6 @@ use super::ActivePermissionProfile; use super::ApprovalsReviewer; use super::AskForApproval; -use super::PermissionProfile; -use super::PermissionProfileSelectionParams; use super::SandboxMode; use super::SandboxPolicy; use super::Thread; @@ -107,6 +105,11 @@ pub struct ThreadStartParams { pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, + /// Optional workspace roots for this thread. Omitted uses the server's + /// configured roots, usually seeded from `cwd`. + #[experimental("thread/start.workspaceRoots")] + #[ts(optional = nullable)] + pub workspace_roots: Option>, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, @@ -116,12 +119,11 @@ pub struct ThreadStartParams { pub approvals_reviewer: Option, #[ts(optional = nullable)] pub sandbox: Option, - /// Named profile selection for this thread. Cannot be combined with - /// `sandbox`. Use bounded `modifications` for supported turn/thread - /// adjustments instead of replacing the full permissions profile. + /// Named permissions profile id for this new thread's initial permissions. + /// Cannot be combined with `sandbox`. #[experimental("thread/start.permissions")] #[ts(optional = nullable)] - pub permissions: Option, + pub permissions: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] @@ -195,6 +197,11 @@ pub struct ThreadStartResponse { pub model_provider: String, pub service_tier: Option, pub cwd: AbsolutePathBuf, + /// Workspace roots used to realize symbolic `:project_roots` permission + /// entries for this thread. + #[experimental("thread/start.workspaceRoots")] + #[serde(default)] + pub workspace_roots: Vec, /// Instruction source files currently loaded for this thread. #[serde(default)] pub instruction_sources: Vec, @@ -203,14 +210,8 @@ pub struct ThreadStartResponse { /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, /// Legacy sandbox policy retained for compatibility. Experimental clients - /// should prefer `permissionProfile` when they need exact runtime - /// permissions. + /// should prefer `activePermissionProfile` and `workspaceRoots`. pub sandbox: SandboxPolicy, - /// Full active permissions for this thread. `activePermissionProfile` - /// carries display/provenance metadata for this runtime profile. - #[experimental("thread/start.permissionProfile")] - #[serde(default)] - pub permission_profile: Option, /// Named or implicit built-in profile that produced the active /// permissions, when known. #[experimental("thread/start.activePermissionProfile")] @@ -264,6 +265,11 @@ pub struct ThreadResumeParams { pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, + /// Optional replacement workspace roots for the resumed thread. Omitted + /// preserves the persisted or configured roots. + #[experimental("thread/resume.workspaceRoots")] + #[ts(optional = nullable)] + pub workspace_roots: Option>, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, @@ -271,14 +277,16 @@ pub struct ThreadResumeParams { /// and subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, + /// Deprecated for resume. When present, the server treats this as a + /// compatibility spelling for selecting a matching named permissions + /// profile. #[ts(optional = nullable)] pub sandbox: Option, - /// Named profile selection for the resumed thread. Cannot be combined - /// with `sandbox`. Use bounded `modifications` for supported thread - /// adjustments instead of replacing the full permissions profile. + /// Named permissions profile id for the resumed thread. Cannot be combined + /// with `sandbox`. #[experimental("thread/resume.permissions")] #[ts(optional = nullable)] - pub permissions: Option, + pub permissions: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] @@ -310,6 +318,11 @@ pub struct ThreadResumeResponse { pub model_provider: String, pub service_tier: Option, pub cwd: AbsolutePathBuf, + /// Workspace roots used to realize symbolic `:project_roots` permission + /// entries for this thread. + #[experimental("thread/resume.workspaceRoots")] + #[serde(default)] + pub workspace_roots: Vec, /// Instruction source files currently loaded for this thread. #[serde(default)] pub instruction_sources: Vec, @@ -318,14 +331,8 @@ pub struct ThreadResumeResponse { /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, /// Legacy sandbox policy retained for compatibility. Experimental clients - /// should prefer `permissionProfile` when they need exact runtime - /// permissions. + /// should prefer `activePermissionProfile` and `workspaceRoots`. pub sandbox: SandboxPolicy, - /// Full active permissions for this thread. `activePermissionProfile` - /// carries display/provenance metadata for this runtime profile. - #[experimental("thread/resume.permissionProfile")] - #[serde(default)] - pub permission_profile: Option, /// Named or implicit built-in profile that produced the active /// permissions, when known. #[experimental("thread/resume.activePermissionProfile")] @@ -370,6 +377,11 @@ pub struct ThreadForkParams { pub service_tier: Option>, #[ts(optional = nullable)] pub cwd: Option, + /// Optional replacement workspace roots for the forked thread. Omitted + /// preserves the source thread roots when available. + #[experimental("thread/fork.workspaceRoots")] + #[ts(optional = nullable)] + pub workspace_roots: Option>, #[experimental(nested)] #[ts(optional = nullable)] pub approval_policy: Option, @@ -377,14 +389,16 @@ pub struct ThreadForkParams { /// and subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, + /// Deprecated for fork. When present, the server treats this as a + /// compatibility spelling for selecting a matching named permissions + /// profile. #[ts(optional = nullable)] pub sandbox: Option, - /// Named profile selection for the forked thread. Cannot be combined with - /// `sandbox`. Use bounded `modifications` for supported thread - /// adjustments instead of replacing the full permissions profile. + /// Named permissions profile id for the forked thread. Cannot be combined + /// with `sandbox`. #[experimental("thread/fork.permissions")] #[ts(optional = nullable)] - pub permissions: Option, + pub permissions: Option, #[ts(optional = nullable)] pub config: Option>, #[ts(optional = nullable)] @@ -419,6 +433,11 @@ pub struct ThreadForkResponse { pub model_provider: String, pub service_tier: Option, pub cwd: AbsolutePathBuf, + /// Workspace roots used to realize symbolic `:project_roots` permission + /// entries for this thread. + #[experimental("thread/fork.workspaceRoots")] + #[serde(default)] + pub workspace_roots: Vec, /// Instruction source files currently loaded for this thread. #[serde(default)] pub instruction_sources: Vec, @@ -427,14 +446,8 @@ pub struct ThreadForkResponse { /// Reviewer currently used for approval requests on this thread. pub approvals_reviewer: ApprovalsReviewer, /// Legacy sandbox policy retained for compatibility. Experimental clients - /// should prefer `permissionProfile` when they need exact runtime - /// permissions. + /// should prefer `activePermissionProfile` and `workspaceRoots`. pub sandbox: SandboxPolicy, - /// Full active permissions for this thread. `activePermissionProfile` - /// carries display/provenance metadata for this runtime profile. - #[experimental("thread/fork.permissionProfile")] - #[serde(default)] - pub permission_profile: Option, /// Named or implicit built-in profile that produced the active /// permissions, when known. #[experimental("thread/fork.activePermissionProfile")] diff --git a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs index 61a09bfbf5..7dff5363c9 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs @@ -1,6 +1,5 @@ use super::ApprovalsReviewer; use super::AskForApproval; -use super::PermissionProfileSelectionParams; use super::SandboxPolicy; use super::Turn; use codex_experimental_api_macros::ExperimentalApi; @@ -64,6 +63,11 @@ pub struct TurnStartParams { /// Override the working directory for this turn and subsequent turns. #[ts(optional = nullable)] pub cwd: Option, + /// Replace the workspace roots for this turn and subsequent turns. Omitted + /// preserves the current roots. + #[experimental("turn/start.workspaceRoots")] + #[ts(optional = nullable)] + pub workspace_roots: Option>, /// Override the approval policy for this turn and subsequent turns. #[experimental(nested)] #[ts(optional = nullable)] @@ -72,16 +76,16 @@ pub struct TurnStartParams { /// subsequent turns. #[ts(optional = nullable)] pub approvals_reviewer: Option, - /// Override the sandbox policy for this turn and subsequent turns. + /// Deprecated for turns. When present, the server treats this as a + /// compatibility spelling for selecting a matching named permissions + /// profile. #[ts(optional = nullable)] pub sandbox_policy: Option, - /// Select a named permissions profile for this turn and subsequent turns. - /// Cannot be combined with `sandboxPolicy`. Use bounded `modifications` - /// for supported turn adjustments instead of replacing the full - /// permissions profile. + /// Select a named permissions profile id for this turn and subsequent + /// turns. Cannot be combined with `sandboxPolicy`. #[experimental("turn/start.permissions")] #[ts(optional = nullable)] - pub permissions: Option, + pub permissions: Option, /// Override the model for this turn and subsequent turns. #[ts(optional = nullable)] pub model: Option, diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index f655f65b91..4ec50dc20f 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -585,7 +585,7 @@ pub async fn run_main_with_transport_options( }); } if let Some(warning) = - codex_core::config::system_bwrap_warning(config.permissions.permission_profile.get()) + codex_core::config::system_bwrap_warning(config.permissions.permission_profile_ref()) { config_warnings.push(ConfigWarningNotification { summary: warning, diff --git a/codex-rs/app-server/src/request_processors/command_exec_processor.rs b/codex-rs/app-server/src/request_processors/command_exec_processor.rs index 3236a67627..f3729b2aeb 100644 --- a/codex-rs/app-server/src/request_processors/command_exec_processor.rs +++ b/codex-rs/app-server/src/request_processors/command_exec_processor.rs @@ -164,7 +164,7 @@ impl CommandExecRequestProcessor { let started_network_proxy = match self.config.permissions.network.as_ref() { Some(spec) => match spec .start_proxy( - self.config.permissions.permission_profile.get(), + self.config.permissions.permission_profile_ref(), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, @@ -200,7 +200,8 @@ impl CommandExecRequestProcessor { } else { ExecCapturePolicy::ShellTool }; - let sandbox_cwd = if permission_profile.is_some() { + let has_request_permission_profile = permission_profile.is_some(); + let sandbox_cwd = if has_request_permission_profile { cwd.clone() } else { self.config.cwd.clone() @@ -243,28 +244,20 @@ impl CommandExecRequestProcessor { ); self.config .permissions - .permission_profile + .permission_profile_constraint() .can_set(&effective_permission_profile) .map_err(|err| invalid_request(format!("invalid permission profile: {err}")))?; effective_permission_profile - } else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) { + } else if let Some(policy) = sandbox_policy { + let legacy_policy = policy.to_core(); self.config .permissions - .can_set_legacy_sandbox_policy(&policy, &sandbox_cwd) + .can_set_legacy_sandbox_policy(&legacy_policy, &sandbox_cwd) .map_err(|err| invalid_request(format!("invalid sandbox policy: {err}")))?; - let file_system_sandbox_policy = - codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd); - let network_sandbox_policy = - codex_protocol::permissions::NetworkSandboxPolicy::from(&policy); - let permission_profile = - codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement( - codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(&policy), - &file_system_sandbox_policy, - network_sandbox_policy, - ); + let permission_profile = policy.to_permission_profile_for_cwd(&sandbox_cwd); self.config .permissions - .permission_profile + .permission_profile_constraint() .can_set(&permission_profile) .map_err(|err| invalid_request(format!("invalid sandbox policy: {err}")))?; permission_profile @@ -283,10 +276,16 @@ impl CommandExecRequestProcessor { None => None, }; + let workspace_roots = if has_request_permission_profile { + vec![sandbox_cwd.clone()] + } else { + self.config.workspace_roots.clone() + }; let exec_request = codex_core::exec::build_exec_request( exec_params, &effective_permission_profile, &sandbox_cwd, + &workspace_roots, &codex_linux_sandbox_exe, use_legacy_landlock, ) diff --git a/codex-rs/cli/src/debug_sandbox.rs b/codex-rs/cli/src/debug_sandbox.rs index 2722f69849..895ac076fe 100644 --- a/codex-rs/cli/src/debug_sandbox.rs +++ b/codex-rs/cli/src/debug_sandbox.rs @@ -229,7 +229,7 @@ async fn run_command_under_sandbox( let network_proxy = match config.permissions.network.as_ref() { Some(spec) => Some( spec.start_proxy( - config.permissions.permission_profile.get(), + config.permissions.permission_profile_ref(), /*policy_decider*/ None, /*blocked_request_observer*/ None, managed_network_requirements_enabled, @@ -284,7 +284,7 @@ async fn run_command_under_sandbox( let args = create_linux_sandbox_command_args_for_permission_profile( command, cwd.as_path(), - &config.permissions.permission_profile(), + config.permissions.permission_profile_ref(), sandbox_policy_cwd.as_path(), use_legacy_landlock, allow_network_for_proxy(managed_network_requirements_enabled), @@ -769,6 +769,16 @@ mod tests { Ok(()) } + fn workspace_write_policy_for_codex_home( + codex_home: &TempDir, + ) -> codex_protocol::permissions::FileSystemSandboxPolicy { + let memories_root = AbsolutePathBuf::try_from(codex_home.path().join("memories")) + .expect("codex home tempdir should be absolute"); + codex_protocol::models::PermissionProfile::workspace_write() + .file_system_sandbox_policy() + .with_additional_legacy_workspace_writable_roots(std::slice::from_ref(&memories_root)) + } + #[tokio::test] async fn debug_sandbox_honors_active_permission_profiles() -> anyhow::Result<()> { let codex_home = TempDir::new()?; @@ -947,8 +957,7 @@ mod tests { assert_eq!( config.permissions.file_system_sandbox_policy(), - codex_protocol::models::PermissionProfile::workspace_write() - .file_system_sandbox_policy() + workspace_write_policy_for_codex_home(&codex_home) ); Ok(()) @@ -980,8 +989,7 @@ mod tests { assert_eq!( config.permissions.file_system_sandbox_policy(), - codex_protocol::models::PermissionProfile::workspace_write() - .file_system_sandbox_policy() + workspace_write_policy_for_codex_home(&codex_home) ); Ok(()) diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 8bf3148ba8..2dccad1fca 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -2144,7 +2144,6 @@ allowed_approvals_reviewers = ["user"] let config: ConfigRequirementsToml = from_str(toml_str)?; let requirements: ConfigRequirements = with_unknown_source(config).try_into()?; - let root = if cfg!(windows) { "C:\\repo" } else { "/repo" }; assert!( requirements .permission_profile @@ -2154,7 +2153,6 @@ allowed_approvals_reviewers = ["user"] .is_ok() ); let workspace_write_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?], network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, @@ -2265,9 +2263,7 @@ allowed_approvals_reviewers = ["user"] ); let requirements = ConfigRequirements::try_from(requirements_with_sources)?; - let root = if cfg!(windows) { "C:\\repo" } else { "/repo" }; let workspace_write_policy = SandboxPolicy::WorkspaceWrite { - writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?], network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, diff --git a/codex-rs/config/src/config_toml.rs b/codex-rs/config/src/config_toml.rs index 06993e046a..53a7c0f6d4 100644 --- a/codex-rs/config/src/config_toml.rs +++ b/codex-rs/config/src/config_toml.rs @@ -760,7 +760,7 @@ impl ConfigToml { SandboxMode::ReadOnly => PermissionProfile::read_only(), SandboxMode::WorkspaceWrite => match self.sandbox_workspace_write.as_ref() { Some(SandboxWorkspaceWrite { - writable_roots, + writable_roots: _, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, @@ -771,7 +771,7 @@ impl ConfigToml { NetworkSandboxPolicy::Restricted }; PermissionProfile::workspace_write_with( - writable_roots, + &[], network_policy, *exclude_tmpdir_env_var, *exclude_slash_tmp, diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index c110d30f66..0680429237 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -59,6 +59,7 @@ pub struct ThreadConfigSnapshot { pub permission_profile: PermissionProfile, pub active_permission_profile: Option, pub cwd: AbsolutePathBuf, + pub workspace_roots: Vec, pub ephemeral: bool, pub reasoning_effort: Option, pub personality: Option, @@ -68,11 +69,15 @@ pub struct ThreadConfigSnapshot { impl ThreadConfigSnapshot { pub fn sandbox_policy(&self) -> SandboxPolicy { - let file_system_sandbox_policy = self.permission_profile.file_system_sandbox_policy(); + let permission_profile = self + .permission_profile + .clone() + .materialize_project_roots_with_workspace_roots(&self.workspace_roots); + let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy(); codex_sandboxing::compatibility_sandbox_policy_for_permission_profile( - &self.permission_profile, + &permission_profile, &file_system_sandbox_policy, - self.permission_profile.network_sandbox_policy(), + permission_profile.network_sandbox_policy(), self.cwd.as_path(), ) } @@ -82,6 +87,7 @@ impl ThreadConfigSnapshot { #[derive(Clone, Default)] pub struct CodexThreadTurnContextOverrides { pub cwd: Option, + pub workspace_roots: Option>, pub approval_policy: Option, pub approvals_reviewer: Option, pub sandbox_policy: Option, @@ -241,8 +247,26 @@ impl CodexThread { &self, overrides: CodexThreadTurnContextOverrides, ) -> ConstraintResult<()> { + let updates = self.turn_context_updates_from_overrides(overrides).await; + self.codex.session.validate_settings(&updates).await + } + + /// Apply persistent thread context overrides immediately. + pub async fn update_turn_context_overrides( + &self, + overrides: CodexThreadTurnContextOverrides, + ) -> ConstraintResult<()> { + let updates = self.turn_context_updates_from_overrides(overrides).await; + self.codex.session.update_settings(updates).await + } + + async fn turn_context_updates_from_overrides( + &self, + overrides: CodexThreadTurnContextOverrides, + ) -> SessionSettingsUpdate { let CodexThreadTurnContextOverrides { cwd, + workspace_roots, approval_policy, approvals_reviewer, sandbox_policy, @@ -266,8 +290,9 @@ impl CodexThread { .with_updates(model, effort, /*developer_instructions*/ None) }; - let updates = SessionSettingsUpdate { + SessionSettingsUpdate { cwd, + workspace_roots, approval_policy, approvals_reviewer, sandbox_policy, @@ -279,8 +304,7 @@ impl CodexThread { service_tier, personality, ..Default::default() - }; - self.codex.session.validate_settings(&updates).await + } } /// Use sparingly: this is intended to be removed soon. diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 6296d13886..a3ae9a466f 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -527,9 +527,10 @@ writable_roots = ["~/code"] let expected_root = AbsolutePathBuf::from_absolute_path(home.join("code"))?; match &config.legacy_sandbox_policy() { - SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { + SandboxPolicy::WorkspaceWrite { .. } => { assert_eq!( - writable_roots + config + .workspace_roots .iter() .filter(|root| **root == expected_root) .count(), @@ -593,7 +594,6 @@ allowed_sandbox_modes = ["read-only"] .permission_profile .can_set(&PermissionProfile::from_legacy_sandbox_policy( &SandboxPolicy::WorkspaceWrite { - writable_roots: Vec::new(), network_access: false, exclude_tmpdir_env_var: false, exclude_slash_tmp: false, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 664040e2b0..093aa92d58 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -68,7 +68,6 @@ use codex_model_provider_info::WireApi; use codex_models_manager::bundled_models_response; use codex_protocol::config_types::ServiceTier; use codex_protocol::models::ActivePermissionProfile; -use codex_protocol::models::ActivePermissionProfileModification; use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::PermissionProfile; use codex_protocol::models::SandboxEnforcement; @@ -100,6 +99,14 @@ use std::path::Path; use std::time::Duration; use tempfile::TempDir; +fn materialized_file_system_sandbox_policy(config: &Config) -> FileSystemSandboxPolicy { + config + .permissions + .permission_profile() + .materialize_project_roots_with_workspace_roots(&config.workspace_roots) + .file_system_sandbox_policy() +} + fn stdio_mcp(command: &str) -> McpServerConfig { McpServerConfig { transport: McpServerTransportConfig::Stdio { @@ -1364,7 +1371,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std:: }, FileSystemSandboxEntry { path: FileSystemPath::Path { - path: memories_root.clone(), + path: memories_root, }, access: FileSystemAccessMode::Write, }, @@ -1373,7 +1380,6 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std:: assert_eq!( &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { - writable_roots: vec![memories_root], network_access: false, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, @@ -1550,7 +1556,6 @@ async fn permission_profile_override_applies_runtime_roots_to_legacy_projection( assert_eq!( &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { - writable_roots: vec![memories_root], network_access: false, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, @@ -1751,7 +1756,7 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); + let policy = materialized_file_system_sandbox_policy(&config); assert_eq!( config .permissions @@ -1772,7 +1777,7 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl } #[tokio::test] -async fn default_permissions_read_only_applies_additional_writable_roots_as_modifications() +async fn default_permissions_read_only_records_additional_writable_roots_as_workspace_roots() -> std::io::Result<()> { let codex_home = TempDir::new()?; let cwd = TempDir::new()?; @@ -1793,18 +1798,18 @@ async fn default_permissions_read_only_applies_additional_writable_roots_as_modi ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); - assert!( - policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()), - "expected additional writable root to modify :read-only, policy: {policy:?}" + let policy = materialized_file_system_sandbox_policy(&config); + assert_eq!( + policy, + PermissionProfile::read_only().file_system_sandbox_policy() + ); + assert_eq!( + config.workspace_roots, + vec![cwd.path().abs(), extra_root.clone()] ); assert_eq!( config.permissions.active_permission_profile(), - Some( - ActivePermissionProfile::new(":read-only").with_modifications(vec![ - ActivePermissionProfileModification::AdditionalWritableRoot { path: extra_root }, - ]) - ) + Some(ActivePermissionProfile::new(":read-only")) ); Ok(()) } @@ -1835,7 +1840,7 @@ async fn explicit_builtin_workspace_profile_ignores_legacy_workspace_write_setti ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); + let policy = materialized_file_system_sandbox_policy(&config); assert_eq!( config.permissions.network_sandbox_policy(), NetworkSandboxPolicy::Restricted @@ -1875,7 +1880,7 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std:: ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); + let policy = materialized_file_system_sandbox_policy(&config); assert_eq!( config .permissions @@ -1943,7 +1948,7 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); + let policy = materialized_file_system_sandbox_policy(&config); assert!( policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()), "expected implicit :workspace to preserve sandbox_workspace_write.writable_roots, policy: {policy:?}" @@ -1960,12 +1965,11 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se ); match config.legacy_sandbox_policy() { SandboxPolicy::WorkspaceWrite { - writable_roots, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, } => { - assert!(writable_roots.contains(&extra_root)); + assert!(config.workspace_roots.contains(&extra_root)); assert!(network_access); assert!(exclude_tmpdir_env_var); assert!(!exclude_slash_tmp); @@ -2009,7 +2013,7 @@ async fn implicit_builtin_workspace_profile_preserves_add_dir_metadata_carveouts ) .await?; - let policy = config.permissions.file_system_sandbox_policy(); + let policy = materialized_file_system_sandbox_policy(&config); let extra_root = extra_root.path().abs(); assert!( policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()), @@ -2184,9 +2188,6 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root() ) .await?; - let memories_root = AbsolutePathBuf::from_absolute_path(std::fs::canonicalize( - codex_home.path().join("memories"), - )?)?; assert!( config .permissions @@ -2196,7 +2197,6 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root() assert_eq!( &config.legacy_sandbox_policy(), &SandboxPolicy::WorkspaceWrite { - writable_roots: vec![external_write_path, memories_root], network_access: false, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, @@ -2871,7 +2871,6 @@ trust_level = "trusted" assert_eq!( resolution, SandboxPolicy::WorkspaceWrite { - writable_roots: vec![writable_root.clone()], network_access: false, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, @@ -2911,7 +2910,6 @@ exclude_slash_tmp = true assert_eq!( resolution, SandboxPolicy::WorkspaceWrite { - writable_roots: vec![writable_root], network_access: false, exclude_tmpdir_env_var: true, exclude_slash_tmp: true, @@ -2975,13 +2973,21 @@ exclude_slash_tmp = true NetworkSandboxPolicy::from(&sandbox_policy), "case `{name}` should preserve network semantics from legacy config" ); - assert_eq!( - file_system_policy - .to_legacy_sandbox_policy(network_policy, cwd.path()) - .unwrap_or_else(|err| panic!("case `{name}` should round-trip: {err}")), - sandbox_policy, - "case `{name}` should preserve its legacy compatibility projection" - ); + let direct_legacy_projection = + file_system_policy.to_legacy_sandbox_policy(network_policy, cwd.path()); + if name == "workspace-write" && !cfg!(target_os = "windows") { + assert!( + direct_legacy_projection.is_err(), + "case `{name}` should require the compatibility projection for split workspace roots" + ); + } else { + assert_eq!( + direct_legacy_projection + .unwrap_or_else(|err| panic!("case `{name}` should round-trip: {err}")), + sandbox_policy, + "case `{name}` should preserve its legacy compatibility projection" + ); + } match name.as_str() { "danger-full-access" | "read-only" => { @@ -3023,14 +3029,19 @@ exclude_slash_tmp = true }) ); assert!( - file_system_policy + config.workspace_roots.contains(&extra_root), + "case `{name}` should store legacy writable roots as thread workspace roots" + ); + assert!( + materialized_file_system_sandbox_policy(&config) .entries .contains(&FileSystemSandboxEntry { path: FileSystemPath::Path { path: extra_root.clone(), }, access: FileSystemAccessMode::Write, - }) + }), + "case `{name}` should materialize workspace roots into runtime write access" ); for subpath in [".git", ".agents", ".codex"] { assert!( @@ -3773,14 +3784,15 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result< } } else { match &config.legacy_sandbox_policy() { - SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { + SandboxPolicy::WorkspaceWrite { .. } => { assert_eq!( - writable_roots + config + .workspace_roots .iter() .filter(|root| **root == expected_backend) .count(), 1, - "expected single writable root entry for {}", + "expected single workspace root entry for {}", expected_backend.display() ); } @@ -3840,13 +3852,16 @@ async fn workspace_write_always_includes_memories_root_once() -> std::io::Result "expected memories root directory to exist at {}", memories_root.display() ); - let expected_memories_root = memories_root.abs(); + let expected_memories_root = + AbsolutePathBuf::from_absolute_path(std::fs::canonicalize(&memories_root)?)?; match &config.legacy_sandbox_policy() { - SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { + SandboxPolicy::WorkspaceWrite { .. } => { + let writable_roots = materialized_file_system_sandbox_policy(&config) + .get_writable_roots_with_cwd(config.cwd.as_path()); assert_eq!( writable_roots .iter() - .filter(|root| **root == expected_memories_root) + .filter(|root| root.root == expected_memories_root) .count(), 1, "expected single writable root entry for {}", @@ -7294,6 +7309,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> { user_instructions: None, notify: None, cwd: fixture.cwd(), + workspace_roots: vec![fixture.cwd()], cli_auth_credentials_store_mode: Default::default(), mcp_servers: Constrained::allow_any(HashMap::new()), mcp_oauth_credentials_store_mode: resolve_mcp_oauth_credentials_store_mode( @@ -7741,6 +7757,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> { user_instructions: None, notify: None, cwd: fixture.cwd(), + workspace_roots: vec![fixture.cwd()], cli_auth_credentials_store_mode: Default::default(), mcp_servers: Constrained::allow_any(HashMap::new()), mcp_oauth_credentials_store_mode: resolve_mcp_oauth_credentials_store_mode( @@ -7902,6 +7919,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> { user_instructions: None, notify: None, cwd: fixture.cwd(), + workspace_roots: vec![fixture.cwd()], cli_auth_credentials_store_mode: Default::default(), mcp_servers: Constrained::allow_any(HashMap::new()), mcp_oauth_credentials_store_mode: resolve_mcp_oauth_credentials_store_mode( @@ -8048,6 +8066,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> { user_instructions: None, notify: None, cwd: fixture.cwd(), + workspace_roots: vec![fixture.cwd()], cli_auth_credentials_store_mode: Default::default(), mcp_servers: Constrained::allow_any(HashMap::new()), mcp_oauth_credentials_store_mode: resolve_mcp_oauth_credentials_store_mode( @@ -9056,7 +9075,7 @@ async fn requirements_web_search_mode_overrides_danger_full_access_default() -> assert_eq!( resolve_web_search_mode_for_turn( &config.web_search_mode, - &config.permissions.permission_profile(), + config.permissions.permission_profile_ref(), ), WebSearchMode::Cached, ); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 21bad4ee5e..4d9211a23a 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -89,7 +89,6 @@ use codex_protocol::config_types::WebSearchConfig; use codex_protocol::config_types::WebSearchMode; use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::models::ActivePermissionProfile; -use codex_protocol::models::ActivePermissionProfileModification; use codex_protocol::models::PermissionProfile; use codex_protocol::models::SandboxEnforcement; use codex_protocol::openai_models::ModelsResponse; @@ -242,10 +241,10 @@ pub struct Permissions { pub approval_policy: Constrained, /// Canonical effective runtime permissions after config requirements and /// runtime readable-root additions have been applied. - pub permission_profile: Constrained, + permission_profile: Constrained, /// Named or implicit built-in profile selected by config, rather than an /// ad-hoc override. - pub active_permission_profile: Option, + active_permission_profile: Option, /// Effective network configuration applied to all spawned processes. pub network: Option, /// Whether the model may request a login shell for shell-based tools. @@ -267,6 +266,35 @@ pub struct Permissions { } impl Permissions { + /// Build permissions from the two constrained values that are required for + /// a minimal in-process configuration. + pub fn from_approval_and_profile( + approval_policy: Constrained, + permission_profile: Constrained, + ) -> Self { + Self { + approval_policy, + permission_profile, + active_permission_profile: None, + network: None, + allow_login_shell: true, + shell_environment_policy: ShellEnvironmentPolicy::default(), + windows_sandbox_mode: None, + windows_sandbox_private_desktop: true, + } + } + + /// Borrow the constrained canonical profile. This is for code that must + /// preserve or inspect the active requirements wrapper without mutating it. + pub fn permission_profile_constraint(&self) -> &Constrained { + &self.permission_profile + } + + /// Borrow the canonical effective runtime permissions without cloning. + pub fn permission_profile_ref(&self) -> &PermissionProfile { + self.permission_profile.get() + } + /// Effective runtime permissions after config requirements and runtime /// readable-root additions have been applied. pub fn permission_profile(&self) -> PermissionProfile { @@ -278,6 +306,45 @@ impl Permissions { self.active_permission_profile.clone() } + /// Replace the full constrained profile value and clear any profile-name + /// sidecar because the new constraint may no longer match that name. + pub fn replace_permission_profile_constraint( + &mut self, + permission_profile: Constrained, + ) { + self.permission_profile = permission_profile; + self.active_permission_profile = None; + } + + /// Replace the full constrained profile value and preserve the active + /// profile sidecar when the caller has already validated both together. + pub fn replace_permission_profile_constraint_with_active_profile( + &mut self, + permission_profile: Constrained, + active_permission_profile: Option, + ) { + self.permission_profile = permission_profile; + self.active_permission_profile = active_permission_profile; + } + + /// Record the active profile id only if it still describes the current + /// canonical profile. + pub fn set_active_permission_profile_for_current_profile( + &mut self, + active_permission_profile: Option, + expected_permission_profile: Option<&PermissionProfile>, + ) { + self.active_permission_profile = + match (active_permission_profile, expected_permission_profile) { + (Some(active_permission_profile), Some(expected_permission_profile)) + if self.permission_profile.get() == expected_permission_profile => + { + Some(active_permission_profile) + } + _ => None, + }; + } + /// Effective filesystem sandbox policy derived from the canonical profile. pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy { self.permission_profile.get().file_system_sandbox_policy() @@ -579,6 +646,10 @@ pub struct Config { /// layer are resolved against this path. pub cwd: AbsolutePathBuf, + /// Absolute roots that define the writable project/workspace set for + /// symbolic `:project_roots` permission entries. + pub workspace_roots: Vec, + /// Preferred store for CLI auth credentials. /// file (default): Use a file in the Codex home directory. /// keyring: Use an OS-specific keyring service. @@ -1860,6 +1931,11 @@ fn apply_managed_filesystem_constraints( } } +fn dedupe_absolute_paths(paths: &mut Vec) { + let mut seen = HashSet::new(); + paths.retain(|path| seen.insert(path.clone())); +} + /// Optional overrides for user configuration (e.g., from CLI flags). #[derive(Default, Debug, Clone)] pub struct ConfigOverrides { @@ -1888,6 +1964,9 @@ pub struct ConfigOverrides { pub ephemeral: Option, /// Additional directories that should be treated as writable roots for this session. pub additional_writable_roots: Vec, + /// Explicit workspace roots for this session. When set, this is the full + /// root list rather than an additive override. + pub workspace_roots: Option>, } /// Resolves the OSS provider from CLI override, profile config, or global config. @@ -2169,6 +2248,7 @@ impl Config { tools_web_search_request: override_tools_web_search_request, ephemeral, additional_writable_roots, + workspace_roots: workspace_roots_override, } = overrides; if sandbox_mode.is_some() && permission_profile.is_some() { @@ -2257,11 +2337,10 @@ impl Config { } } }))?; - let mut additional_writable_roots: Vec = additional_writable_roots + let requested_additional_writable_roots: Vec = additional_writable_roots .into_iter() .map(|path| AbsolutePathBuf::resolve_path_against_base(path, resolved_cwd.as_path())) .collect(); - let requested_additional_writable_roots = additional_writable_roots.clone(); let repo_root = resolve_root_git_project_for_trust(fs, &resolved_cwd).await; let active_project = cfg .get_active_project( @@ -2303,12 +2382,7 @@ impl Config { }; let memories_root = memory_root(&codex_home); std::fs::create_dir_all(&memories_root)?; - if !additional_writable_roots - .iter() - .any(|existing| existing == &memories_root) - { - additional_writable_roots.push(memories_root); - } + let internal_writable_roots = vec![memories_root]; let profiles_are_active = default_permissions_override.is_some() || matches!( @@ -2318,6 +2392,31 @@ impl Config { || permission_config_syntax.is_none(); let using_implicit_builtin_profile = permission_config_syntax.is_none() && default_permissions.is_none(); + let should_seed_legacy_workspace_roots = + default_permissions.is_none() + && matches!( + permission_config_syntax, + None | Some(PermissionConfigSyntax::Legacy) + ); + let mut workspace_roots = match workspace_roots_override { + Some(workspace_roots) => workspace_roots + .into_iter() + .map(|path| { + AbsolutePathBuf::resolve_path_against_base(path, resolved_cwd.as_path()) + }) + .collect(), + None => { + let mut workspace_roots = vec![resolved_cwd.clone()]; + workspace_roots.extend(requested_additional_writable_roots.clone()); + if should_seed_legacy_workspace_roots + && let Some(sandbox_workspace_write) = cfg.sandbox_workspace_write.as_ref() + { + workspace_roots.extend(sandbox_workspace_write.writable_roots.clone()); + } + workspace_roots + } + }; + dedupe_absolute_paths(&mut workspace_roots); let ( mut configured_network_proxy_config, permission_profile, @@ -2354,10 +2453,7 @@ impl Config { ); if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) { file_system_sandbox_policy = file_system_sandbox_policy - .with_additional_writable_roots( - resolved_cwd.as_path(), - &additional_writable_roots, - ); + .with_additional_legacy_workspace_writable_roots(&internal_writable_roots); permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( permission_profile.enforcement(), &file_system_sandbox_policy, @@ -2408,28 +2504,8 @@ impl Config { resolved_cwd.as_path(), ); if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) { - file_system_sandbox_policy = if using_implicit_builtin_profile { - file_system_sandbox_policy - .with_additional_legacy_workspace_writable_roots( - &additional_writable_roots, - ) - } else { - file_system_sandbox_policy.with_additional_writable_roots( - resolved_cwd.as_path(), - &additional_writable_roots, - ) - }; - permission_profile = PermissionProfile::from_runtime_permissions( - &file_system_sandbox_policy, - network_sandbox_policy, - ); - } else if matches!(permission_profile, PermissionProfile::Managed { .. }) - && !requested_additional_writable_roots.is_empty() - { - file_system_sandbox_policy = file_system_sandbox_policy.with_additional_writable_roots( - resolved_cwd.as_path(), - &requested_additional_writable_roots, - ); + file_system_sandbox_policy = file_system_sandbox_policy + .with_additional_legacy_workspace_writable_roots(&internal_writable_roots); permission_profile = PermissionProfile::from_runtime_permissions( &file_system_sandbox_policy, network_sandbox_policy, @@ -2446,22 +2522,7 @@ impl Config { // when doing so would lose roots, network, or tmp settings. None } else { - let active_permission_profile = if !requested_additional_writable_roots.is_empty() - && matches!(permission_profile, PermissionProfile::Managed { .. }) - { - ActivePermissionProfile::new(default_permissions).with_modifications( - requested_additional_writable_roots - .iter() - .cloned() - .map(|path| { - ActivePermissionProfileModification::AdditionalWritableRoot { path } - }) - .collect(), - ) - } else { - ActivePermissionProfile::new(default_permissions) - }; - Some(active_permission_profile) + Some(ActivePermissionProfile::new(default_permissions)) }; ( configured_network_proxy_config, @@ -2500,25 +2561,25 @@ impl Config { } let (mut file_system_sandbox_policy, network_sandbox_policy) = permission_profile.to_runtime_permissions(); - // `additional_writable_roots` is a legacy workspace-write knob. It - // only applies when the derived managed profile has workspace-style - // write access to the project roots; read-only, disabled, external, - // and future non-workspace profiles must not silently grow extra - // write access. + // Internal writable roots only apply when the derived managed + // profile has workspace-style write access to the project roots; + // read-only, disabled, external, and future non-workspace profiles + // must not silently grow extra write access. + let materialized_file_system_sandbox_policy = permission_profile + .materialize_project_roots_with_workspace_roots(&workspace_roots) + .file_system_sandbox_policy(); if matches!(permission_profile.enforcement(), SandboxEnforcement::Managed) - && file_system_sandbox_policy.can_write_path_with_cwd( + && materialized_file_system_sandbox_policy.can_write_path_with_cwd( resolved_cwd.as_path(), resolved_cwd.as_path(), ) - && !file_system_sandbox_policy.has_full_disk_write_access() + && !materialized_file_system_sandbox_policy.has_full_disk_write_access() { - // Keep legacy behavior for extra writable roots while storing - // the result as the canonical permission profile. Explicit - // extra roots are concrete paths, so their metadata carveouts - // are also concrete rather than symbolic `:project_roots` - // entries. + // Keep Codex runtime write access while storing the result as + // the canonical permission profile. Workspace roots themselves + // are held separately on the thread. file_system_sandbox_policy = file_system_sandbox_policy - .with_additional_legacy_workspace_writable_roots(&additional_writable_roots); + .with_additional_legacy_workspace_writable_roots(&internal_writable_roots); permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement( permission_profile.enforcement(), &file_system_sandbox_policy, @@ -3016,6 +3077,7 @@ impl Config { model_provider_id, model_provider, cwd: resolved_cwd, + workspace_roots, startup_warnings, permissions: Permissions { approval_policy: constrained_approval_policy.value, @@ -3306,7 +3368,7 @@ impl Config { pub fn managed_network_requirements_enabled(&self) -> bool { !matches!( - self.permissions.permission_profile.get(), + self.permissions.permission_profile_ref(), PermissionProfile::Disabled ) && self .config_layer_stack diff --git a/codex-rs/core/src/config/permissions.rs b/codex-rs/core/src/config/permissions.rs index 6b6021ad34..eb4da0d447 100644 --- a/codex-rs/core/src/config/permissions.rs +++ b/codex-rs/core/src/config/permissions.rs @@ -68,12 +68,12 @@ pub(crate) fn builtin_permission_profile( BUILT_IN_READ_ONLY_PROFILE => Some(PermissionProfile::read_only()), BUILT_IN_WORKSPACE_PROFILE => Some(match workspace_write { Some(SandboxWorkspaceWrite { - writable_roots, + writable_roots: _, network_access, exclude_tmpdir_env_var, exclude_slash_tmp, }) => PermissionProfile::workspace_write_with( - writable_roots, + &[], if *network_access { NetworkSandboxPolicy::Enabled } else { diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index 0ccd6c33a7..4f30c644c0 100644 --- a/codex-rs/core/src/context/permissions_instructions.rs +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -30,6 +30,8 @@ const SANDBOX_MODE_DANGER_FULL_ACCESS: &str = const SANDBOX_MODE_WORKSPACE_WRITE: &str = include_str!("prompts/permissions/sandbox_mode/workspace_write.md"); const SANDBOX_MODE_READ_ONLY: &str = include_str!("prompts/permissions/sandbox_mode/read_only.md"); +const MAX_WRITABLE_ROOTS_IN_PROMPT: usize = 8; +const MAX_WRITABLE_ROOT_LABEL_CHARS: usize = 160; static SANDBOX_MODE_DANGER_FULL_ACCESS_TEMPLATE: LazyLock