mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
permissions: move workspace roots onto thread state
This commit is contained in:
Generated
+3
@@ -2708,6 +2708,7 @@ dependencies = [
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-cli",
|
||||
"codex-utils-oss",
|
||||
"codex-utils-sandbox-summary",
|
||||
"core_test_support",
|
||||
"libc",
|
||||
"opentelemetry",
|
||||
@@ -3525,6 +3526,7 @@ dependencies = [
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-path",
|
||||
"codex-utils-string",
|
||||
"pretty_assertions",
|
||||
@@ -3717,6 +3719,7 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-rollout",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-path",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
+5
-69
@@ -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",
|
||||
@@ -3144,13 +3085,6 @@
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicyType",
|
||||
"type": "string"
|
||||
},
|
||||
"writableRoots": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3417,7 +3351,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": [
|
||||
@@ -3828,7 +3763,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": [
|
||||
@@ -4208,7 +4144,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.",
|
||||
|
||||
+8
-105
@@ -5607,14 +5607,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` 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": [
|
||||
@@ -5622,31 +5614,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",
|
||||
@@ -11740,31 +11707,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": {
|
||||
@@ -11776,40 +11718,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",
|
||||
@@ -14501,13 +14409,6 @@
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicyType",
|
||||
"type": "string"
|
||||
},
|
||||
"writableRoots": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -15495,7 +15396,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": [
|
||||
@@ -15571,7 +15473,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": [
|
||||
@@ -16998,7 +16900,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": [
|
||||
@@ -17063,7 +16966,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": [
|
||||
@@ -17371,7 +17274,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": [
|
||||
@@ -18040,7 +17943,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.",
|
||||
|
||||
+8
-105
@@ -143,14 +143,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` 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",
|
||||
@@ -8289,31 +8256,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": {
|
||||
@@ -8325,40 +8267,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",
|
||||
@@ -11050,13 +10958,6 @@
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicyType",
|
||||
"type": "string"
|
||||
},
|
||||
"writableRoots": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -13319,7 +13220,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": [
|
||||
@@ -13395,7 +13297,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": [
|
||||
@@ -14822,7 +14724,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": [
|
||||
@@ -14887,7 +14790,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": [
|
||||
@@ -15195,7 +15098,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": [
|
||||
@@ -15864,7 +15767,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.",
|
||||
|
||||
@@ -441,13 +441,6 @@
|
||||
],
|
||||
"title": "WorkspaceWriteSandboxPolicyType",
|
||||
"type": "string"
|
||||
},
|
||||
"writableRoots": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
+1
-366
@@ -18,14 +18,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` 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": [
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` 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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` 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": [
|
||||
|
||||
@@ -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.",
|
||||
|
||||
+1
-7
@@ -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<ActivePermissionProfileModification>, };
|
||||
extends: string | null, };
|
||||
|
||||
-6
@@ -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, };
|
||||
-6
@@ -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, };
|
||||
-6
@@ -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<PermissionProfileModificationParams> | null, };
|
||||
@@ -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<AbsolutePathBuf>, 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, };
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -16,7 +16,6 @@ instructionSources: Array<AbsolutePathBuf>, 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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -16,7 +16,6 @@ instructionSources: Array<AbsolutePathBuf>, 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};
|
||||
|
||||
@@ -16,7 +16,6 @@ instructionSources: Array<AbsolutePathBuf>, 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};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -223,6 +223,7 @@ macro_rules! client_request_definitions {
|
||||
|
||||
/// Typed response from the server to the client.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[serde(tag = "method", rename_all = "camelCase")]
|
||||
pub enum ClientResponse {
|
||||
$(
|
||||
@@ -2294,11 +2295,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,
|
||||
},
|
||||
@@ -2338,13 +2339,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
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
/// Bounded user-requested modifications applied on top of the named
|
||||
/// profile, if any.
|
||||
#[serde(default)]
|
||||
pub modifications: Vec<ActivePermissionProfileModification>,
|
||||
}
|
||||
|
||||
#[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<CoreActivePermissionProfileModification> for ActivePermissionProfileModification {
|
||||
fn from(value: CoreActivePermissionProfileModification) -> Self {
|
||||
match value {
|
||||
CoreActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
Self::AdditionalWritableRoot { path }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivePermissionProfileModification> for CoreActivePermissionProfileModification {
|
||||
fn from(value: ActivePermissionProfileModification) -> Self {
|
||||
match value {
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
Self::AdditionalWritableRoot { path }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreActivePermissionProfile> for ActivePermissionProfile {
|
||||
@@ -479,11 +443,6 @@ impl From<CoreActivePermissionProfile> for ActivePermissionProfile {
|
||||
Self {
|
||||
id: value.id,
|
||||
extends: value.extends,
|
||||
modifications: value
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(ActivePermissionProfileModification::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,42 +452,10 @@ impl From<ActivePermissionProfile> 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<Vec<PermissionProfileModificationParams>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[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<AbsolutePathBuf>,
|
||||
#[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<AbsolutePathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
|
||||
@@ -752,15 +723,14 @@ impl From<codex_protocol::protocol::SandboxPolicy> 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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2020,17 +2020,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,
|
||||
@@ -2061,10 +2060,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::<SandboxPolicy>(json!({
|
||||
"type": "workspaceWrite",
|
||||
"writableRoots": [writable_root],
|
||||
"writableRoots": [],
|
||||
"readOnlyAccess": {
|
||||
"type": "fullAccess"
|
||||
},
|
||||
@@ -2076,14 +2074,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::<SandboxPolicy>(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::<SandboxPolicy>(json!({
|
||||
@@ -3439,9 +3461,6 @@ fn thread_lifecycle_responses_default_missing_optional_fields() {
|
||||
assert_eq!(start.instruction_sources, Vec::<AbsolutePathBuf>::new());
|
||||
assert_eq!(resume.instruction_sources, Vec::<AbsolutePathBuf>::new());
|
||||
assert_eq!(fork.instruction_sources, Vec::<AbsolutePathBuf>::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);
|
||||
@@ -3469,6 +3488,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,
|
||||
@@ -3486,6 +3506,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();
|
||||
|
||||
@@ -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<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<String>,
|
||||
/// 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<Vec<AbsolutePathBuf>>,
|
||||
#[experimental(nested)]
|
||||
#[ts(optional = nullable)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
@@ -116,12 +119,11 @@ pub struct ThreadStartParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// 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<PermissionProfileSelectionParams>,
|
||||
pub permissions: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, JsonValue>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -195,6 +197,11 @@ pub struct ThreadStartResponse {
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
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<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
@@ -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<PermissionProfile>,
|
||||
/// 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<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<String>,
|
||||
/// 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<Vec<AbsolutePathBuf>>,
|
||||
#[experimental(nested)]
|
||||
#[ts(optional = nullable)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
@@ -271,14 +277,16 @@ pub struct ThreadResumeParams {
|
||||
/// and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
/// 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<SandboxMode>,
|
||||
/// 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<PermissionProfileSelectionParams>,
|
||||
pub permissions: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -310,6 +318,11 @@ pub struct ThreadResumeResponse {
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
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<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
@@ -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<PermissionProfile>,
|
||||
/// 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<Option<String>>,
|
||||
#[ts(optional = nullable)]
|
||||
pub cwd: Option<String>,
|
||||
/// 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<Vec<AbsolutePathBuf>>,
|
||||
#[experimental(nested)]
|
||||
#[ts(optional = nullable)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
@@ -377,14 +389,16 @@ pub struct ThreadForkParams {
|
||||
/// and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
/// 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<SandboxMode>,
|
||||
/// 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<PermissionProfileSelectionParams>,
|
||||
pub permissions: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -419,6 +433,11 @@ pub struct ThreadForkResponse {
|
||||
pub model_provider: String,
|
||||
pub service_tier: Option<String>,
|
||||
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<AbsolutePathBuf>,
|
||||
/// Instruction source files currently loaded for this thread.
|
||||
#[serde(default)]
|
||||
pub instruction_sources: Vec<AbsolutePathBuf>,
|
||||
@@ -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<PermissionProfile>,
|
||||
/// Named or implicit built-in profile that produced the active
|
||||
/// permissions, when known.
|
||||
#[experimental("thread/fork.activePermissionProfile")]
|
||||
|
||||
@@ -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<PathBuf>,
|
||||
/// 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<Vec<AbsolutePathBuf>>,
|
||||
/// 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<ApprovalsReviewer>,
|
||||
/// 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<SandboxPolicy>,
|
||||
/// 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<PermissionProfileSelectionParams>,
|
||||
pub permissions: Option<String>,
|
||||
/// Override the model for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub model: Option<String>,
|
||||
|
||||
@@ -444,12 +444,12 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
|
||||
let was_initialized = session.initialized();
|
||||
processor
|
||||
.process_client_request(
|
||||
IN_PROCESS_CONNECTION_ID,
|
||||
*request,
|
||||
Arc::clone(&session),
|
||||
&outbound_initialized,
|
||||
)
|
||||
.await;
|
||||
IN_PROCESS_CONNECTION_ID,
|
||||
*request,
|
||||
Arc::clone(&session),
|
||||
&outbound_initialized,
|
||||
)
|
||||
.await;
|
||||
let opted_out_notification_methods_snapshot =
|
||||
session.opted_out_notification_methods();
|
||||
let experimental_api_enabled =
|
||||
@@ -792,80 +792,119 @@ mod tests {
|
||||
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_start_initializes_and_handles_typed_v2_request() {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
let response = client
|
||||
.request(ClientRequest::ConfigRequirementsRead {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: None,
|
||||
fn run_current_thread_test_with_stack<F>(name: &str, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
const TEST_STACK_SIZE_BYTES: usize = 4 * 1024 * 1024;
|
||||
|
||||
let handle = std::thread::Builder::new()
|
||||
.name(name.to_string())
|
||||
.stack_size(TEST_STACK_SIZE_BYTES)
|
||||
.spawn(move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("current-thread runtime should build");
|
||||
runtime.block_on(Box::pin(future));
|
||||
})
|
||||
.await
|
||||
.expect("request transport should work")
|
||||
.expect("request should succeed");
|
||||
assert!(response.is_object());
|
||||
.expect("stack-sized test thread should spawn");
|
||||
|
||||
let _parsed: ConfigRequirementsReadResponse =
|
||||
serde_json::from_value(response).expect("response should match v2 schema");
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
assert!(handle.join().is_ok(), "{name} thread panicked");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_start_uses_requested_session_source_for_thread_start() {
|
||||
for (requested_source, expected_source) in [
|
||||
(SessionSource::Cli, ApiSessionSource::Cli),
|
||||
(SessionSource::Exec, ApiSessionSource::Exec),
|
||||
] {
|
||||
let client = start_test_client(requested_source).await;
|
||||
let response = client
|
||||
.request(ClientRequest::ThreadStart {
|
||||
request_id: RequestId::Integer(2),
|
||||
params: ThreadStartParams {
|
||||
ephemeral: Some(true),
|
||||
..ThreadStartParams::default()
|
||||
},
|
||||
})
|
||||
.await
|
||||
.expect("request transport should work")
|
||||
.expect("thread/start should succeed");
|
||||
let parsed: ThreadStartResponse =
|
||||
serde_json::from_value(response).expect("thread/start response should parse");
|
||||
assert_eq!(parsed.thread.source, expected_source);
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
}
|
||||
#[test]
|
||||
fn in_process_start_initializes_and_handles_typed_v2_request() {
|
||||
run_current_thread_test_with_stack(
|
||||
"in_process_start_initializes_and_handles_typed_v2_request",
|
||||
async {
|
||||
let client = start_test_client(SessionSource::Cli).await;
|
||||
let response = client
|
||||
.request(ClientRequest::ConfigRequirementsRead {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: None,
|
||||
})
|
||||
.await
|
||||
.expect("request transport should work")
|
||||
.expect("request should succeed");
|
||||
assert!(response.is_object());
|
||||
|
||||
let _parsed: ConfigRequirementsReadResponse =
|
||||
serde_json::from_value(response).expect("response should match v2 schema");
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn in_process_start_clamps_zero_channel_capacity() {
|
||||
let client =
|
||||
start_test_client_with_capacity(SessionSource::Cli, /*channel_capacity*/ 0).await;
|
||||
let response = loop {
|
||||
match client
|
||||
.request(ClientRequest::ConfigRequirementsRead {
|
||||
request_id: RequestId::Integer(4),
|
||||
params: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(response) => break response.expect("request should succeed"),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
tokio::task::yield_now().await;
|
||||
#[test]
|
||||
fn in_process_start_uses_requested_session_source_for_thread_start() {
|
||||
run_current_thread_test_with_stack(
|
||||
"in_process_start_uses_requested_session_source_for_thread_start",
|
||||
async {
|
||||
for (requested_source, expected_source) in [
|
||||
(SessionSource::Cli, ApiSessionSource::Cli),
|
||||
(SessionSource::Exec, ApiSessionSource::Exec),
|
||||
] {
|
||||
let client = start_test_client(requested_source).await;
|
||||
let response = client
|
||||
.request(ClientRequest::ThreadStart {
|
||||
request_id: RequestId::Integer(2),
|
||||
params: ThreadStartParams {
|
||||
ephemeral: Some(true),
|
||||
..ThreadStartParams::default()
|
||||
},
|
||||
})
|
||||
.await
|
||||
.expect("request transport should work")
|
||||
.expect("thread/start should succeed");
|
||||
let parsed: ThreadStartResponse = serde_json::from_value(response)
|
||||
.expect("thread/start response should parse");
|
||||
assert_eq!(parsed.thread.source, expected_source);
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
}
|
||||
Err(err) => panic!("request transport should work: {err}"),
|
||||
}
|
||||
};
|
||||
let _parsed: ConfigRequirementsReadResponse =
|
||||
serde_json::from_value(response).expect("response should match v2 schema");
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_process_start_clamps_zero_channel_capacity() {
|
||||
run_current_thread_test_with_stack(
|
||||
"in_process_start_clamps_zero_channel_capacity",
|
||||
async {
|
||||
let client = start_test_client_with_capacity(
|
||||
SessionSource::Cli,
|
||||
/*channel_capacity*/ 0,
|
||||
)
|
||||
.await;
|
||||
let response = loop {
|
||||
match client
|
||||
.request(ClientRequest::ConfigRequirementsRead {
|
||||
request_id: RequestId::Integer(4),
|
||||
params: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(response) => break response.expect("request should succeed"),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
Err(err) => panic!("request transport should work: {err}"),
|
||||
}
|
||||
};
|
||||
let _parsed: ConfigRequirementsReadResponse =
|
||||
serde_json::from_value(response).expect("response should match v2 schema");
|
||||
client
|
||||
.shutdown()
|
||||
.await
|
||||
.expect("in-process runtime should shutdown cleanly");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -595,7 +595,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,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
@@ -497,13 +498,13 @@ impl MessageProcessor {
|
||||
self.account_processor.clear_external_auth();
|
||||
}
|
||||
|
||||
pub(crate) async fn process_request(
|
||||
self: &Arc<Self>,
|
||||
pub(crate) fn process_request<'a>(
|
||||
self: &'a Arc<Self>,
|
||||
connection_id: ConnectionId,
|
||||
request: JSONRPCRequest,
|
||||
transport: &AppServerTransport,
|
||||
transport: &'a AppServerTransport,
|
||||
session: Arc<ConnectionSessionState>,
|
||||
) {
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
let request_method = request.method.as_str();
|
||||
tracing::trace!(
|
||||
?connection_id,
|
||||
@@ -521,52 +522,55 @@ impl MessageProcessor {
|
||||
tracestate: trace.tracestate.clone(),
|
||||
});
|
||||
let request_context = RequestContext::new(request_id.clone(), request_span, request_trace);
|
||||
Self::run_request_with_context(
|
||||
Arc::clone(&self.outgoing),
|
||||
request_context.clone(),
|
||||
async {
|
||||
let codex_request = serde_json::to_value(&request)
|
||||
.map_err(|err| invalid_request(format!("Invalid request: {err}")))
|
||||
.and_then(|request_json| {
|
||||
serde_json::from_value::<ClientRequest>(request_json)
|
||||
.map_err(|err| invalid_request(format!("Invalid request: {err}")))
|
||||
});
|
||||
let result = match codex_request {
|
||||
Ok(codex_request) => {
|
||||
// Websocket callers finalize outbound readiness in lib.rs after mirroring
|
||||
// session state into outbound state and sending initialize notifications to
|
||||
// this specific connection. Passing `None` avoids marking the connection
|
||||
// ready too early from inside the shared request handler.
|
||||
self.handle_client_request(
|
||||
request_id.clone(),
|
||||
codex_request,
|
||||
Arc::clone(&session),
|
||||
/*outbound_initialized*/ None,
|
||||
request_context.clone(),
|
||||
)
|
||||
.await
|
||||
Box::pin(async move {
|
||||
Self::run_request_with_context(
|
||||
Arc::clone(&self.outgoing),
|
||||
request_context.clone(),
|
||||
Box::pin(async move {
|
||||
let codex_request = serde_json::to_value(&request)
|
||||
.map_err(|err| invalid_request(format!("Invalid request: {err}")))
|
||||
.and_then(|request_json| {
|
||||
serde_json::from_value::<ClientRequest>(request_json)
|
||||
.map_err(|err| invalid_request(format!("Invalid request: {err}")))
|
||||
});
|
||||
let result = match codex_request {
|
||||
Ok(codex_request) => {
|
||||
// Websocket callers finalize outbound readiness in lib.rs after
|
||||
// mirroring session state into outbound state and sending initialize
|
||||
// notifications to this specific connection. Passing `None` avoids
|
||||
// marking the connection ready too early from inside the shared
|
||||
// request handler.
|
||||
self.handle_client_request(
|
||||
request_id.clone(),
|
||||
codex_request,
|
||||
Arc::clone(&session),
|
||||
/*outbound_initialized*/ None,
|
||||
request_context.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
};
|
||||
if let Err(error) = result {
|
||||
self.outgoing.send_error(request_id.clone(), error).await;
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
};
|
||||
if let Err(error) = result {
|
||||
self.outgoing.send_error(request_id.clone(), error).await;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles a typed request path used by in-process embedders.
|
||||
///
|
||||
/// This bypasses JSON request deserialization but keeps identical request
|
||||
/// semantics by delegating to `handle_client_request`.
|
||||
pub(crate) async fn process_client_request(
|
||||
self: &Arc<Self>,
|
||||
pub(crate) fn process_client_request<'a>(
|
||||
self: &'a Arc<Self>,
|
||||
connection_id: ConnectionId,
|
||||
request: ClientRequest,
|
||||
session: Arc<ConnectionSessionState>,
|
||||
outbound_initialized: &AtomicBool,
|
||||
) {
|
||||
outbound_initialized: &'a AtomicBool,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
let request_id = ConnectionRequestId {
|
||||
connection_id,
|
||||
request_id: request.id().clone(),
|
||||
@@ -580,28 +584,30 @@ impl MessageProcessor {
|
||||
request_id = ?request_id.request_id,
|
||||
"app-server typed request"
|
||||
);
|
||||
Self::run_request_with_context(
|
||||
Arc::clone(&self.outgoing),
|
||||
request_context.clone(),
|
||||
async {
|
||||
// In-process clients do not have the websocket transport loop that performs
|
||||
// post-initialize bookkeeping, so they still finalize outbound readiness in
|
||||
// the shared request handler.
|
||||
let result = self
|
||||
.handle_client_request(
|
||||
request_id.clone(),
|
||||
request,
|
||||
Arc::clone(&session),
|
||||
Some(outbound_initialized),
|
||||
request_context.clone(),
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = result {
|
||||
self.outgoing.send_error(request_id.clone(), error).await;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
Box::pin(async move {
|
||||
Self::run_request_with_context(
|
||||
Arc::clone(&self.outgoing),
|
||||
request_context.clone(),
|
||||
Box::pin(async move {
|
||||
// In-process clients do not have the websocket transport loop that performs
|
||||
// post-initialize bookkeeping, so they still finalize outbound readiness in
|
||||
// the shared request handler.
|
||||
let result = self
|
||||
.handle_client_request(
|
||||
request_id.clone(),
|
||||
request,
|
||||
Arc::clone(&session),
|
||||
Some(outbound_initialized),
|
||||
request_context.clone(),
|
||||
)
|
||||
.await;
|
||||
if let Err(error) = result {
|
||||
self.outgoing.send_error(request_id.clone(), error).await;
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn process_notification(&self, notification: JSONRPCNotification) {
|
||||
@@ -617,13 +623,11 @@ impl MessageProcessor {
|
||||
tracing::info!("<- typed notification: {:?}", notification);
|
||||
}
|
||||
|
||||
async fn run_request_with_context<F>(
|
||||
async fn run_request_with_context<'a>(
|
||||
outgoing: Arc<OutgoingMessageSender>,
|
||||
request_context: RequestContext,
|
||||
request_fut: F,
|
||||
) where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
request_fut: Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
|
||||
) {
|
||||
outgoing
|
||||
.register_request_context(request_context.clone())
|
||||
.await;
|
||||
|
||||
@@ -630,77 +630,80 @@ fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> Resul
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[serial(app_server_tracing)]
|
||||
async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
let mut harness = TracingHarness::new().await?;
|
||||
let thread_start_response = harness.start_thread(/*request_id*/ 2, /*trace*/ None).await;
|
||||
let thread_id = thread_start_response.thread.id.clone();
|
||||
#[test]
|
||||
fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
run_current_thread_test_with_stack("turn_start_jsonrpc_span_parents_core_turn_spans", async {
|
||||
let mut harness = TracingHarness::new().await?;
|
||||
let thread_start_response = harness.start_thread(/*request_id*/ 2, /*trace*/ None).await;
|
||||
let thread_id = thread_start_response.thread.id.clone();
|
||||
|
||||
harness.reset_tracing();
|
||||
harness.reset_tracing();
|
||||
|
||||
let RemoteTrace {
|
||||
trace_id: remote_trace_id,
|
||||
parent_span_id: remote_parent_span_id,
|
||||
context: remote_trace,
|
||||
} = RemoteTrace::new("00000000000000000000000000000077", "0000000000000088");
|
||||
let turn_start_response: TurnStartResponse = harness
|
||||
.request(
|
||||
ClientRequest::TurnStart {
|
||||
request_id: RequestId::Integer(3),
|
||||
params: TurnStartParams {
|
||||
environments: None,
|
||||
thread_id,
|
||||
input: vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
responsesapi_client_metadata: None,
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
permissions: None,
|
||||
approvals_reviewer: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
personality: None,
|
||||
output_schema: None,
|
||||
collaboration_mode: None,
|
||||
let RemoteTrace {
|
||||
trace_id: remote_trace_id,
|
||||
parent_span_id: remote_parent_span_id,
|
||||
context: remote_trace,
|
||||
} = RemoteTrace::new("00000000000000000000000000000077", "0000000000000088");
|
||||
let turn_start_response: TurnStartResponse = harness
|
||||
.request(
|
||||
ClientRequest::TurnStart {
|
||||
request_id: RequestId::Integer(3),
|
||||
params: TurnStartParams {
|
||||
environments: None,
|
||||
thread_id,
|
||||
input: vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
responsesapi_client_metadata: None,
|
||||
cwd: None,
|
||||
workspace_roots: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
permissions: None,
|
||||
approvals_reviewer: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
personality: None,
|
||||
output_schema: None,
|
||||
collaboration_mode: None,
|
||||
},
|
||||
},
|
||||
},
|
||||
Some(remote_trace),
|
||||
)
|
||||
.await;
|
||||
let spans = wait_for_exported_spans(harness.tracing, |spans| {
|
||||
spans.iter().any(|span| {
|
||||
span.span_kind == SpanKind::Server
|
||||
&& span_attr(span, "rpc.method") == Some("turn/start")
|
||||
&& span.span_context.trace_id() == remote_trace_id
|
||||
}) && spans.iter().any(|span| {
|
||||
span_attr(span, "codex.op") == Some("user_input")
|
||||
&& span.span_context.trace_id() == remote_trace_id
|
||||
Some(remote_trace),
|
||||
)
|
||||
.await;
|
||||
let spans = wait_for_exported_spans(harness.tracing, |spans| {
|
||||
spans.iter().any(|span| {
|
||||
span.span_kind == SpanKind::Server
|
||||
&& span_attr(span, "rpc.method") == Some("turn/start")
|
||||
&& span.span_context.trace_id() == remote_trace_id
|
||||
}) && spans.iter().any(|span| {
|
||||
span_attr(span, "codex.op") == Some("user_input")
|
||||
&& span.span_context.trace_id() == remote_trace_id
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
let server_request_span =
|
||||
find_rpc_span_with_trace(&spans, SpanKind::Server, "turn/start", remote_trace_id);
|
||||
let core_turn_span =
|
||||
find_span_with_trace(&spans, remote_trace_id, "codex.op=user_input", |span| {
|
||||
span_attr(span, "codex.op") == Some("user_input")
|
||||
});
|
||||
|
||||
assert_eq!(server_request_span.parent_span_id, remote_parent_span_id);
|
||||
assert!(server_request_span.parent_span_is_remote);
|
||||
assert_eq!(server_request_span.span_context.trace_id(), remote_trace_id);
|
||||
assert_eq!(
|
||||
span_attr(server_request_span, "turn.id"),
|
||||
Some(turn_start_response.turn.id.as_str())
|
||||
);
|
||||
assert_span_descends_from(&spans, core_turn_span, server_request_span);
|
||||
harness.shutdown().await;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await;
|
||||
|
||||
let server_request_span =
|
||||
find_rpc_span_with_trace(&spans, SpanKind::Server, "turn/start", remote_trace_id);
|
||||
let core_turn_span =
|
||||
find_span_with_trace(&spans, remote_trace_id, "codex.op=user_input", |span| {
|
||||
span_attr(span, "codex.op") == Some("user_input")
|
||||
});
|
||||
|
||||
assert_eq!(server_request_span.parent_span_id, remote_parent_span_id);
|
||||
assert!(server_request_span.parent_span_is_remote);
|
||||
assert_eq!(server_request_span.span_context.trace_id(), remote_trace_id);
|
||||
assert_eq!(
|
||||
span_attr(server_request_span, "turn.id"),
|
||||
Some(turn_start_response.turn.id.as_str())
|
||||
);
|
||||
assert_span_descends_from(&spans, core_turn_span, server_request_span);
|
||||
harness.shutdown().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -103,8 +103,6 @@ use codex_app_server_protocol::MockExperimentalMethodParams;
|
||||
use codex_app_server_protocol::MockExperimentalMethodResponse;
|
||||
use codex_app_server_protocol::ModelListParams;
|
||||
use codex_app_server_protocol::ModelListResponse;
|
||||
use codex_app_server_protocol::PermissionProfileModificationParams;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::PluginDetail;
|
||||
use codex_app_server_protocol::PluginInstallParams;
|
||||
use codex_app_server_protocol::PluginInstallResponse;
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -604,11 +604,13 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
reasoning_effort,
|
||||
..
|
||||
} = pending.config_snapshot;
|
||||
let instruction_sources = pending.instruction_sources;
|
||||
let sandbox = thread_response_sandbox_policy(&permission_profile, cwd.as_path());
|
||||
let sandbox =
|
||||
thread_response_sandbox_policy(&permission_profile, &workspace_roots, cwd.as_path());
|
||||
let active_permission_profile =
|
||||
thread_response_active_permission_profile(active_permission_profile);
|
||||
let session_id = conversation.session_configured().session_id.to_string();
|
||||
@@ -624,7 +626,7 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
approval_policy: approval_policy.into(),
|
||||
approvals_reviewer: approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile: Some(permission_profile.into()),
|
||||
workspace_roots,
|
||||
active_permission_profile,
|
||||
reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -802,6 +802,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -835,6 +836,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -1160,6 +1162,7 @@ impl ThreadRequestProcessor {
|
||||
|
||||
let sandbox = thread_response_sandbox_policy(
|
||||
&config_snapshot.permission_profile,
|
||||
&config_snapshot.workspace_roots,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let active_permission_profile =
|
||||
@@ -1175,7 +1178,7 @@ impl ThreadRequestProcessor {
|
||||
approval_policy: config_snapshot.approval_policy.into(),
|
||||
approvals_reviewer: config_snapshot.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
workspace_roots: config_snapshot.workspace_roots,
|
||||
active_permission_profile,
|
||||
reasoning_effort: config_snapshot.reasoning_effort,
|
||||
};
|
||||
@@ -1212,10 +1215,11 @@ impl ThreadRequestProcessor {
|
||||
model_provider: Option<String>,
|
||||
service_tier: Option<Option<String>>,
|
||||
cwd: Option<String>,
|
||||
workspace_roots: Option<Vec<AbsolutePathBuf>>,
|
||||
approval_policy: Option<codex_app_server_protocol::AskForApproval>,
|
||||
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
|
||||
sandbox: Option<SandboxMode>,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
permissions: Option<String>,
|
||||
base_instructions: Option<String>,
|
||||
developer_instructions: Option<String>,
|
||||
personality: Option<Personality>,
|
||||
@@ -1225,6 +1229,8 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd: cwd.map(PathBuf::from),
|
||||
workspace_roots: workspace_roots
|
||||
.map(|roots| roots.into_iter().map(|root| root.to_path_buf()).collect()),
|
||||
approval_policy: approval_policy
|
||||
.map(codex_app_server_protocol::AskForApproval::to_core),
|
||||
approvals_reviewer: approvals_reviewer
|
||||
@@ -2349,6 +2355,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -2384,6 +2391,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -2505,6 +2513,7 @@ impl ThreadRequestProcessor {
|
||||
let config_snapshot = codex_thread.config_snapshot().await;
|
||||
let sandbox = thread_response_sandbox_policy(
|
||||
&config_snapshot.permission_profile,
|
||||
&config_snapshot.workspace_roots,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
@@ -2525,7 +2534,7 @@ impl ThreadRequestProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
workspace_roots: config_snapshot.workspace_roots,
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
@@ -2985,6 +2994,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -3050,6 +3060,7 @@ impl ThreadRequestProcessor {
|
||||
model_provider,
|
||||
service_tier,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
@@ -3168,6 +3179,7 @@ impl ThreadRequestProcessor {
|
||||
let config_snapshot = forked_thread.config_snapshot().await;
|
||||
let sandbox = thread_response_sandbox_policy(
|
||||
&config_snapshot.permission_profile,
|
||||
&config_snapshot.workspace_roots,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let active_permission_profile =
|
||||
@@ -3183,7 +3195,7 @@ impl ThreadRequestProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
workspace_roots: config_snapshot.workspace_roots,
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -630,6 +630,7 @@ mod thread_processor_behavior_tests {
|
||||
model_provider: None,
|
||||
service_tier: Some(Some("priority".to_string())),
|
||||
cwd: None,
|
||||
workspace_roots: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox: None,
|
||||
@@ -650,6 +651,7 @@ mod thread_processor_behavior_tests {
|
||||
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
|
||||
active_permission_profile: None,
|
||||
cwd,
|
||||
workspace_roots: Vec::new(),
|
||||
ephemeral: false,
|
||||
reasoning_effort: None,
|
||||
personality: None,
|
||||
|
||||
@@ -177,32 +177,26 @@ pub(super) fn thread_response_active_permission_profile(
|
||||
|
||||
pub(super) fn apply_permission_profile_selection_to_config_overrides(
|
||||
overrides: &mut ConfigOverrides,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
permissions: Option<String>,
|
||||
) {
|
||||
let Some(PermissionProfileSelectionParams::Profile { id, modifications }) = permissions else {
|
||||
let Some(id) = permissions else {
|
||||
return;
|
||||
};
|
||||
overrides.default_permissions = Some(id);
|
||||
overrides
|
||||
.additional_writable_roots
|
||||
.extend(modifications.unwrap_or_default().into_iter().map(
|
||||
|modification| match modification {
|
||||
PermissionProfileModificationParams::AdditionalWritableRoot { path } => {
|
||||
path.to_path_buf()
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
pub(super) fn thread_response_sandbox_policy(
|
||||
permission_profile: &codex_protocol::models::PermissionProfile,
|
||||
workspace_roots: &[AbsolutePathBuf],
|
||||
cwd: &Path,
|
||||
) -> codex_app_server_protocol::SandboxPolicy {
|
||||
let file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
let materialized_permission_profile =
|
||||
permission_profile.materialize_project_roots_with_workspace_roots(workspace_roots);
|
||||
let file_system_policy = materialized_permission_profile.file_system_sandbox_policy();
|
||||
let sandbox_policy = codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
|
||||
permission_profile,
|
||||
&materialized_permission_profile,
|
||||
&file_system_policy,
|
||||
permission_profile.network_sandbox_policy(),
|
||||
materialized_permission_profile.network_sandbox_policy(),
|
||||
cwd,
|
||||
);
|
||||
sandbox_policy.into()
|
||||
|
||||
@@ -357,6 +357,7 @@ impl TurnRequestProcessor {
|
||||
let turn_has_input = !mapped_items.is_empty();
|
||||
|
||||
let has_any_overrides = params.cwd.is_some()
|
||||
|| params.workspace_roots.is_some()
|
||||
|| params.approval_policy.is_some()
|
||||
|| params.approvals_reviewer.is_some()
|
||||
|| params.sandbox_policy.is_some()
|
||||
@@ -375,6 +376,7 @@ impl TurnRequestProcessor {
|
||||
}
|
||||
|
||||
let cwd = params.cwd;
|
||||
let workspace_roots = params.workspace_roots;
|
||||
let approval_policy = params.approval_policy.map(AskForApproval::to_core);
|
||||
let approvals_reviewer = params
|
||||
.approvals_reviewer
|
||||
@@ -432,6 +434,9 @@ impl TurnRequestProcessor {
|
||||
thread
|
||||
.validate_turn_context_overrides(CodexThreadTurnContextOverrides {
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: workspace_roots
|
||||
.clone()
|
||||
.map(|roots| roots.into_iter().map(|root| root.to_path_buf()).collect()),
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy: sandbox_policy.clone(),
|
||||
@@ -457,6 +462,7 @@ impl TurnRequestProcessor {
|
||||
final_output_json_schema: params.output_schema,
|
||||
responsesapi_client_metadata: params.responsesapi_client_metadata,
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
|
||||
@@ -135,6 +135,7 @@ pub fn create_fake_rollout_with_source(
|
||||
forked_from_id: None,
|
||||
timestamp: meta_rfc3339.to_string(),
|
||||
cwd: PathBuf::from("/"),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "codex".to_string(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source,
|
||||
@@ -219,6 +220,7 @@ pub fn create_fake_rollout_with_text_elements(
|
||||
forked_from_id: None,
|
||||
timestamp: meta_rfc3339.to_string(),
|
||||
cwd: PathBuf::from("/"),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "codex".to_string(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: SessionSource::Cli,
|
||||
|
||||
@@ -130,6 +130,7 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: None,
|
||||
workspace_roots: Vec::new(),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Disabled,
|
||||
},
|
||||
|
||||
@@ -83,8 +83,7 @@ async fn process_spawn_returns_before_exit_and_emits_exit_notification() -> Resu
|
||||
.await?;
|
||||
assert_eq!(response.result, serde_json::json!({}));
|
||||
|
||||
wait_for_file(&probe_file).await?;
|
||||
assert_eq!(std::fs::read_to_string(&probe_file)?, "process");
|
||||
wait_for_file_contents(&probe_file, "process").await?;
|
||||
std::fs::write(&release_file, "release")?;
|
||||
|
||||
let exited = read_process_exited(&mut mcp).await?;
|
||||
@@ -239,9 +238,9 @@ async fn read_process_exited(mcp: &mut McpProcess) -> Result<ProcessExitedNotifi
|
||||
serde_json::from_value(params).context("deserialize process/exited notification")
|
||||
}
|
||||
|
||||
async fn wait_for_file(path: &Path) -> Result<()> {
|
||||
async fn wait_for_file_contents(path: &Path, expected: &str) -> Result<()> {
|
||||
timeout(DEFAULT_READ_TIMEOUT, async {
|
||||
while !path.exists() {
|
||||
while std::fs::read_to_string(path).ok().as_deref() != Some(expected) {
|
||||
sleep(Duration::from_millis(20)).await;
|
||||
}
|
||||
})
|
||||
|
||||
@@ -614,6 +614,7 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<(
|
||||
model_provider: None,
|
||||
service_tier: None,
|
||||
cwd: None,
|
||||
workspace_roots: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox: None,
|
||||
|
||||
@@ -610,6 +610,7 @@ sqlite = true
|
||||
codex_home: codex_home.path().to_path_buf(),
|
||||
sqlite_home: codex_home.path().to_path_buf(),
|
||||
cwd: codex_home.path().to_path_buf(),
|
||||
workspace_roots: Vec::new(),
|
||||
model_provider_id: "mock_provider".to_string(),
|
||||
generate_memories: false,
|
||||
};
|
||||
|
||||
@@ -1288,6 +1288,7 @@ async fn seed_pathless_store_thread(
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: None,
|
||||
workspace_roots: Vec::new(),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Disabled,
|
||||
},
|
||||
|
||||
@@ -1474,6 +1474,7 @@ stream_max_retries = 0
|
||||
forked_from_id: None,
|
||||
timestamp: "2025-01-05T12:00:00Z".to_string(),
|
||||
cwd: repo_path.clone(),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "codex".to_string(),
|
||||
cli_version: "0.0.0".to_string(),
|
||||
source: RolloutSessionSource::Cli,
|
||||
|
||||
@@ -44,6 +44,7 @@ use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
use std::fs::FileTimes;
|
||||
use std::fs::OpenOptions;
|
||||
use std::future::Future;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -54,6 +55,28 @@ use uuid::Uuid;
|
||||
|
||||
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
|
||||
fn run_current_thread_test_with_stack<F>(name: &str, future: F) -> Result<()>
|
||||
where
|
||||
F: Future<Output = Result<()>> + Send + 'static,
|
||||
{
|
||||
const TEST_STACK_SIZE_BYTES: usize = 4 * 1024 * 1024;
|
||||
|
||||
let handle = std::thread::Builder::new()
|
||||
.name(name.to_string())
|
||||
.stack_size(TEST_STACK_SIZE_BYTES)
|
||||
.spawn(move || -> Result<()> {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
runtime.block_on(Box::pin(future))
|
||||
})?;
|
||||
|
||||
match handle.join() {
|
||||
Ok(result) => result,
|
||||
Err(_) => Err(anyhow::anyhow!("{name} thread panicked")),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
@@ -197,96 +220,102 @@ async fn thread_unarchive_moves_rollout_back_into_sessions_directory() -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let store_id = Uuid::new_v4().to_string();
|
||||
create_config_toml_with_in_memory_thread_store(codex_home.path(), &store_id)?;
|
||||
let store = InMemoryThreadStore::for_id(store_id.clone());
|
||||
let _in_memory_store = InMemoryThreadStoreId { store_id };
|
||||
let thread_id = ThreadId::from_string("00000000-0000-4000-8000-000000000126")?;
|
||||
let parent_thread_id = ThreadId::from_string("00000000-0000-4000-8000-000000000127")?;
|
||||
store
|
||||
.create_thread(CreateThreadParams {
|
||||
thread_id,
|
||||
forked_from_id: Some(parent_thread_id),
|
||||
source: SessionSource::Cli,
|
||||
thread_source: None,
|
||||
base_instructions: BaseInstructions::default(),
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: None,
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Disabled,
|
||||
},
|
||||
event_persistence_mode: ThreadEventPersistenceMode::default(),
|
||||
})
|
||||
.await?;
|
||||
store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id,
|
||||
patch: ThreadMetadataPatch {
|
||||
name: Some(Some("named pathless thread".to_string())),
|
||||
..Default::default()
|
||||
},
|
||||
include_archived: true,
|
||||
})
|
||||
.await?;
|
||||
#[test]
|
||||
fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
|
||||
run_current_thread_test_with_stack(
|
||||
"thread_unarchive_preserves_pathless_store_metadata",
|
||||
async {
|
||||
let codex_home = TempDir::new()?;
|
||||
let store_id = Uuid::new_v4().to_string();
|
||||
create_config_toml_with_in_memory_thread_store(codex_home.path(), &store_id)?;
|
||||
let store = InMemoryThreadStore::for_id(store_id.clone());
|
||||
let _in_memory_store = InMemoryThreadStoreId { store_id };
|
||||
let thread_id = ThreadId::from_string("00000000-0000-4000-8000-000000000126")?;
|
||||
let parent_thread_id = ThreadId::from_string("00000000-0000-4000-8000-000000000127")?;
|
||||
store
|
||||
.create_thread(CreateThreadParams {
|
||||
thread_id,
|
||||
forked_from_id: Some(parent_thread_id),
|
||||
source: SessionSource::Cli,
|
||||
thread_source: None,
|
||||
base_instructions: BaseInstructions::default(),
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: None,
|
||||
workspace_roots: Vec::new(),
|
||||
model_provider: "test-provider".to_string(),
|
||||
memory_mode: ThreadMemoryMode::Disabled,
|
||||
},
|
||||
event_persistence_mode: ThreadEventPersistenceMode::default(),
|
||||
})
|
||||
.await?;
|
||||
store
|
||||
.update_thread_metadata(UpdateThreadMetadataParams {
|
||||
thread_id,
|
||||
patch: ThreadMetadataPatch {
|
||||
name: Some(Some("named pathless thread".to_string())),
|
||||
..Default::default()
|
||||
},
|
||||
include_archived: true,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let loader_overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.loader_overrides(loader_overrides.clone())
|
||||
.build()
|
||||
.await?;
|
||||
let client = in_process::start(InProcessStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::Cli,
|
||||
enable_codex_api_key_env: false,
|
||||
initialize: InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
name: "codex-app-server-tests".to_string(),
|
||||
title: None,
|
||||
version: "0.1.0".to_string(),
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: true,
|
||||
..Default::default()
|
||||
}),
|
||||
let loader_overrides = LoaderOverrides::without_managed_config_for_tests();
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.loader_overrides(loader_overrides.clone())
|
||||
.build()
|
||||
.await?;
|
||||
let client = in_process::start(InProcessStartArgs {
|
||||
arg0_paths: Arg0DispatchPaths::default(),
|
||||
config: Arc::new(config),
|
||||
cli_overrides: Vec::new(),
|
||||
loader_overrides,
|
||||
cloud_requirements: CloudRequirementsLoader::default(),
|
||||
thread_config_loader: Arc::new(codex_config::NoopThreadConfigLoader),
|
||||
feedback: CodexFeedback::new(),
|
||||
log_db: None,
|
||||
state_db: None,
|
||||
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
|
||||
config_warnings: Vec::new(),
|
||||
session_source: SessionSource::Cli,
|
||||
enable_codex_api_key_env: false,
|
||||
initialize: InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
name: "codex-app-server-tests".to_string(),
|
||||
title: None,
|
||||
version: "0.1.0".to_string(),
|
||||
},
|
||||
capabilities: Some(InitializeCapabilities {
|
||||
experimental_api: true,
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
channel_capacity: in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let result = client
|
||||
.request(ClientRequest::ThreadUnarchive {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: ThreadUnarchiveParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.expect("thread/unarchive should succeed");
|
||||
let ThreadUnarchiveResponse { thread } = serde_json::from_value(result)?;
|
||||
|
||||
assert_eq!(thread.id, thread_id.to_string());
|
||||
assert_eq!(thread.path, None);
|
||||
assert_eq!(thread.forked_from_id, Some(parent_thread_id.to_string()));
|
||||
assert_eq!(thread.name, Some("named pathless thread".to_string()));
|
||||
|
||||
client.shutdown().await?;
|
||||
Ok(())
|
||||
},
|
||||
channel_capacity: in_process::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let result = client
|
||||
.request(ClientRequest::ThreadUnarchive {
|
||||
request_id: RequestId::Integer(1),
|
||||
params: ThreadUnarchiveParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
},
|
||||
})
|
||||
.await?
|
||||
.expect("thread/unarchive should succeed");
|
||||
let ThreadUnarchiveResponse { thread } = serde_json::from_value(result)?;
|
||||
|
||||
assert_eq!(thread.id, thread_id.to_string());
|
||||
assert_eq!(thread.path, None);
|
||||
assert_eq!(thread.forked_from_id, Some(parent_thread_id.to_string()));
|
||||
assert_eq!(thread.name, Some("named pathless thread".to_string()));
|
||||
|
||||
client.shutdown().await?;
|
||||
Ok(())
|
||||
)
|
||||
}
|
||||
|
||||
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
|
||||
|
||||
@@ -34,7 +34,6 @@ use codex_app_server_protocol::JSONRPCNotification;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PatchApplyStatus;
|
||||
use codex_app_server_protocol::PatchChangeKind;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::ServerRequestResolvedNotification;
|
||||
@@ -329,9 +328,27 @@ async fn turn_start_emits_thread_scoped_warning_notification_for_trimmed_skills(
|
||||
let warning: WarningNotification =
|
||||
serde_json::from_value(params).expect("deserialize warning notification");
|
||||
assert_eq!(warning.thread_id.as_deref(), Some(thread.id.as_str()));
|
||||
assert_eq!(
|
||||
warning.message,
|
||||
"Exceeded skills context budget of 2%. All skill descriptions were removed and 7 additional skills were not included in the model-visible skills list."
|
||||
const WARNING_PREFIX: &str =
|
||||
"Exceeded skills context budget of 2%. All skill descriptions were removed and ";
|
||||
const WARNING_SUFFIX: &str =
|
||||
" additional skills were not included in the model-visible skills list.";
|
||||
assert!(
|
||||
warning.message.starts_with(WARNING_PREFIX),
|
||||
"unexpected warning prefix: {}",
|
||||
warning.message
|
||||
);
|
||||
assert!(
|
||||
warning.message.ends_with(WARNING_SUFFIX),
|
||||
"unexpected warning suffix: {}",
|
||||
warning.message
|
||||
);
|
||||
let trimmed_count = warning.message
|
||||
[WARNING_PREFIX.len()..warning.message.len() - WARNING_SUFFIX.len()]
|
||||
.parse::<usize>()
|
||||
.expect("warning should include a numeric trimmed skills count");
|
||||
assert!(
|
||||
trimmed_count > 0,
|
||||
"warning should report at least one trimmed skill"
|
||||
);
|
||||
|
||||
timeout(
|
||||
@@ -779,10 +796,7 @@ async fn turn_start_rejects_invalid_permission_selection_before_starting_turn()
|
||||
text: "Hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
permissions: Some(PermissionProfileSelectionParams::Profile {
|
||||
id: ":danger-no-sandbox".to_string(),
|
||||
modifications: None,
|
||||
}),
|
||||
permissions: Some(":danger-no-sandbox".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
@@ -1890,13 +1904,14 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
}],
|
||||
responsesapi_client_metadata: None,
|
||||
cwd: Some(first_cwd.clone()),
|
||||
workspace_roots: Some(vec![first_cwd.try_into()?]),
|
||||
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![first_cwd.try_into()?],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
legacy_writable_roots: Vec::new(),
|
||||
}),
|
||||
permissions: None,
|
||||
model: Some("mock-model".to_string()),
|
||||
@@ -1931,6 +1946,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
}],
|
||||
responsesapi_client_metadata: None,
|
||||
cwd: Some(second_cwd.clone()),
|
||||
workspace_roots: None,
|
||||
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess),
|
||||
|
||||
@@ -533,12 +533,13 @@ async fn turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2()
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: Some(workspace.clone()),
|
||||
workspace_roots: Some(vec![workspace.clone().try_into()?]),
|
||||
approval_policy: Some(codex_app_server_protocol::AskForApproval::UnlessTrusted),
|
||||
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![workspace.clone().try_into()?],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
legacy_writable_roots: Vec::new(),
|
||||
}),
|
||||
model: Some("mock-model".to_string()),
|
||||
effort: Some(codex_protocol::openai_models::ReasoningEffort::Medium),
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -2191,7 +2191,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
|
||||
@@ -2201,7 +2200,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,
|
||||
@@ -2312,9 +2310,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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -59,6 +59,7 @@ pub struct ThreadConfigSnapshot {
|
||||
pub permission_profile: PermissionProfile,
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub workspace_roots: Vec<AbsolutePathBuf>,
|
||||
pub ephemeral: bool,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
pub personality: Option<Personality>,
|
||||
@@ -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<PathBuf>,
|
||||
pub workspace_roots: Option<Vec<PathBuf>>,
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
@@ -256,8 +262,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,
|
||||
@@ -281,8 +305,9 @@ impl CodexThread {
|
||||
.with_updates(model, effort, /*developer_instructions*/ None)
|
||||
};
|
||||
|
||||
let updates = SessionSettingsUpdate {
|
||||
SessionSettingsUpdate {
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
@@ -294,8 +319,7 @@ impl CodexThread {
|
||||
service_tier,
|
||||
personality,
|
||||
..Default::default()
|
||||
};
|
||||
self.codex.session.validate_settings(&updates).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Use sparingly: this is intended to be removed soon.
|
||||
|
||||
@@ -630,9 +630,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(),
|
||||
@@ -696,7 +697,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,
|
||||
|
||||
@@ -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 {}",
|
||||
@@ -7293,6 +7308,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(
|
||||
@@ -7903,6 +7920,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(
|
||||
@@ -8050,6 +8068,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(
|
||||
@@ -9084,7 +9103,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,
|
||||
);
|
||||
|
||||
+129
-67
@@ -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<AskForApproval>,
|
||||
/// Canonical effective runtime permissions after config requirements and
|
||||
/// runtime readable-root additions have been applied.
|
||||
pub permission_profile: Constrained<PermissionProfile>,
|
||||
permission_profile: Constrained<PermissionProfile>,
|
||||
/// Named or implicit built-in profile selected by config, rather than an
|
||||
/// ad-hoc override.
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
/// 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<AskForApproval>,
|
||||
permission_profile: Constrained<PermissionProfile>,
|
||||
) -> 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<PermissionProfile> {
|
||||
&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<PermissionProfile>,
|
||||
) {
|
||||
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<PermissionProfile>,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
) {
|
||||
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<ActivePermissionProfile>,
|
||||
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<AbsolutePathBuf>,
|
||||
|
||||
/// Preferred store for CLI auth credentials.
|
||||
/// file (default): Use a file in the Codex home directory.
|
||||
/// keyring: Use an OS-specific keyring service.
|
||||
@@ -1865,6 +1936,11 @@ fn apply_managed_filesystem_constraints(
|
||||
}
|
||||
}
|
||||
|
||||
fn dedupe_absolute_paths(paths: &mut Vec<AbsolutePathBuf>) {
|
||||
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 {
|
||||
@@ -1894,6 +1970,9 @@ pub struct ConfigOverrides {
|
||||
pub bypass_hook_trust: Option<bool>,
|
||||
/// Additional directories that should be treated as writable roots for this session.
|
||||
pub additional_writable_roots: Vec<PathBuf>,
|
||||
/// Explicit workspace roots for this session. When set, this is the full
|
||||
/// root list rather than an additive override.
|
||||
pub workspace_roots: Option<Vec<PathBuf>>,
|
||||
}
|
||||
|
||||
/// Resolves the OSS provider from CLI override, profile config, or global config.
|
||||
@@ -2177,6 +2256,7 @@ impl Config {
|
||||
ephemeral,
|
||||
bypass_hook_trust,
|
||||
additional_writable_roots,
|
||||
workspace_roots: workspace_roots_override,
|
||||
} = overrides;
|
||||
let bypass_hook_trust = bypass_hook_trust.unwrap_or_default();
|
||||
|
||||
@@ -2273,11 +2353,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
}))?;
|
||||
let mut additional_writable_roots: Vec<AbsolutePathBuf> = additional_writable_roots
|
||||
let requested_additional_writable_roots: Vec<AbsolutePathBuf> = 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(
|
||||
@@ -2319,12 +2398,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!(
|
||||
@@ -2334,6 +2408,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,
|
||||
@@ -2370,10 +2469,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,
|
||||
@@ -2424,28 +2520,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,
|
||||
@@ -2462,22 +2538,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,
|
||||
@@ -2516,25 +2577,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,
|
||||
@@ -3032,6 +3093,7 @@ impl Config {
|
||||
model_provider_id,
|
||||
model_provider,
|
||||
cwd: resolved_cwd,
|
||||
workspace_roots,
|
||||
startup_warnings,
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
@@ -3323,7 +3385,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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Template> = LazyLock::new(|| {
|
||||
Template::parse(SANDBOX_MODE_DANGER_FULL_ACCESS.trim_end())
|
||||
@@ -256,17 +258,42 @@ fn writable_roots_text(writable_roots: Option<Vec<WritableRoot>>) -> Option<Stri
|
||||
return None;
|
||||
}
|
||||
|
||||
let total_roots = roots.len();
|
||||
let roots_list: Vec<String> = roots
|
||||
.iter()
|
||||
.map(|r| format!("`{}`", r.root.to_string_lossy()))
|
||||
.take(MAX_WRITABLE_ROOTS_IN_PROMPT)
|
||||
.map(|r| {
|
||||
format!(
|
||||
"`{}`",
|
||||
truncate_writable_root_label(&r.root.to_string_lossy())
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Some(if roots_list.len() == 1 {
|
||||
Some(if total_roots == 1 {
|
||||
format!(" The writable root is {}.", roots_list[0])
|
||||
} else {
|
||||
} else if total_roots == roots_list.len() {
|
||||
format!(" The writable roots are {}.", roots_list.join(", "))
|
||||
} else {
|
||||
format!(
|
||||
" The writable roots include {}, and {} more.",
|
||||
roots_list.join(", "),
|
||||
total_roots - roots_list.len()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn truncate_writable_root_label(label: &str) -> String {
|
||||
if label.chars().count() <= MAX_WRITABLE_ROOT_LABEL_CHARS {
|
||||
return label.to_string();
|
||||
}
|
||||
|
||||
label
|
||||
.chars()
|
||||
.take(MAX_WRITABLE_ROOT_LABEL_CHARS - 3)
|
||||
.chain("...".chars())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn approved_command_prefixes_text(exec_policy: &Policy) -> Option<String> {
|
||||
format_allow_prefixes(exec_policy.get_allowed_prefixes())
|
||||
.filter(|prefixes| !prefixes.is_empty())
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::path::PathBuf;
|
||||
fn renders_sandbox_mode_text() {
|
||||
assert_eq!(
|
||||
sandbox_text(SandboxMode::WorkspaceWrite, NetworkAccess::Restricted),
|
||||
"Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in `cwd` and `writable_roots`. Editing files in other directories requires approval. Network access is restricted."
|
||||
"Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in the configured writable roots. Editing files in other directories requires approval. Network access is restricted."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -56,7 +56,6 @@ fn builds_permissions_with_network_access_override() {
|
||||
#[test]
|
||||
fn builds_permissions_from_policy() {
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
@@ -106,6 +105,45 @@ fn builds_permissions_from_profile() {
|
||||
assert!(text.contains(writable_root.to_string_lossy().as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writable_roots_text_limits_long_root_lists() {
|
||||
let roots = (0..MAX_WRITABLE_ROOTS_IN_PROMPT + 2)
|
||||
.map(|index| WritableRoot {
|
||||
root: AbsolutePathBuf::from_absolute_path(format!("/tmp/root-{index}"))
|
||||
.expect("absolute path"),
|
||||
read_only_subpaths: Vec::new(),
|
||||
protected_metadata_names: Vec::new(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let visible_root_labels = roots
|
||||
.iter()
|
||||
.map(|root| root.root.to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let text = writable_roots_text(Some(roots)).expect("writable roots text");
|
||||
|
||||
assert!(text.contains(&visible_root_labels[0]));
|
||||
assert!(text.contains(&visible_root_labels[MAX_WRITABLE_ROOTS_IN_PROMPT - 1]));
|
||||
assert!(!text.contains(&visible_root_labels[MAX_WRITABLE_ROOTS_IN_PROMPT]));
|
||||
assert!(text.contains("and 2 more"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writable_roots_text_truncates_long_root_labels() {
|
||||
let long_segment = "a".repeat(MAX_WRITABLE_ROOT_LABEL_CHARS + 20);
|
||||
let root = WritableRoot {
|
||||
root: AbsolutePathBuf::from_absolute_path(format!("/tmp/{long_segment}"))
|
||||
.expect("absolute path"),
|
||||
read_only_subpaths: Vec::new(),
|
||||
protected_metadata_names: Vec::new(),
|
||||
};
|
||||
|
||||
let text = writable_roots_text(Some(vec![root])).expect("writable roots text");
|
||||
|
||||
assert!(!text.contains(&long_segment));
|
||||
assert!(text.contains("..."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn includes_request_rule_instructions_for_on_request() {
|
||||
let mut exec_policy = Policy::empty();
|
||||
|
||||
@@ -1 +1 @@
|
||||
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in `cwd` and `writable_roots`. Editing files in other directories requires approval. Network access is {{network_access}}.
|
||||
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in the configured writable roots. Editing files in other directories requires approval. Network access is {{network_access}}.
|
||||
|
||||
@@ -123,11 +123,13 @@ fn reference_context_item() -> TurnContextItem {
|
||||
turn_id: Some("reference-turn".to_string()),
|
||||
trace_id: None,
|
||||
cwd: PathBuf::from("/tmp/reference-cwd"),
|
||||
workspace_roots: Vec::new(),
|
||||
current_date: Some("2026-03-23".to_string()),
|
||||
timezone: Some("America/Los_Angeles".to_string()),
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: "gpt-test".to_string(),
|
||||
|
||||
@@ -49,7 +49,14 @@ fn build_permissions_update_item(
|
||||
}
|
||||
|
||||
let prev = previous?;
|
||||
if prev.permission_profile() == next.permission_profile()
|
||||
let prev_permission_profile = prev
|
||||
.permission_profile()
|
||||
.materialize_project_roots_with_workspace_roots(&prev.workspace_roots);
|
||||
let next_permission_profile = next
|
||||
.permission_profile
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&next.workspace_roots);
|
||||
if prev_permission_profile == next_permission_profile
|
||||
&& prev.approval_policy == next.approval_policy.value()
|
||||
{
|
||||
return None;
|
||||
@@ -57,7 +64,7 @@ fn build_permissions_update_item(
|
||||
|
||||
Some(
|
||||
PermissionsInstructions::from_permission_profile(
|
||||
&next.permission_profile,
|
||||
&next_permission_profile,
|
||||
next.approval_policy.value(),
|
||||
next.config.approvals_reviewer,
|
||||
exec_policy,
|
||||
|
||||
+34
-28
@@ -99,11 +99,11 @@ pub struct ExecParams {
|
||||
///
|
||||
/// The elevated Windows backend consumes extra deny-read paths plus explicit
|
||||
/// read and write roots during setup/refresh. The unelevated restricted-token
|
||||
/// backend only consumes extra deny-write carveouts on top of the legacy
|
||||
/// `WorkspaceWrite` allow set. Read-root overrides are layered on top of the
|
||||
/// baseline helper roots that the elevated setup path needs to launch the
|
||||
/// sandboxed command; split policies that opt into platform defaults carry
|
||||
/// that explicitly with the override.
|
||||
/// backend consumes explicit write roots and extra deny-write carveouts on top
|
||||
/// of the legacy `WorkspaceWrite` shape.
|
||||
/// Read-root overrides are layered on top of the baseline helper roots that the
|
||||
/// elevated setup path needs to launch the sandboxed command. Split policies
|
||||
/// that opt into platform defaults carry that explicitly with the override.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct WindowsSandboxFilesystemOverrides {
|
||||
pub(crate) read_roots_override: Option<Vec<PathBuf>>,
|
||||
@@ -296,6 +296,7 @@ pub async fn process_exec_tool_call(
|
||||
params: ExecParams,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_cwd: &AbsolutePathBuf,
|
||||
workspace_roots: &[AbsolutePathBuf],
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_legacy_landlock: bool,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
@@ -304,6 +305,7 @@ pub async fn process_exec_tool_call(
|
||||
params,
|
||||
permission_profile,
|
||||
sandbox_cwd,
|
||||
workspace_roots,
|
||||
codex_linux_sandbox_exe,
|
||||
use_legacy_landlock,
|
||||
)?;
|
||||
@@ -318,6 +320,7 @@ pub fn build_exec_request(
|
||||
params: ExecParams,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_cwd: &AbsolutePathBuf,
|
||||
workspace_roots: &[AbsolutePathBuf],
|
||||
codex_linux_sandbox_exe: &Option<PathBuf>,
|
||||
use_legacy_landlock: bool,
|
||||
) -> Result<ExecRequest> {
|
||||
@@ -379,6 +382,7 @@ pub fn build_exec_request(
|
||||
enforce_managed_network,
|
||||
network: network.as_ref(),
|
||||
sandbox_policy_cwd: sandbox_cwd,
|
||||
workspace_roots,
|
||||
codex_linux_sandbox_exe: codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock,
|
||||
windows_sandbox_level,
|
||||
@@ -612,11 +616,11 @@ async fn exec_windows_sandbox(
|
||||
let additional_deny_read_paths = windows_sandbox_filesystem_overrides
|
||||
.map(|overrides| overrides.additional_deny_read_paths.clone())
|
||||
.unwrap_or_default();
|
||||
let elevated_read_roots_override = windows_sandbox_filesystem_overrides
|
||||
let read_roots_override = windows_sandbox_filesystem_overrides
|
||||
.and_then(|overrides| overrides.read_roots_override.clone());
|
||||
let elevated_read_roots_include_platform_defaults = windows_sandbox_filesystem_overrides
|
||||
let read_roots_include_platform_defaults = windows_sandbox_filesystem_overrides
|
||||
.is_some_and(|overrides| overrides.read_roots_include_platform_defaults);
|
||||
let elevated_write_roots_override = windows_sandbox_filesystem_overrides
|
||||
let write_roots_override = windows_sandbox_filesystem_overrides
|
||||
.and_then(|overrides| overrides.write_roots_override.clone());
|
||||
let spawn_res = tokio::task::spawn_blocking(move || {
|
||||
if use_elevated {
|
||||
@@ -631,10 +635,9 @@ async fn exec_windows_sandbox(
|
||||
timeout_ms,
|
||||
use_private_desktop: windows_sandbox_private_desktop,
|
||||
proxy_enforced,
|
||||
read_roots_override: elevated_read_roots_override.as_deref(),
|
||||
read_roots_include_platform_defaults:
|
||||
elevated_read_roots_include_platform_defaults,
|
||||
write_roots_override: elevated_write_roots_override.as_deref(),
|
||||
read_roots_override: read_roots_override.as_deref(),
|
||||
read_roots_include_platform_defaults,
|
||||
write_roots_override: write_roots_override.as_deref(),
|
||||
deny_read_paths_override: &additional_deny_read_paths,
|
||||
deny_write_paths_override: &additional_deny_write_paths,
|
||||
},
|
||||
@@ -648,6 +651,7 @@ async fn exec_windows_sandbox(
|
||||
&cwd,
|
||||
env,
|
||||
timeout_ms,
|
||||
write_roots_override.as_deref(),
|
||||
&additional_deny_read_paths,
|
||||
&additional_deny_write_paths,
|
||||
windows_sandbox_private_desktop,
|
||||
@@ -1075,12 +1079,18 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
.map(|root| normalize_windows_override_path(root.root.as_path()))
|
||||
.collect::<std::result::Result<_, _>>()?;
|
||||
|
||||
if legacy_root_paths != split_root_paths {
|
||||
let write_roots_override = if legacy_root_paths == split_root_paths {
|
||||
None
|
||||
} else if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. })
|
||||
&& !split_root_paths.iter().any(|path| path.parent().is_none())
|
||||
{
|
||||
Some(split_root_paths.iter().cloned().collect::<Vec<_>>())
|
||||
} else {
|
||||
return Err(
|
||||
"windows unelevated restricted-token sandbox cannot enforce split writable root sets directly; refusing to run unsandboxed"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
for writable_root in &split_writable_roots {
|
||||
for read_only_subpath in &writable_root.read_only_subpaths {
|
||||
@@ -1102,22 +1112,18 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
let mut additional_deny_write_paths = BTreeSet::new();
|
||||
for split_root in &split_writable_roots {
|
||||
let split_root_path = normalize_windows_override_path(split_root.root.as_path())?;
|
||||
let Some(legacy_root) = legacy_writable_roots.iter().find(|candidate| {
|
||||
let legacy_root = legacy_writable_roots.iter().find(|candidate| {
|
||||
normalize_windows_override_path(candidate.root.as_path())
|
||||
.is_ok_and(|candidate_path| candidate_path == split_root_path)
|
||||
}) else {
|
||||
return Err(
|
||||
"windows unelevated restricted-token sandbox cannot enforce split writable root sets directly; refusing to run unsandboxed"
|
||||
.to_string(),
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
for read_only_subpath in &split_root.read_only_subpaths {
|
||||
if !legacy_root
|
||||
.read_only_subpaths
|
||||
.iter()
|
||||
.any(|candidate| candidate == read_only_subpath)
|
||||
{
|
||||
if !legacy_root.is_some_and(|legacy_root| {
|
||||
legacy_root
|
||||
.read_only_subpaths
|
||||
.iter()
|
||||
.any(|candidate| candidate == read_only_subpath)
|
||||
}) {
|
||||
additional_deny_write_paths.insert(normalize_windows_override_path(
|
||||
read_only_subpath.as_path(),
|
||||
)?);
|
||||
@@ -1125,15 +1131,15 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
}
|
||||
}
|
||||
|
||||
if additional_deny_read_paths.is_empty() && additional_deny_write_paths.is_empty() {
|
||||
if write_roots_override.is_none() && additional_deny_write_paths.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(WindowsSandboxFilesystemOverrides {
|
||||
read_roots_override: None,
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: None,
|
||||
additional_deny_read_paths,
|
||||
write_roots_override,
|
||||
additional_deny_write_paths: additional_deny_write_paths
|
||||
.into_iter()
|
||||
.map(|path| AbsolutePathBuf::from_absolute_path(path).map_err(|err| err.to_string()))
|
||||
|
||||
@@ -365,6 +365,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
|
||||
},
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
&[],
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
@@ -470,7 +471,6 @@ fn windows_restricted_token_allows_legacy_restricted_policies() {
|
||||
#[test]
|
||||
fn windows_restricted_token_allows_legacy_workspace_write_policies() {
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -528,7 +528,6 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
|
||||
let docs = temp_dir.path().join("docs");
|
||||
std::fs::create_dir_all(&docs).expect("create docs");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -573,7 +572,6 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
|
||||
let docs = temp_dir.path().join("docs");
|
||||
std::fs::create_dir_all(&docs).expect("create docs");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -619,7 +617,6 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
|
||||
let docs = cwd.join("docs");
|
||||
std::fs::create_dir_all(docs.as_path()).expect("create docs");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -677,7 +674,6 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
|
||||
let blocked = cwd.join("blocked");
|
||||
std::fs::create_dir_all(blocked.as_path()).expect("create blocked");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -719,6 +715,57 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_restricted_token_supports_extra_split_writable_root_override() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let cwd = dunce::canonicalize(temp_dir.path())
|
||||
.expect("canonicalize temp dir")
|
||||
.abs();
|
||||
let extra_root = cwd.join("extra-root");
|
||||
std::fs::create_dir_all(extra_root.as_path()).expect("create extra root");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path: cwd.clone() },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path {
|
||||
path: extra_root.clone(),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
resolve_windows_restricted_token_filesystem_overrides(
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
&policy,
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
&cwd,
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
),
|
||||
Ok(Some(WindowsSandboxFilesystemOverrides {
|
||||
read_roots_override: None,
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: Some(vec![cwd.to_path_buf(), extra_root.to_path_buf()]),
|
||||
additional_deny_read_paths: vec![],
|
||||
additional_deny_write_paths: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_elevated_supports_split_restricted_read_roots() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
@@ -764,7 +811,6 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
std::fs::create_dir_all(&docs).expect("create docs");
|
||||
let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -822,7 +868,6 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
|
||||
std::fs::create_dir_all(&blocked).expect("create blocked");
|
||||
let expected_blocked = dunce::canonicalize(&blocked).expect("canonical blocked");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -885,7 +930,6 @@ fn windows_elevated_supports_unreadable_globs() {
|
||||
std::fs::create_dir_all(secret.parent().expect("parent")).expect("create parent");
|
||||
std::fs::write(&secret, "secret").expect("write secret");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -942,7 +986,6 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
|
||||
let nested = docs.join("nested");
|
||||
std::fs::create_dir_all(&nested).expect("create nested");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -1115,6 +1158,7 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
|
||||
params,
|
||||
&PermissionProfile::Disabled,
|
||||
&cwd,
|
||||
&[],
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
|
||||
@@ -895,9 +895,11 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
guardian_config.developer_instructions = None;
|
||||
guardian_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
guardian_config.permissions.permission_profile = Constrained::allow_only(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
);
|
||||
guardian_config
|
||||
.permissions
|
||||
.replace_permission_profile_constraint(Constrained::allow_only(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
));
|
||||
guardian_config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(sandbox_policy, guardian_config.cwd.as_path())
|
||||
@@ -923,7 +925,7 @@ pub(crate) fn build_guardian_review_session_config(
|
||||
guardian_config.permissions.network = Some(NetworkProxySpec::from_config_and_constraints(
|
||||
live_network_config,
|
||||
network_constraints,
|
||||
guardian_config.permissions.permission_profile.get(),
|
||||
guardian_config.permissions.permission_profile_ref(),
|
||||
)?);
|
||||
}
|
||||
for feature in [
|
||||
|
||||
@@ -2163,7 +2163,7 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
parent_config.permissions.permission_profile.get(),
|
||||
parent_config.permissions.permission_profile_ref(),
|
||||
)
|
||||
.expect("network proxy spec");
|
||||
parent_config.permissions.network = Some(network.clone());
|
||||
@@ -2190,8 +2190,8 @@ async fn guardian_review_session_config_preserves_parent_network_proxy() {
|
||||
Constrained::allow_only(AskForApproval::Never)
|
||||
);
|
||||
assert_eq!(
|
||||
guardian_config.permissions.permission_profile,
|
||||
Constrained::allow_only(PermissionProfile::from_legacy_sandbox_policy(
|
||||
guardian_config.permissions.permission_profile_constraint(),
|
||||
&Constrained::allow_only(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
))
|
||||
);
|
||||
@@ -2230,7 +2230,7 @@ async fn guardian_review_session_config_uses_live_network_proxy_state() {
|
||||
NetworkProxySpec::from_config_and_constraints(
|
||||
parent_network,
|
||||
/*requirements*/ None,
|
||||
parent_config.permissions.permission_profile.get(),
|
||||
parent_config.permissions.permission_profile_ref(),
|
||||
)
|
||||
.expect("parent network proxy spec"),
|
||||
);
|
||||
|
||||
@@ -47,6 +47,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
|
||||
forked_from_id: None,
|
||||
timestamp: TEST_TIMESTAMP.to_string(),
|
||||
cwd: std::path::PathBuf::from("."),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "test_originator".to_string(),
|
||||
cli_version: "test_version".to_string(),
|
||||
source: SessionSource::Cli,
|
||||
|
||||
@@ -37,6 +37,10 @@ impl codex_rollout::RolloutConfigView for Config {
|
||||
self.cwd.as_path()
|
||||
}
|
||||
|
||||
fn workspace_roots(&self) -> &[codex_utils_absolute_path::AbsolutePathBuf] {
|
||||
&self.workspace_roots
|
||||
}
|
||||
|
||||
fn model_provider_id(&self) -> &str {
|
||||
self.model_provider_id.as_str()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ fn test_writable_roots_constraint() {
|
||||
// Policy limited to the workspace only; exclude system temp roots so
|
||||
// only `cwd` is writable by default.
|
||||
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -51,17 +50,16 @@ fn test_writable_roots_constraint() {
|
||||
&cwd,
|
||||
));
|
||||
|
||||
// With the parent dir explicitly added as a writable root, the
|
||||
// outside write should be permitted.
|
||||
let policy_with_parent = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![parent],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
// With the parent dir explicitly added as a workspace root, the outside
|
||||
// write should be permitted.
|
||||
let file_system_policy_with_parent =
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: parent },
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
assert!(is_write_patch_constrained_to_writable_paths(
|
||||
&add_outside,
|
||||
&FileSystemSandboxPolicy::from(&policy_with_parent),
|
||||
&file_system_policy_with_parent,
|
||||
&cwd,
|
||||
));
|
||||
}
|
||||
@@ -101,7 +99,6 @@ fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() {
|
||||
let outside_path = parent.join("outside.txt");
|
||||
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
|
||||
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -145,7 +142,6 @@ fn granular_sandbox_approval_false_rejects_out_of_root_patch() {
|
||||
let outside_path = parent.join("outside.txt");
|
||||
let add_outside = ApplyPatchAction::new_add_for_test(&outside_path, "".to_string());
|
||||
let policy_workspace_only = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -295,7 +291,6 @@ fn missing_project_dot_codex_config_requires_approval() {
|
||||
let config_path = cwd.join(".codex").join("config.toml");
|
||||
let action = ApplyPatchAction::new_add_for_test(&config_path, "".to_string());
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
||||
@@ -143,6 +143,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
items,
|
||||
SessionSettingsUpdate {
|
||||
cwd: Some(cwd),
|
||||
workspace_roots: None,
|
||||
approval_policy: Some(approval_policy),
|
||||
approvals_reviewer,
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
@@ -163,6 +164,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
}
|
||||
Op::UserInputWithTurnContext {
|
||||
cwd,
|
||||
workspace_roots,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
@@ -195,6 +197,8 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
items,
|
||||
SessionSettingsUpdate {
|
||||
cwd,
|
||||
workspace_roots: workspace_roots
|
||||
.map(|roots| roots.into_iter().map(|root| root.to_path_buf()).collect()),
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
|
||||
@@ -615,10 +615,11 @@ impl Codex {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: environment_selections.to_selections(),
|
||||
@@ -2624,9 +2625,13 @@ impl Session {
|
||||
developer_sections.push(model_switch_message);
|
||||
}
|
||||
if turn_context.config.include_permissions_instructions {
|
||||
let permission_profile = turn_context
|
||||
.permission_profile
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&turn_context.workspace_roots);
|
||||
developer_sections.push(
|
||||
PermissionsInstructions::from_permission_profile(
|
||||
&turn_context.permission_profile,
|
||||
&permission_profile,
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.config.approvals_reviewer,
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
|
||||
@@ -143,6 +143,7 @@ pub(super) async fn spawn_review_thread(
|
||||
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
|
||||
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
|
||||
cwd: parent_turn_context.cwd.clone(),
|
||||
workspace_roots: parent_turn_context.workspace_roots.clone(),
|
||||
final_output_json_schema: None,
|
||||
codex_self_exe: parent_turn_context.codex_self_exe.clone(),
|
||||
codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),
|
||||
|
||||
@@ -61,11 +61,13 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -102,11 +104,13 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -912,11 +916,13 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -990,11 +996,13 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -1021,11 +1029,13 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -1136,11 +1146,13 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
turn_id: Some(current_turn_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: current_model.to_string(),
|
||||
@@ -1250,11 +1262,13 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -1402,11 +1416,13 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
|
||||
@@ -75,6 +75,9 @@ pub(crate) struct SessionConfiguration {
|
||||
/// execution sandbox are resolved against this directory **instead** of
|
||||
/// the process-wide current working directory.
|
||||
pub(super) cwd: AbsolutePathBuf,
|
||||
/// Workspace roots used to materialize symbolic `:project_roots`
|
||||
/// permission entries for this thread.
|
||||
pub(super) workspace_roots: Vec<AbsolutePathBuf>,
|
||||
/// Directory containing all Codex state for this session.
|
||||
pub(super) codex_home: AbsolutePathBuf,
|
||||
/// Optional user-facing name for the thread, updated during the session.
|
||||
@@ -112,12 +115,13 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.permission_profile()
|
||||
let permission_profile = self.materialized_permission_profile();
|
||||
permission_profile
|
||||
.to_legacy_sandbox_policy(&self.cwd)
|
||||
.unwrap_or_else(|_| {
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
|
||||
self.permission_profile.get(),
|
||||
&permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
self.network_sandbox_policy(),
|
||||
&self.cwd,
|
||||
@@ -126,13 +130,21 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.permission_profile.get().file_system_sandbox_policy()
|
||||
self.materialized_permission_profile()
|
||||
.file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(super) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.permission_profile.get().network_sandbox_policy()
|
||||
}
|
||||
|
||||
fn materialized_permission_profile(&self) -> PermissionProfile {
|
||||
self.permission_profile
|
||||
.get()
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&self.workspace_roots)
|
||||
}
|
||||
|
||||
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
ThreadConfigSnapshot {
|
||||
model: self.collaboration_mode.model().to_string(),
|
||||
@@ -143,6 +155,7 @@ impl SessionConfiguration {
|
||||
permission_profile: self.permission_profile(),
|
||||
active_permission_profile: self.active_permission_profile(),
|
||||
cwd: self.cwd.clone(),
|
||||
workspace_roots: self.workspace_roots.clone(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
personality: self.personality,
|
||||
@@ -223,6 +236,20 @@ impl SessionConfiguration {
|
||||
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
|
||||
next_configuration.cwd = absolute_cwd;
|
||||
|
||||
if let Some(workspace_roots) = updates.workspace_roots.clone() {
|
||||
let mut workspace_roots: Vec<AbsolutePathBuf> = workspace_roots
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
AbsolutePathBuf::resolve_path_against_base(
|
||||
normalize_for_native_workdir(path.as_path()),
|
||||
next_configuration.cwd.as_path(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
dedupe_absolute_paths(&mut workspace_roots);
|
||||
next_configuration.workspace_roots = workspace_roots;
|
||||
}
|
||||
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
let active_permission_profile =
|
||||
updates.active_permission_profile.clone().or_else(|| {
|
||||
@@ -253,26 +280,31 @@ impl SessionConfiguration {
|
||||
),
|
||||
)?;
|
||||
next_configuration.active_permission_profile = None;
|
||||
} else if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_project_root_write
|
||||
{
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using a structurally
|
||||
// cwd-bound legacy bridge.
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
current_network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
} else {
|
||||
if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_project_root_write
|
||||
{
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using a structurally
|
||||
// cwd-bound legacy bridge.
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
current_network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
if let Some(active_permission_profile) = updates.active_permission_profile.clone() {
|
||||
next_configuration.active_permission_profile = Some(active_permission_profile);
|
||||
}
|
||||
}
|
||||
if let Some(app_server_client_name) = updates.app_server_client_name.clone() {
|
||||
next_configuration.app_server_client_name = Some(app_server_client_name);
|
||||
@@ -306,9 +338,15 @@ impl SessionConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
fn dedupe_absolute_paths(paths: &mut Vec<AbsolutePathBuf>) {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
paths.retain(|path| seen.insert(path.clone()));
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) cwd: Option<PathBuf>,
|
||||
pub(crate) workspace_roots: Option<Vec<PathBuf>>,
|
||||
pub(crate) approval_policy: Option<AskForApproval>,
|
||||
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub(crate) sandbox_policy: Option<SandboxPolicy>,
|
||||
@@ -425,6 +463,7 @@ impl Session {
|
||||
dynamic_tools: session_configuration.dynamic_tools.clone(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
model_provider: config.model_provider_id.clone(),
|
||||
memory_mode: if config.memories.generate_memories {
|
||||
ThreadMemoryMode::Enabled
|
||||
@@ -447,6 +486,7 @@ impl Session {
|
||||
include_archived: true,
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
model_provider: config.model_provider_id.clone(),
|
||||
memory_mode: if config.memories.generate_memories {
|
||||
ThreadMemoryMode::Enabled
|
||||
@@ -769,7 +809,7 @@ impl Session {
|
||||
let (network_proxy, session_network_proxy) = Self::start_managed_network_proxy(
|
||||
spec,
|
||||
current_exec_policy.as_ref(),
|
||||
config.permissions.permission_profile.get(),
|
||||
config.permissions.permission_profile_ref(),
|
||||
network_policy_decider.as_ref().map(Arc::clone),
|
||||
blocked_request_observer.as_ref().map(Arc::clone),
|
||||
managed_network_requirements_configured,
|
||||
@@ -833,7 +873,7 @@ impl Session {
|
||||
// setup is straightforward enough and performs well.
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.permission_profile,
|
||||
config.permissions.permission_profile_constraint(),
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -935,6 +975,7 @@ impl Session {
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
active_permission_profile: session_configuration.active_permission_profile(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
workspace_roots: session_configuration.workspace_roots.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
initial_messages,
|
||||
network_proxy: session_network_proxy.filter(|_| {
|
||||
|
||||
@@ -2064,8 +2064,10 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
|
||||
};
|
||||
let expected_sandbox_policy = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.permissions.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
config.permissions.replace_permission_profile_constraint(
|
||||
codex_config::Constrained::allow_any(PermissionProfile::from_legacy_sandbox_policy(
|
||||
&sandbox_policy,
|
||||
)),
|
||||
);
|
||||
config
|
||||
.set_legacy_sandbox_policy(sandbox_policy)
|
||||
@@ -2218,11 +2220,13 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
turn_id: Some(turn_context.sub_id.clone()),
|
||||
trace_id: turn_context.trace_id.clone(),
|
||||
cwd: turn_context.cwd.to_path_buf(),
|
||||
workspace_roots: turn_context.workspace_roots.clone(),
|
||||
current_date: turn_context.current_date.clone(),
|
||||
timezone: turn_context.timezone.clone(),
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
sandbox_policy: turn_context.sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
@@ -2819,10 +2823,11 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: Vec::new(),
|
||||
@@ -2923,10 +2928,11 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: Vec::new(),
|
||||
@@ -3189,6 +3195,7 @@ async fn attach_thread_persistence(session: &mut Session) -> PathBuf {
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
model_provider: config.model_provider_id.clone(),
|
||||
memory_mode: if config.memories.generate_memories {
|
||||
ThreadMemoryMode::Enabled
|
||||
@@ -3396,10 +3403,11 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: Vec::new(),
|
||||
@@ -3442,7 +3450,6 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd
|
||||
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -3476,9 +3483,15 @@ async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd
|
||||
.expect("cwd-only update should succeed");
|
||||
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy(),
|
||||
updated.permission_profile().file_system_sandbox_policy(),
|
||||
file_system_sandbox_policy
|
||||
);
|
||||
let expected_materialized_file_system_policy = file_system_sandbox_policy
|
||||
.materialize_project_roots_with_workspace_roots(&updated.workspace_roots);
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy(),
|
||||
expected_materialized_file_system_policy
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -3528,9 +3541,16 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
|
||||
expected_file_system_policy.glob_scan_max_depth = Some(2);
|
||||
expected_file_system_policy.entries.push(deny_entry);
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy(),
|
||||
updated.permission_profile().file_system_sandbox_policy(),
|
||||
expected_file_system_policy
|
||||
);
|
||||
let expected_materialized_file_system_policy = expected_file_system_policy
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&updated.workspace_roots);
|
||||
assert_eq!(
|
||||
updated.file_system_sandbox_policy(),
|
||||
expected_materialized_file_system_policy
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -3547,7 +3567,7 @@ async fn session_configuration_apply_permission_profile_accepts_direct_write_roo
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: external_write_path.clone(),
|
||||
path: external_write_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
@@ -3571,7 +3591,6 @@ async fn session_configuration_apply_permission_profile_accepts_direct_write_roo
|
||||
assert_eq!(
|
||||
updated.sandbox_policy(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![external_write_path],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -3666,14 +3685,15 @@ enabled = false
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_update() {
|
||||
async fn session_configuration_apply_preserves_legacy_workspace_roots_on_cwd_update() {
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let project_root = workspace.path().join("project");
|
||||
let original_cwd = project_root.join("subdir");
|
||||
session_configuration.cwd = original_cwd.abs();
|
||||
let original_cwd = original_cwd.abs();
|
||||
session_configuration.cwd = original_cwd.clone();
|
||||
session_configuration.workspace_roots = vec![original_cwd.clone()];
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
@@ -3692,20 +3712,61 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
cwd: Some(project_root.clone()),
|
||||
cwd: Some(project_root),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("cwd-only update should succeed");
|
||||
|
||||
let expected_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&updated.sandbox_policy(),
|
||||
&project_root,
|
||||
);
|
||||
let expected_file_system_policy = file_system_sandbox_policy
|
||||
.materialize_project_roots_with_workspace_roots(std::slice::from_ref(&original_cwd));
|
||||
assert_eq!(updated.workspace_roots, vec![original_cwd.clone()]);
|
||||
assert!(
|
||||
updated
|
||||
.file_system_sandbox_policy()
|
||||
.is_semantically_equivalent_to(&expected_file_system_policy, &project_root),
|
||||
"cwd-only update should rederive the legacy filesystem policy for the new cwd"
|
||||
.is_semantically_equivalent_to(&expected_file_system_policy, original_cwd.as_path()),
|
||||
"cwd-only update should preserve the existing workspace roots"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configuration_apply_preserves_active_permission_profile_on_legacy_cwd_update() {
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
let workspace = tempfile::tempdir().expect("create temp dir");
|
||||
let project_root = workspace.path().join("project");
|
||||
let original_cwd = project_root.join("subdir");
|
||||
let original_cwd = original_cwd.abs();
|
||||
session_configuration.cwd = original_cwd.clone();
|
||||
session_configuration.workspace_roots = vec![original_cwd];
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&sandbox_policy,
|
||||
&session_configuration.cwd,
|
||||
);
|
||||
session_configuration.permission_profile = codex_config::Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::from(&sandbox_policy),
|
||||
),
|
||||
);
|
||||
|
||||
let active_permission_profile =
|
||||
codex_protocol::models::ActivePermissionProfile::new(":workspace");
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
cwd: Some(project_root),
|
||||
active_permission_profile: Some(active_permission_profile.clone()),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("cwd and active permission profile update should succeed");
|
||||
|
||||
assert_eq!(
|
||||
updated.active_permission_profile(),
|
||||
Some(active_permission_profile)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3922,10 +3983,11 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: Vec::new(),
|
||||
@@ -4031,10 +4093,11 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: default_environments,
|
||||
@@ -4078,7 +4141,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.permission_profile,
|
||||
config.permissions.permission_profile_constraint(),
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -4263,10 +4326,11 @@ async fn make_session_with_config_and_rx(
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: default_environments,
|
||||
@@ -4366,10 +4430,11 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: default_environments,
|
||||
@@ -5017,6 +5082,7 @@ fn op_kind_distinguishes_turn_ops() {
|
||||
final_output_json_schema: None,
|
||||
responsesapi_client_metadata: None,
|
||||
cwd: None,
|
||||
workspace_roots: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
@@ -5412,6 +5478,7 @@ async fn shutdown_complete_does_not_append_to_thread_store_after_shutdown() {
|
||||
dynamic_tools: Vec::new(),
|
||||
metadata: ThreadPersistenceMetadata {
|
||||
cwd: Some(config.cwd.to_path_buf()),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
model_provider: config.model_provider_id.clone(),
|
||||
memory_mode: if config.memories.generate_memories {
|
||||
ThreadMemoryMode::Enabled
|
||||
@@ -5872,10 +5939,11 @@ where
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
permission_profile: config.permissions.permission_profile_constraint().clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
thread_name: None,
|
||||
environments: default_environments,
|
||||
@@ -5919,7 +5987,7 @@ where
|
||||
let services = SessionServices {
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.permission_profile,
|
||||
config.permissions.permission_profile_constraint(),
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
|
||||
@@ -70,6 +70,7 @@ pub struct TurnContext {
|
||||
/// by the model as well as sandbox policies are resolved against this path
|
||||
/// instead of `std::env::current_dir()`.
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
pub(crate) workspace_roots: Vec<AbsolutePathBuf>,
|
||||
pub(crate) current_date: Option<String>,
|
||||
pub(crate) timezone: Option<String>,
|
||||
pub(crate) app_server_client_name: Option<String>,
|
||||
@@ -104,7 +105,10 @@ impl TurnContext {
|
||||
}
|
||||
|
||||
pub(crate) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.permission_profile.file_system_sandbox_policy()
|
||||
self.permission_profile
|
||||
.clone()
|
||||
.materialize_project_roots_with_workspace_roots(&self.workspace_roots)
|
||||
.file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(crate) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
@@ -253,6 +257,7 @@ impl TurnContext {
|
||||
thread_source: self.thread_source,
|
||||
environments: self.environments.clone(),
|
||||
cwd: self.cwd.clone(),
|
||||
workspace_roots: self.workspace_roots.clone(),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
app_server_client_name: self.app_server_client_name.clone(),
|
||||
@@ -314,6 +319,7 @@ impl TurnContext {
|
||||
FileSystemSandboxContext {
|
||||
permissions,
|
||||
cwd: Some(self.cwd.clone()),
|
||||
workspace_roots: self.workspace_roots.clone(),
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: self
|
||||
.config
|
||||
@@ -349,11 +355,13 @@ impl TurnContext {
|
||||
turn_id: Some(self.sub_id.clone()),
|
||||
trace_id: self.trace_id.clone(),
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
workspace_roots: self.workspace_roots.clone(),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
approval_policy: self.approval_policy.value(),
|
||||
sandbox_policy: self.sandbox_policy(),
|
||||
permission_profile: Some(self.permission_profile()),
|
||||
active_permission_profile: self.config.permissions.active_permission_profile(),
|
||||
network: self.turn_context_network_item(),
|
||||
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
|
||||
model: self.model_info.slug.clone(),
|
||||
@@ -411,14 +419,19 @@ impl Session {
|
||||
let config = session_configuration.original_config_do_not_use.clone();
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.cwd = cwd;
|
||||
per_turn_config.workspace_roots = session_configuration.workspace_roots.clone();
|
||||
per_turn_config.model_reasoning_effort =
|
||||
session_configuration.collaboration_mode.reasoning_effort();
|
||||
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
|
||||
per_turn_config.service_tier = session_configuration.service_tier.clone();
|
||||
per_turn_config.personality = session_configuration.personality;
|
||||
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
|
||||
per_turn_config.permissions.permission_profile =
|
||||
session_configuration.permission_profile.clone();
|
||||
per_turn_config
|
||||
.permissions
|
||||
.replace_permission_profile_constraint_with_active_profile(
|
||||
session_configuration.permission_profile.clone(),
|
||||
session_configuration.active_permission_profile(),
|
||||
);
|
||||
let permission_profile = session_configuration.permission_profile();
|
||||
let resolved_web_search_mode =
|
||||
resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &permission_profile);
|
||||
@@ -565,6 +578,7 @@ impl Session {
|
||||
thread_source: session_configuration.thread_source,
|
||||
environments,
|
||||
cwd,
|
||||
workspace_roots: session_configuration.workspace_roots.clone(),
|
||||
current_date: Some(current_date),
|
||||
timezone: Some(timezone),
|
||||
app_server_client_name: session_configuration.app_server_client_name.clone(),
|
||||
|
||||
@@ -252,7 +252,6 @@ fn write_permissions_for_paths_skip_dirs_already_writable_under_workspace_root()
|
||||
let file_path = AbsolutePathBuf::try_from(nested.join("file.txt"))
|
||||
.expect("nested file path should be absolute");
|
||||
let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: false,
|
||||
@@ -274,7 +273,6 @@ fn write_permissions_for_paths_keep_dirs_outside_workspace_root() {
|
||||
.expect("outside file path should be absolute");
|
||||
let cwd_abs = cwd.abs();
|
||||
let sandbox_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
||||
@@ -269,6 +269,7 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
config.permissions.shell_environment_policy = turn.shell_environment_policy.clone();
|
||||
config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone();
|
||||
config.cwd = turn.cwd.clone();
|
||||
config.workspace_roots = turn.workspace_roots.clone();
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile(turn.permission_profile())
|
||||
|
||||
@@ -3770,7 +3770,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
turn.cwd = temp_dir.abs();
|
||||
turn.codex_linux_sandbox_exe = Some(PathBuf::from("/bin/echo"));
|
||||
let sandbox_policy = pick_allowed_sandbox_policy(
|
||||
&turn.config.permissions.permission_profile,
|
||||
turn.config.permissions.permission_profile_constraint(),
|
||||
turn.config.legacy_sandbox_policy(),
|
||||
turn.cwd.as_path(),
|
||||
);
|
||||
|
||||
@@ -85,6 +85,7 @@ impl ToolOrchestrator {
|
||||
enforce_managed_network: attempt.enforce_managed_network,
|
||||
manager: attempt.manager,
|
||||
sandbox_cwd: attempt.sandbox_cwd,
|
||||
workspace_roots: attempt.workspace_roots,
|
||||
codex_linux_sandbox_exe: attempt.codex_linux_sandbox_exe,
|
||||
use_legacy_landlock: attempt.use_legacy_landlock,
|
||||
windows_sandbox_level: attempt.windows_sandbox_level,
|
||||
@@ -241,6 +242,7 @@ impl ToolOrchestrator {
|
||||
enforce_managed_network: managed_network_active,
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd,
|
||||
workspace_roots: &turn_ctx.workspace_roots,
|
||||
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
|
||||
use_legacy_landlock,
|
||||
windows_sandbox_level: turn_ctx.windows_sandbox_level,
|
||||
@@ -359,6 +361,7 @@ impl ToolOrchestrator {
|
||||
enforce_managed_network: managed_network_active,
|
||||
manager: &self.sandbox,
|
||||
sandbox_cwd,
|
||||
workspace_roots: &turn_ctx.workspace_roots,
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_legacy_landlock,
|
||||
windows_sandbox_level: turn_ctx.windows_sandbox_level,
|
||||
|
||||
@@ -99,6 +99,7 @@ impl ApplyPatchRuntime {
|
||||
Some(FileSystemSandboxContext {
|
||||
permissions,
|
||||
cwd: Some(attempt.sandbox_cwd.clone()),
|
||||
workspace_roots: attempt.workspace_roots.to_vec(),
|
||||
windows_sandbox_level: attempt.windows_sandbox_level,
|
||||
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
|
||||
use_legacy_landlock: attempt.use_legacy_landlock,
|
||||
|
||||
@@ -214,6 +214,7 @@ async fn file_system_sandbox_context_uses_active_attempt() {
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &path,
|
||||
workspace_roots: std::slice::from_ref(&path),
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_legacy_landlock: true,
|
||||
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
||||
@@ -267,6 +268,7 @@ async fn no_sandbox_attempt_has_no_file_system_context() {
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &path,
|
||||
workspace_roots: std::slice::from_ref(&path),
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_legacy_landlock: false,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
|
||||
@@ -111,6 +111,7 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
|
||||
enforce_managed_network: false,
|
||||
manager: &manager,
|
||||
sandbox_cwd: &cwd,
|
||||
workspace_roots: std::slice::from_ref(&cwd),
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_legacy_landlock: false,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
|
||||
@@ -168,6 +168,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
windows_sandbox_level,
|
||||
arg0,
|
||||
sandbox_policy_cwd,
|
||||
workspace_roots: attempt.workspace_roots.to_vec(),
|
||||
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
|
||||
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
|
||||
};
|
||||
@@ -232,7 +233,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
|
||||
pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
req: &crate::tools::runtimes::unified_exec::UnifiedExecRequest,
|
||||
_attempt: &SandboxAttempt<'_>,
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
ctx: &ToolCtx,
|
||||
exec_request: ExecRequest,
|
||||
shell_zsh_path: &std::path::Path,
|
||||
@@ -269,6 +270,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
windows_sandbox_level: exec_request.windows_sandbox_level,
|
||||
arg0: exec_request.arg0.clone(),
|
||||
sandbox_policy_cwd: exec_request.windows_sandbox_policy_cwd.clone(),
|
||||
workspace_roots: attempt.workspace_roots.to_vec(),
|
||||
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
|
||||
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
|
||||
};
|
||||
@@ -753,6 +755,7 @@ struct CoreShellCommandExecutor {
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
arg0: Option<String>,
|
||||
sandbox_policy_cwd: AbsolutePathBuf,
|
||||
workspace_roots: Vec<AbsolutePathBuf>,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
use_legacy_landlock: bool,
|
||||
}
|
||||
@@ -923,6 +926,7 @@ impl CoreShellCommandExecutor {
|
||||
enforce_managed_network: self.network.is_some(),
|
||||
network: self.network.as_ref(),
|
||||
sandbox_policy_cwd: &self.sandbox_policy_cwd,
|
||||
workspace_roots: &self.workspace_roots,
|
||||
codex_linux_sandbox_exe: self.codex_linux_sandbox_exe.as_deref(),
|
||||
use_legacy_landlock: self.use_legacy_landlock,
|
||||
windows_sandbox_level: self.windows_sandbox_level,
|
||||
|
||||
@@ -385,6 +385,7 @@ pub(crate) struct SandboxAttempt<'a> {
|
||||
pub enforce_managed_network: bool,
|
||||
pub(crate) manager: &'a SandboxManager,
|
||||
pub(crate) sandbox_cwd: &'a AbsolutePathBuf,
|
||||
pub(crate) workspace_roots: &'a [AbsolutePathBuf],
|
||||
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
|
||||
pub use_legacy_landlock: bool,
|
||||
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
|
||||
@@ -407,6 +408,7 @@ impl<'a> SandboxAttempt<'a> {
|
||||
enforce_managed_network: self.enforce_managed_network,
|
||||
network,
|
||||
sandbox_policy_cwd: self.sandbox_cwd,
|
||||
workspace_roots: self.workspace_roots,
|
||||
codex_linux_sandbox_exe: self
|
||||
.codex_linux_sandbox_exe
|
||||
.map(std::path::PathBuf::as_path),
|
||||
|
||||
@@ -519,6 +519,7 @@ impl TestCodexBuilder {
|
||||
load_default_config_for_test(home).await
|
||||
};
|
||||
config.cwd = cwd_override;
|
||||
config.workspace_roots = vec![config.cwd.clone()];
|
||||
config.model_provider = model_provider;
|
||||
if let Ok(path) = codex_utils_cargo_bin::cargo_bin("codex") {
|
||||
config.codex_self_exe = Some(path);
|
||||
@@ -543,6 +544,7 @@ impl TestCodexBuilder {
|
||||
for mutator in mutators {
|
||||
mutator(&mut config);
|
||||
}
|
||||
config.workspace_roots = vec![config.cwd.clone()];
|
||||
ensure_test_model_catalog(&mut config)?;
|
||||
|
||||
if config.include_apply_patch_tool {
|
||||
|
||||
@@ -827,7 +827,6 @@ fn scenarios() -> Vec<ScenarioSpec> {
|
||||
use AskForApproval::*;
|
||||
|
||||
let workspace_write = |network_access| SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
@@ -2047,7 +2046,6 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file()
|
||||
let server = start_mock_server().await;
|
||||
let approval_policy = AskForApproval::OnRequest;
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
@@ -2803,7 +2801,6 @@ allow_local_binding = true
|
||||
)?;
|
||||
let approval_policy = AskForApproval::OnFailure;
|
||||
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
@@ -3083,7 +3080,6 @@ allow_local_binding = true
|
||||
)?;
|
||||
let approval_policy = AskForApproval::OnFailure;
|
||||
let turn_sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
|
||||
@@ -53,6 +53,7 @@ where
|
||||
params,
|
||||
&PermissionProfile::read_only(),
|
||||
&cwd,
|
||||
&[],
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
|
||||
@@ -575,9 +575,13 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
||||
let permissions = permissions_texts(&req.single_request());
|
||||
let normalize_line_endings = |s: &str| s.replace("\r\n", "\n");
|
||||
let exec_policy = load_exec_policy(&test.config.config_layer_stack).await?;
|
||||
let sandbox_policy = test.config.legacy_sandbox_policy();
|
||||
let expected = PermissionsInstructions::from_policy(
|
||||
&sandbox_policy,
|
||||
let permission_profile = test
|
||||
.config
|
||||
.permissions
|
||||
.permission_profile()
|
||||
.materialize_project_roots_with_workspace_roots(&test.config.workspace_roots);
|
||||
let expected = PermissionsInstructions::from_permission_profile(
|
||||
&permission_profile,
|
||||
AskForApproval::OnRequest,
|
||||
test.config.approvals_reviewer,
|
||||
&exec_policy,
|
||||
|
||||
@@ -63,6 +63,7 @@ async fn write_rollout_with_user_event(dir: &Path, thread_id: ThreadId) -> io::R
|
||||
forked_from_id: None,
|
||||
timestamp: TEST_TIMESTAMP.to_string(),
|
||||
cwd: std::path::PathBuf::from("."),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "test_originator".to_string(),
|
||||
cli_version: "test_version".to_string(),
|
||||
source: SessionSource::Cli,
|
||||
@@ -109,6 +110,7 @@ async fn write_rollout_with_meta_only(dir: &Path, thread_id: ThreadId) -> io::Re
|
||||
forked_from_id: None,
|
||||
timestamp: TEST_TIMESTAMP.to_string(),
|
||||
cwd: std::path::PathBuf::from("."),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "test_originator".to_string(),
|
||||
cli_version: "test_version".to_string(),
|
||||
source: SessionSource::Cli,
|
||||
|
||||
@@ -436,9 +436,12 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
|
||||
/*exclude_tmpdir_env_var*/ true,
|
||||
/*exclude_slash_tmp*/ true,
|
||||
);
|
||||
let sandbox_policy = permission_profile
|
||||
.to_legacy_sandbox_policy(config.cwd.as_path())
|
||||
.expect("workspace profile should have legacy projection");
|
||||
let sandbox_policy = codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
|
||||
&permission_profile,
|
||||
&permission_profile.file_system_sandbox_policy(),
|
||||
permission_profile.network_sandbox_policy(),
|
||||
config.cwd.as_path(),
|
||||
);
|
||||
codex
|
||||
.submit(Op::OverrideTurnContext {
|
||||
cwd: None,
|
||||
|
||||
@@ -285,7 +285,6 @@ async fn expect_request_permissions_event(
|
||||
|
||||
fn workspace_write_excluding_tmp() -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
|
||||
@@ -29,11 +29,13 @@ fn resume_history(
|
||||
turn_id: Some(turn_id.clone()),
|
||||
trace_id: None,
|
||||
cwd: config.cwd.to_path_buf(),
|
||||
workspace_roots: config.workspace_roots.clone(),
|
||||
current_date: None,
|
||||
timezone: None,
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.legacy_sandbox_policy(),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
model: previous_model.to_string(),
|
||||
|
||||
@@ -141,6 +141,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> {
|
||||
forked_from_id: None,
|
||||
timestamp: "2026-01-27T12:00:00Z".to_string(),
|
||||
cwd: codex_home.to_path_buf(),
|
||||
workspace_roots: Vec::new(),
|
||||
originator: "test".to_string(),
|
||||
cli_version: "test".to_string(),
|
||||
source: SessionSource::default(),
|
||||
|
||||
@@ -906,9 +906,11 @@ allow_local_binding = true
|
||||
.enable(Feature::UnifiedExec)
|
||||
.expect("test config should allow feature update");
|
||||
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::Never);
|
||||
config.permissions.permission_profile = Constrained::allow_any(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy_for_config),
|
||||
);
|
||||
config
|
||||
.permissions
|
||||
.replace_permission_profile_constraint(Constrained::allow_any(
|
||||
PermissionProfile::from_legacy_sandbox_policy(&sandbox_policy_for_config),
|
||||
));
|
||||
});
|
||||
let test = builder.build_remote_aware(server).await?;
|
||||
assert!(
|
||||
@@ -2752,10 +2754,13 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
});
|
||||
config.permissions.permission_profile =
|
||||
Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
config
|
||||
.permissions
|
||||
.replace_permission_profile_constraint(Constrained::allow_any(
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
));
|
||||
});
|
||||
let TestCodex {
|
||||
|
||||
@@ -344,11 +344,12 @@ async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Re
|
||||
let server = responses::start_mock_server().await;
|
||||
let mut builder = core_test_support::test_codex::test_codex().with_config(|config| {
|
||||
let file_system_sandbox_policy = config.permissions.file_system_sandbox_policy();
|
||||
config.permissions.permission_profile =
|
||||
config.permissions.replace_permission_profile_constraint(
|
||||
codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
));
|
||||
)),
|
||||
);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> a
|
||||
},
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
std::slice::from_ref(&cwd),
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
@@ -211,6 +212,7 @@ async fn windows_elevated_enforces_exact_and_glob_deny_read_policy() -> anyhow::
|
||||
},
|
||||
&permission_profile,
|
||||
&cwd,
|
||||
std::slice::from_ref(&cwd),
|
||||
&None,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*stdout_stream*/ None,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user