mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
permissions: expose active profile metadata (#20095)
This commit is contained in:
@@ -65,7 +65,6 @@ use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::NonSteerableTurnKind;
|
||||
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::ServerNotification;
|
||||
@@ -158,15 +157,12 @@ fn sample_thread_start_response(
|
||||
approval_policy: AppServerAskForApproval::OnFailure,
|
||||
approvals_reviewer: AppServerApprovalsReviewer::User,
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(sample_permission_profile()),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn sample_permission_profile() -> AppServerPermissionProfile {
|
||||
CorePermissionProfile::Disabled.into()
|
||||
}
|
||||
|
||||
fn sample_app_server_client_metadata() -> CodexAppServerClientMetadata {
|
||||
CodexAppServerClientMetadata {
|
||||
product_client_id: DEFAULT_ORIGINATOR.to_string(),
|
||||
@@ -215,7 +211,8 @@ fn sample_thread_resume_response_with_source(
|
||||
approval_policy: AppServerAskForApproval::OnFailure,
|
||||
approvals_reviewer: AppServerApprovalsReviewer::User,
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(sample_permission_profile()),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ fn sample_thread_start_response() -> ClientResponsePayload {
|
||||
approvals_reviewer: AppServerApprovalsReviewer::User,
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(sample_permission_profile()),
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
@@ -127,6 +128,7 @@ fn sample_thread_resume_response() -> ClientResponsePayload {
|
||||
approvals_reviewer: AppServerApprovalsReviewer::User,
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(sample_permission_profile()),
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
@@ -143,6 +145,7 @@ fn sample_thread_fork_response() -> ClientResponsePayload {
|
||||
approvals_reviewer: AppServerApprovalsReviewer::User,
|
||||
sandbox: AppServerSandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(sample_permission_profile()),
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2039,6 +2039,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -2050,6 +2075,40 @@
|
||||
],
|
||||
"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",
|
||||
|
||||
+115
-3
@@ -5413,6 +5413,59 @@
|
||||
"title": "AccountUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"id"
|
||||
],
|
||||
"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",
|
||||
@@ -11688,6 +11741,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -11699,6 +11777,40 @@
|
||||
],
|
||||
"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",
|
||||
@@ -14880,7 +14992,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -16391,7 +16503,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -16695,7 +16807,7 @@
|
||||
"$ref": "#/definitions/v2/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
+115
-3
@@ -130,6 +130,59 @@
|
||||
"title": "AccountUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"id"
|
||||
],
|
||||
"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",
|
||||
@@ -8342,6 +8395,31 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
@@ -8353,6 +8431,40 @@
|
||||
],
|
||||
"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",
|
||||
@@ -12766,7 +12878,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -14277,7 +14389,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
@@ -14581,7 +14693,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
+15
-281
@@ -64,26 +64,19 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -91,304 +84,45 @@
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"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"
|
||||
],
|
||||
"id": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SandboxMode": {
|
||||
"enum": [
|
||||
"read-only",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"id"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
+21
-287
@@ -138,202 +138,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FunctionCallOutputBody": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -494,135 +298,65 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"path",
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"id"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
+15
-281
@@ -90,26 +90,19 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -117,304 +110,45 @@
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"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"
|
||||
],
|
||||
"id": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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": {
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -5,6 +5,59 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"ActivePermissionProfile": {
|
||||
"properties": {
|
||||
"extends": {
|
||||
"default": null,
|
||||
"description": "Parent profile identifier once permissions profiles support inheritance. This is currently always `null`.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"id"
|
||||
],
|
||||
"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"
|
||||
},
|
||||
@@ -2501,7 +2554,7 @@
|
||||
"$ref": "#/definitions/SandboxPolicy"
|
||||
}
|
||||
],
|
||||
"description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view."
|
||||
"description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `permissionProfile` when they need exact runtime permissions."
|
||||
},
|
||||
"serviceTier": {
|
||||
"anyOf": [
|
||||
|
||||
+21
-287
@@ -99,202 +99,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ModeKind": {
|
||||
"description": "Initial collaboration mode to use when the TUI starts.",
|
||||
"enum": [
|
||||
@@ -310,135 +114,65 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"PermissionProfileModificationParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"path",
|
||||
"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",
|
||||
"title": "AdditionalWritableRootPermissionProfileModificationParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"PermissionProfileSelectionParams": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Select a named built-in or user-defined profile and optionally apply bounded modifications that Codex knows how to validate.",
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"modifications": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/PermissionProfileModificationParams"
|
||||
},
|
||||
"type": [
|
||||
"integer",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
"profile"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "ProfilePermissionProfileSelectionParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"id",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "ProfilePermissionProfileSelectionParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// 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 = {
|
||||
/**
|
||||
* Identifier from `default_permissions` or the implicit built-in default,
|
||||
* such as `:workspace` or a user-defined `[permissions.<id>]` profile.
|
||||
*/
|
||||
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>, };
|
||||
+6
@@ -0,0 +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";
|
||||
|
||||
export type ActivePermissionProfileModification = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
|
||||
+6
@@ -0,0 +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";
|
||||
|
||||
export type PermissionProfileModificationParams = { "type": "additionalWritableRoot", path: AbsolutePathBuf, };
|
||||
+6
@@ -0,0 +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 { PermissionProfileModificationParams } from "./PermissionProfileModificationParams";
|
||||
|
||||
export type PermissionProfileSelectionParams = { "type": "profile", id: string, modifications?: Array<PermissionProfileModificationParams> | null, };
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -16,8 +16,8 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval, /**
|
||||
* Reviewer currently used for approval requests on this thread.
|
||||
*/
|
||||
approvalsReviewer: ApprovalsReviewer, /**
|
||||
* Legacy sandbox policy retained for compatibility. New clients should use
|
||||
* `permissionProfile` when present as the canonical active permissions
|
||||
* view.
|
||||
* Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
* should prefer `permissionProfile` when they need exact runtime
|
||||
* permissions.
|
||||
*/
|
||||
sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null};
|
||||
|
||||
@@ -4,6 +4,8 @@ export type { Account } from "./Account";
|
||||
export type { AccountLoginCompletedNotification } from "./AccountLoginCompletedNotification";
|
||||
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";
|
||||
@@ -261,7 +263,9 @@ 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";
|
||||
|
||||
@@ -2131,12 +2131,8 @@ mod tests {
|
||||
approval_policy: v2::AskForApproval::OnFailure,
|
||||
approvals_reviewer: v2::ApprovalsReviewer::User,
|
||||
sandbox: v2::SandboxPolicy::DangerFullAccess,
|
||||
permission_profile: Some(
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
},
|
||||
};
|
||||
@@ -2179,9 +2175,8 @@ mod tests {
|
||||
"sandbox": {
|
||||
"type": "dangerFullAccess"
|
||||
},
|
||||
"permissionProfile": {
|
||||
"type": "disabled"
|
||||
},
|
||||
"permissionProfile": null,
|
||||
"activePermissionProfile": null,
|
||||
"reasoningEffort": null
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -38,6 +38,8 @@ use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
|
||||
use codex_protocol::mcp::Tool as McpTool;
|
||||
use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation;
|
||||
use codex_protocol::memory_citation::MemoryCitationEntry as CoreMemoryCitationEntry;
|
||||
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;
|
||||
@@ -1703,6 +1705,109 @@ impl From<PermissionProfile> for CorePermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ActivePermissionProfile {
|
||||
/// Identifier from `default_permissions` or the implicit built-in default,
|
||||
/// such as `:workspace` or a user-defined `[permissions.<id>]` profile.
|
||||
pub id: String,
|
||||
/// Parent profile identifier once permissions profiles support
|
||||
/// 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 {
|
||||
fn from(value: CoreActivePermissionProfile) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
extends: value.extends,
|
||||
modifications: value
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(ActivePermissionProfileModification::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActivePermissionProfile> for CoreActivePermissionProfile {
|
||||
fn from(value: ActivePermissionProfile) -> Self {
|
||||
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/")]
|
||||
@@ -3462,11 +3567,12 @@ pub struct ThreadStartParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for this thread. Cannot be combined with
|
||||
/// `sandbox`.
|
||||
#[experimental("thread/start.permissionProfile")]
|
||||
/// 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.
|
||||
#[experimental("thread/start.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, JsonValue>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3543,14 +3649,20 @@ pub struct ThreadStartResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// 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")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -3608,11 +3720,12 @@ pub struct ThreadResumeParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for the resumed thread. Cannot be combined
|
||||
/// with `sandbox`.
|
||||
#[experimental("thread/resume.permissionProfile")]
|
||||
/// 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.
|
||||
#[experimental("thread/resume.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3649,14 +3762,20 @@ pub struct ThreadResumeResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// 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")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -3705,11 +3824,12 @@ pub struct ThreadForkParams {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox: Option<SandboxMode>,
|
||||
/// Full permissions override for the forked thread. Cannot be combined
|
||||
/// with `sandbox`.
|
||||
#[experimental("thread/fork.permissionProfile")]
|
||||
/// 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.
|
||||
#[experimental("thread/fork.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
#[ts(optional = nullable)]
|
||||
pub config: Option<HashMap<String, serde_json::Value>>,
|
||||
#[ts(optional = nullable)]
|
||||
@@ -3746,14 +3866,20 @@ pub struct ThreadForkResponse {
|
||||
pub approval_policy: AskForApproval,
|
||||
/// Reviewer currently used for approval requests on this thread.
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
/// Legacy sandbox policy retained for compatibility. New clients should use
|
||||
/// `permissionProfile` when present as the canonical active permissions
|
||||
/// view.
|
||||
/// Legacy sandbox policy retained for compatibility. Experimental clients
|
||||
/// should prefer `permissionProfile` when they need exact runtime
|
||||
/// permissions.
|
||||
pub sandbox: SandboxPolicy,
|
||||
/// Canonical active permissions view for this thread.
|
||||
/// 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")]
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
}
|
||||
|
||||
@@ -5338,11 +5464,13 @@ pub struct TurnStartParams {
|
||||
/// Override the sandbox policy for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
/// Override the full permissions profile for this turn and subsequent
|
||||
/// turns. Cannot be combined with `sandboxPolicy`.
|
||||
#[experimental("turn/start.permissionProfile")]
|
||||
/// 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.
|
||||
#[experimental("turn/start.permissions")]
|
||||
#[ts(optional = nullable)]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permissions: Option<PermissionProfileSelectionParams>,
|
||||
/// Override the model for this turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub model: Option<String>,
|
||||
@@ -10790,6 +10918,9 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10817,7 +10948,7 @@ mod tests {
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
effort: None,
|
||||
|
||||
@@ -88,7 +88,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat
|
||||
- Initialize once per connection: Immediately after opening a transport connection, send an `initialize` request with your client metadata, then emit an `initialized` notification. Any other request on that connection before this handshake gets rejected.
|
||||
- Start (or resume) a thread: Call `thread/start` to open a fresh conversation. The response returns the thread object and you’ll also get a `thread/started` notification. If you’re continuing an existing conversation, call `thread/resume` with its ID instead. If you want to branch from an existing conversation, call `thread/fork` to create a new thread id with copied history. Like `thread/start`, `thread/fork` also accepts `ephemeral: true` for an in-memory temporary thread.
|
||||
The returned `thread.ephemeral` flag tells you whether the session is intentionally in-memory only; when it is `true`, `thread.path` is `null`.
|
||||
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy or `permissionProfile`, approval policy, approvals reviewer, etc. This immediately returns the new turn object. The app-server emits `turn/started` when that turn actually begins running.
|
||||
- Begin a turn: To send user input, call `turn/start` with the target `threadId` and the user's input. Optional fields let you override model, cwd, sandbox policy or experimental `permissions` profile selection, approval policy, approvals reviewer, etc. This immediately returns the new turn object. The app-server emits `turn/started` when that turn actually begins running.
|
||||
- Stream events: After `turn/start`, keep reading JSON-RPC notifications on stdout. You’ll see `item/started`, `item/completed`, deltas like `item/agentMessage/delta`, tool progress, etc. These represent streaming model output plus any side effects (commands, tool calls, reasoning notes).
|
||||
- Finish the turn: When the model is done (or the turn is interrupted via making the `turn/interrupt` call), the server sends `turn/completed` with the final turn state and token usage.
|
||||
|
||||
@@ -142,9 +142,10 @@ Example with notification opt-out:
|
||||
|
||||
## API Overview
|
||||
|
||||
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. For permissions, prefer `permissionProfile`; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissionProfile`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`.
|
||||
- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. For permissions, prefer experimental `permissions` profile selection; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissions`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`.
|
||||
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. Accepts the same permission override rules as `thread/start`.
|
||||
- `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. Pass `excludeTurns: true` when the client plans to page fork history via `thread/turns/list` instead of receiving the full turn array immediately. Accepts the same permission override rules as `thread/start`.
|
||||
- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. Experimental clients can read response `permissionProfile` for the exact active runtime permissions and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known.
|
||||
- `thread/list` — page through stored rollouts; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
|
||||
- `thread/loaded/list` — list the thread ids currently loaded in memory.
|
||||
- `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded.
|
||||
@@ -166,7 +167,7 @@ Example with notification opt-out:
|
||||
- `thread/shellCommand` — run a user-initiated `!` shell command against a thread; this runs unsandboxed with full access rather than inheriting the thread sandbox policy. Returns `{}` immediately while progress streams through standard turn/item notifications and any active turn receives the formatted output in its message stream.
|
||||
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
|
||||
- `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success.
|
||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. Prefer `permissionProfile` for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissionProfile`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. Prefer experimental `permissions` profile selection for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
||||
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
|
||||
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. Review and manual compaction turns reject `turn/steer`.
|
||||
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
|
||||
@@ -238,8 +239,9 @@ Start a fresh thread when you need a new Codex conversation.
|
||||
"cwd": "/Users/me/project",
|
||||
"approvalPolicy": "never",
|
||||
"sandbox": "workspaceWrite",
|
||||
// Prefer "permissionProfile" for full permission overrides. Do not send
|
||||
// both "sandbox" and "permissionProfile".
|
||||
// Prefer experimental profile selection:
|
||||
// "permissions": { "type": "profile", "id": ":workspace" }
|
||||
// Do not send both "sandbox" and "permissions".
|
||||
"personality": "friendly",
|
||||
"serviceName": "my_app_server_client", // optional metrics tag (`service_name`)
|
||||
"sessionStartSource": "startup", // optional: "startup" (default) or "clear"
|
||||
@@ -636,8 +638,9 @@ You can optionally specify config overrides on the new turn. If specified, these
|
||||
"writableRoots": ["/Users/me/project"],
|
||||
"networkAccess": true
|
||||
},
|
||||
// Prefer "permissionProfile" for full permission overrides. Do not send
|
||||
// both "sandboxPolicy" and "permissionProfile".
|
||||
// Prefer experimental profile selection:
|
||||
// "permissions": { "type": "profile", "id": ":workspace" }
|
||||
// Do not send both "sandboxPolicy" and "permissions".
|
||||
"model": "gpt-5.1-codex",
|
||||
"effort": "medium",
|
||||
"summary": "concise",
|
||||
|
||||
@@ -109,7 +109,8 @@ 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::PermissionProfile as ApiPermissionProfile;
|
||||
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;
|
||||
@@ -2438,7 +2439,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config,
|
||||
service_name,
|
||||
base_instructions,
|
||||
@@ -2452,11 +2453,11 @@ impl CodexMessageProcessor {
|
||||
environments,
|
||||
persist_extended_history,
|
||||
} = params;
|
||||
if sandbox.is_some() && permission_profile.is_some() {
|
||||
if sandbox.is_some() && permissions.is_some() {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
invalid_request("`permissionProfile` cannot be combined with `sandbox`"),
|
||||
invalid_request("`permissions` cannot be combined with `sandbox`"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
@@ -2491,7 +2492,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
@@ -2550,7 +2551,7 @@ impl CodexMessageProcessor {
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*sandbox*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*permissions*/ None,
|
||||
/*base_instructions*/ None,
|
||||
/*developer_instructions*/ None,
|
||||
/*personality*/ None,
|
||||
@@ -2861,8 +2862,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile);
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadStartResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -2874,7 +2876,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: config_snapshot.approval_policy.into(),
|
||||
approvals_reviewer: config_snapshot.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: config_snapshot.reasoning_effort,
|
||||
};
|
||||
Ok::<_, JSONRPCErrorError>((response, thread_started_notification(thread)))
|
||||
@@ -2920,12 +2923,12 @@ impl CodexMessageProcessor {
|
||||
approval_policy: Option<codex_app_server_protocol::AskForApproval>,
|
||||
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
|
||||
sandbox: Option<SandboxMode>,
|
||||
permission_profile: Option<ApiPermissionProfile>,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
base_instructions: Option<String>,
|
||||
developer_instructions: Option<String>,
|
||||
personality: Option<Personality>,
|
||||
) -> ConfigOverrides {
|
||||
ConfigOverrides {
|
||||
let mut overrides = ConfigOverrides {
|
||||
model,
|
||||
model_provider,
|
||||
service_tier,
|
||||
@@ -2935,14 +2938,15 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer: approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
|
||||
sandbox_mode: sandbox.map(SandboxMode::to_core),
|
||||
permission_profile: permission_profile.map(Into::into),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
apply_permission_profile_selection_to_config_overrides(&mut overrides, permissions);
|
||||
overrides
|
||||
}
|
||||
|
||||
async fn thread_archive(&self, request_id: ConnectionRequestId, params: ThreadArchiveParams) {
|
||||
@@ -4223,11 +4227,11 @@ impl CodexMessageProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if params.sandbox.is_some() && params.permission_profile.is_some() {
|
||||
if params.sandbox.is_some() && params.permissions.is_some() {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
invalid_request("`permissionProfile` cannot be combined with `sandbox`"),
|
||||
invalid_request("`permissions` cannot be combined with `sandbox`"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
@@ -4260,7 +4264,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: mut request_overrides,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
@@ -4295,7 +4299,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
@@ -4401,8 +4405,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile.clone());
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -4414,7 +4419,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -4813,7 +4819,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: cli_overrides,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
@@ -4823,9 +4829,9 @@ impl CodexMessageProcessor {
|
||||
} = params;
|
||||
let include_turns = !exclude_turns;
|
||||
let result = async {
|
||||
if sandbox.is_some() && permission_profile.is_some() {
|
||||
if sandbox.is_some() && permissions.is_some() {
|
||||
return Err(invalid_request(
|
||||
"`permissionProfile` cannot be combined with `sandbox`",
|
||||
"`permissions` cannot be combined with `sandbox`",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -4878,7 +4884,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
/*personality*/ None,
|
||||
@@ -4993,8 +4999,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile);
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadForkResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -5006,7 +5013,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -6581,7 +6589,7 @@ impl CodexMessageProcessor {
|
||||
|| params.approval_policy.is_some()
|
||||
|| params.approvals_reviewer.is_some()
|
||||
|| params.sandbox_policy.is_some()
|
||||
|| params.permission_profile.is_some()
|
||||
|| params.permissions.is_some()
|
||||
|| params.model.is_some()
|
||||
|| params.service_tier.is_some()
|
||||
|| params.effort.is_some()
|
||||
@@ -6589,9 +6597,9 @@ impl CodexMessageProcessor {
|
||||
|| collaboration_mode.is_some()
|
||||
|| params.personality.is_some();
|
||||
|
||||
if params.sandbox_policy.is_some() && params.permission_profile.is_some() {
|
||||
if params.sandbox_policy.is_some() && params.permissions.is_some() {
|
||||
return Err(invalid_request(
|
||||
"`permissionProfile` cannot be combined with `sandboxPolicy`",
|
||||
"`permissions` cannot be combined with `sandboxPolicy`",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -6601,7 +6609,45 @@ impl CodexMessageProcessor {
|
||||
.approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core);
|
||||
let sandbox_policy = params.sandbox_policy.map(|p| p.to_core());
|
||||
let permission_profile = params.permission_profile.map(Into::into);
|
||||
let (permission_profile, active_permission_profile) =
|
||||
if let Some(permissions) = params.permissions {
|
||||
let snapshot = thread.config_snapshot().await;
|
||||
let mut overrides = ConfigOverrides {
|
||||
cwd: cwd.clone(),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
apply_permission_profile_selection_to_config_overrides(
|
||||
&mut overrides,
|
||||
Some(permissions),
|
||||
);
|
||||
let config = self
|
||||
.config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
overrides,
|
||||
Some(snapshot.cwd.to_path_buf()),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| config_load_error(&err))?;
|
||||
// Startup config is allowed to fall back when requirements
|
||||
// disallow a configured profile. An explicit turn request
|
||||
// is different: reject it before accepting user input.
|
||||
if let Some(warning) = config.startup_warnings.iter().find(|warning| {
|
||||
warning.contains("Configured value for `permission_profile` is disallowed")
|
||||
}) {
|
||||
return Err(invalid_request(format!(
|
||||
"invalid turn context override: {warning}"
|
||||
)));
|
||||
}
|
||||
(
|
||||
Some(config.permissions.permission_profile()),
|
||||
config.permissions.active_permission_profile(),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let model = params.model;
|
||||
let effort = params.effort.map(Some);
|
||||
let summary = params.summary;
|
||||
@@ -6619,6 +6665,7 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer,
|
||||
sandbox_policy: sandbox_policy.clone(),
|
||||
permission_profile: permission_profile.clone(),
|
||||
active_permission_profile: active_permission_profile.clone(),
|
||||
windows_sandbox_level: None,
|
||||
model: model.clone(),
|
||||
effort,
|
||||
@@ -6645,6 +6692,7 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level: None,
|
||||
model,
|
||||
effort,
|
||||
@@ -8314,13 +8362,15 @@ async fn handle_pending_thread_resume_request(
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
cwd,
|
||||
reasoning_effort,
|
||||
..
|
||||
} = pending.config_snapshot;
|
||||
let instruction_sources = pending.instruction_sources;
|
||||
let sandbox = thread_response_sandbox_policy(&permission_profile, cwd.as_path());
|
||||
let permission_profile = thread_response_permission_profile(permission_profile);
|
||||
let active_permission_profile =
|
||||
thread_response_active_permission_profile(active_permission_profile);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -8332,7 +8382,8 @@ async fn handle_pending_thread_resume_request(
|
||||
approval_policy: approval_policy.into(),
|
||||
approvals_reviewer: approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort,
|
||||
};
|
||||
let token_usage_thread = pending.include_turns.then(|| response.thread.clone());
|
||||
@@ -8559,15 +8610,11 @@ fn collect_resume_override_mismatches(
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(requested_permission_profile) = request.permission_profile.as_ref() {
|
||||
let requested_permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from(requested_permission_profile.clone());
|
||||
if requested_permission_profile != config_snapshot.permission_profile {
|
||||
mismatch_details.push(format!(
|
||||
"permission_profile requested={requested_permission_profile:?} active={:?}",
|
||||
config_snapshot.permission_profile
|
||||
));
|
||||
}
|
||||
if request.permissions.is_some() {
|
||||
mismatch_details.push(format!(
|
||||
"permissions override was provided and ignored while running; active={:?}",
|
||||
config_snapshot.active_permission_profile
|
||||
));
|
||||
}
|
||||
if let Some(requested_personality) = request.personality.as_ref()
|
||||
&& config_snapshot.personality.as_ref() != Some(requested_personality)
|
||||
@@ -9474,10 +9521,29 @@ fn with_thread_spawn_agent_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_response_permission_profile(
|
||||
permission_profile: codex_protocol::models::PermissionProfile,
|
||||
) -> Option<codex_app_server_protocol::PermissionProfile> {
|
||||
Some(permission_profile.into())
|
||||
fn thread_response_active_permission_profile(
|
||||
active_permission_profile: Option<codex_protocol::models::ActivePermissionProfile>,
|
||||
) -> Option<codex_app_server_protocol::ActivePermissionProfile> {
|
||||
active_permission_profile.map(Into::into)
|
||||
}
|
||||
|
||||
fn apply_permission_profile_selection_to_config_overrides(
|
||||
overrides: &mut ConfigOverrides,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
) {
|
||||
let Some(PermissionProfileSelectionParams::Profile { id, modifications }) = 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()
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn thread_response_sandbox_policy(
|
||||
@@ -9505,6 +9571,13 @@ fn requested_permissions_trust_project(overrides: &ConfigOverrides, cwd: &Path)
|
||||
return true;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
overrides.default_permissions.as_deref(),
|
||||
Some(":workspace" | ":danger-no-sandbox")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
overrides
|
||||
.permission_profile
|
||||
.as_ref()
|
||||
@@ -10021,23 +10094,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_response_permission_profile_preserves_enforcement() {
|
||||
let full_access_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let external_profile = codex_protocol::models::PermissionProfile::External {
|
||||
network: codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(external_profile.clone()),
|
||||
Some(external_profile.into())
|
||||
);
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(full_access_profile.clone()),
|
||||
Some(full_access_profile.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requested_permissions_trust_project_uses_permission_profile_intent() {
|
||||
let cwd = test_path_buf("/tmp/project").abs();
|
||||
@@ -10082,6 +10138,20 @@ mod tests {
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":danger-no-sandbox".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(!requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
permission_profile: Some(read_only_profile),
|
||||
@@ -10089,6 +10159,13 @@ mod tests {
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(!requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":read-only".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10259,7 +10336,7 @@ mod tests {
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
config: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
@@ -10274,6 +10351,7 @@ mod tests {
|
||||
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
|
||||
active_permission_profile: None,
|
||||
cwd,
|
||||
ephemeral: false,
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -761,7 +761,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
approvals_reviewer: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
|
||||
@@ -667,7 +667,7 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<(
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
config: None,
|
||||
service_name: None,
|
||||
base_instructions: None,
|
||||
|
||||
@@ -27,9 +27,6 @@ use codex_app_server_protocol::FileChangeApprovalDecision;
|
||||
use codex_app_server_protocol::FileChangeOutputDeltaNotification;
|
||||
use codex_app_server_protocol::FileChangePatchUpdatedNotification;
|
||||
use codex_app_server_protocol::FileChangeRequestApprovalResponse;
|
||||
use codex_app_server_protocol::FileSystemAccessMode;
|
||||
use codex_app_server_protocol::FileSystemPath;
|
||||
use codex_app_server_protocol::FileSystemSandboxEntry;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
@@ -38,9 +35,7 @@ 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::PermissionProfile;
|
||||
use codex_app_server_protocol::PermissionProfileFileSystemPermissions;
|
||||
use codex_app_server_protocol::PermissionProfileNetworkPermissions;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ServerRequest;
|
||||
use codex_app_server_protocol::ServerRequestResolvedNotification;
|
||||
@@ -67,7 +62,6 @@ use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -675,9 +669,8 @@ async fn turn_start_rejects_combined_oversized_text_input() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() -> Result<()> {
|
||||
async fn turn_start_rejects_invalid_permission_selection_before_starting_turn() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let disallowed_write_root = TempDir::new()?;
|
||||
create_config_toml(
|
||||
codex_home.path(),
|
||||
"http://localhost/unused",
|
||||
@@ -704,9 +697,6 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
)
|
||||
.await??;
|
||||
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
let disallowed_write_root = AbsolutePathBuf::from_absolute_path(disallowed_write_root.path())
|
||||
.expect("tempdir path should be absolute");
|
||||
|
||||
let turn_req = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id,
|
||||
@@ -714,17 +704,9 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
text: "Hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
permission_profile: Some(PermissionProfile::Managed {
|
||||
network: PermissionProfileNetworkPermissions { enabled: false },
|
||||
file_system: PermissionProfileFileSystemPermissions::Restricted {
|
||||
entries: vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: disallowed_write_root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
permissions: Some(PermissionProfileSelectionParams::Profile {
|
||||
id: ":danger-no-sandbox".to_string(),
|
||||
modifications: None,
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
@@ -749,7 +731,7 @@ async fn turn_start_rejects_invalid_permission_profile_before_starting_turn() ->
|
||||
.await;
|
||||
assert!(
|
||||
turn_started.is_err(),
|
||||
"did not expect a turn/started notification after rejected permissionProfile"
|
||||
"did not expect a turn/started notification after rejected permissions selection"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -1833,7 +1815,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
}),
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
model: Some("mock-model".to_string()),
|
||||
effort: Some(ReasoningEffort::Medium),
|
||||
summary: Some(ReasoningSummary::Auto),
|
||||
@@ -1869,7 +1851,7 @@ async fn turn_start_updates_sandbox_and_cwd_between_turns_v2() -> Result<()> {
|
||||
approval_policy: Some(codex_app_server_protocol::AskForApproval::Never),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: Some(codex_app_server_protocol::SandboxPolicy::DangerFullAccess),
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
model: Some("mock-model".to_string()),
|
||||
effort: Some(ReasoningEffort::Medium),
|
||||
summary: Some(ReasoningSummary::Auto),
|
||||
|
||||
@@ -15,6 +15,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
@@ -48,6 +49,7 @@ pub struct ThreadConfigSnapshot {
|
||||
pub approval_policy: AskForApproval,
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
pub permission_profile: PermissionProfile,
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub ephemeral: bool,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
@@ -75,6 +77,7 @@ pub struct CodexThreadTurnContextOverrides {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
pub model: Option<String>,
|
||||
pub effort: Option<Option<ReasoningEffort>>,
|
||||
@@ -225,6 +228,7 @@ impl CodexThread {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
effort,
|
||||
@@ -249,6 +253,7 @@ impl CodexThread {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
reasoning_summary: summary,
|
||||
|
||||
@@ -60,6 +60,8 @@ use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
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;
|
||||
@@ -907,6 +909,14 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some("workspace")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -928,6 +938,7 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.permissions.permission_profile(), permission_profile);
|
||||
assert_eq!(config.permissions.active_permission_profile(), None);
|
||||
assert_eq!(
|
||||
&config.legacy_sandbox_policy(),
|
||||
&SandboxPolicy::DangerFullAccess
|
||||
@@ -1267,6 +1278,14 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(":workspace")
|
||||
);
|
||||
assert!(
|
||||
policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
|
||||
"expected :workspace to allow writing the project root, policy: {policy:?}"
|
||||
@@ -1278,6 +1297,86 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_permissions_read_only_applies_additional_writable_roots_as_modifications()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let extra_root = TempDir::new()?;
|
||||
let extra_root = extra_root.path().abs();
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some(":read-only".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
additional_writable_roots: vec![extra_root.to_path_buf()],
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.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:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.active_permission_profile(),
|
||||
Some(
|
||||
ActivePermissionProfile::new(":read-only").with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path: extra_root },
|
||||
])
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn explicit_builtin_workspace_profile_ignores_legacy_workspace_write_settings()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let extra_root = TempDir::new()?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
|
||||
writable_roots: vec![extra_root.path().abs()],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert!(
|
||||
!policy.entries.iter().any(|entry| matches!(
|
||||
&entry.path,
|
||||
FileSystemPath::Path { path } if path.as_path() == extra_root.path()
|
||||
)),
|
||||
"explicit :workspace should not inherit sandbox_workspace_write roots as concrete grants, \
|
||||
policy: {policy:?}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -1303,6 +1402,18 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(if cfg!(target_os = "windows") {
|
||||
":read-only"
|
||||
} else {
|
||||
":workspace"
|
||||
})
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
!policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
|
||||
@@ -1367,6 +1478,12 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.active_permission_profile(),
|
||||
None,
|
||||
"implicit :workspace cannot be faithfully re-selected when it includes \
|
||||
legacy sandbox_workspace_write settings"
|
||||
);
|
||||
match config.legacy_sandbox_policy() {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
@@ -1484,6 +1601,14 @@ async fn default_permissions_can_select_builtin_no_sandbox_profile() -> std::io:
|
||||
config.permissions.permission_profile(),
|
||||
PermissionProfile::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(":danger-no-sandbox")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6151,6 +6276,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6346,6 +6472,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6495,6 +6622,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6629,6 +6757,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -7518,6 +7647,41 @@ async fn permission_profile_override_falls_back_when_disallowed_by_requirements(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let requirements = codex_config::ConfigRequirementsToml {
|
||||
allowed_sandbox_modes: Some(vec![codex_config::SandboxModeRequirement::ReadOnly]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.harness_overrides(ConfigOverrides {
|
||||
default_permissions: Some(":danger-no-sandbox".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async move {
|
||||
Ok(Some(requirements))
|
||||
}))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.permission_profile(),
|
||||
PermissionProfile::read_only()
|
||||
);
|
||||
assert_eq!(config.permissions.active_permission_profile(), None);
|
||||
assert!(
|
||||
config.startup_warnings.iter().any(|warning| warning
|
||||
.contains("Configured value for `permission_profile` is disallowed by requirements")),
|
||||
"{:?}",
|
||||
config.startup_warnings
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_preserves_split_write_roots() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
+115
-13
@@ -86,6 +86,8 @@ use codex_protocol::config_types::Verbosity;
|
||||
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;
|
||||
@@ -105,6 +107,7 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::permissions::BUILT_IN_WORKSPACE_PROFILE;
|
||||
use crate::config::permissions::builtin_permission_profile;
|
||||
use crate::config::permissions::compile_permission_profile_selection;
|
||||
use crate::config::permissions::default_builtin_permission_profile_name;
|
||||
@@ -229,6 +232,9 @@ pub struct Permissions {
|
||||
/// Canonical effective runtime permissions after config requirements and
|
||||
/// runtime readable-root additions have been applied.
|
||||
pub 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>,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
/// Whether the model may request a login shell for shell-based tools.
|
||||
@@ -256,6 +262,11 @@ impl Permissions {
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
/// Named profile selected by config, if the current profile has one.
|
||||
pub fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.active_permission_profile.clone()
|
||||
}
|
||||
|
||||
/// 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()
|
||||
@@ -314,6 +325,7 @@ impl Permissions {
|
||||
);
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.active_permission_profile = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -321,10 +333,24 @@ impl Permissions {
|
||||
pub fn set_permission_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
self.set_permission_profile_with_active_profile(
|
||||
permission_profile,
|
||||
/*active_permission_profile*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Replace permissions from the canonical profile and record the named
|
||||
/// source profile, if one is known.
|
||||
pub fn set_permission_profile_with_active_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
) -> ConstraintResult<()> {
|
||||
self.permission_profile.can_set(&permission_profile)?;
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.active_permission_profile = active_permission_profile;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1254,7 +1280,7 @@ fn apply_requirement_constrained_value<T>(
|
||||
configured_value: T,
|
||||
constrained_value: &mut ConstrainedWithSource<T>,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) -> std::io::Result<()>
|
||||
) -> std::io::Result<bool>
|
||||
where
|
||||
T: Clone + std::fmt::Debug + Send + Sync,
|
||||
{
|
||||
@@ -1279,9 +1305,10 @@ where
|
||||
),
|
||||
)
|
||||
})?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn mcp_server_matches_requirement(
|
||||
@@ -1681,6 +1708,7 @@ pub struct ConfigOverrides {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_mode: Option<SandboxMode>,
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub default_permissions: Option<String>,
|
||||
pub model_provider: Option<String>,
|
||||
pub service_tier: Option<Option<ServiceTier>>,
|
||||
pub config_profile: Option<String>,
|
||||
@@ -1945,6 +1973,7 @@ impl Config {
|
||||
approvals_reviewer: approvals_reviewer_override,
|
||||
sandbox_mode,
|
||||
permission_profile,
|
||||
default_permissions: default_permissions_override,
|
||||
model_provider,
|
||||
service_tier: service_tier_override,
|
||||
config_profile: config_profile_key,
|
||||
@@ -1969,6 +1998,18 @@ impl Config {
|
||||
"`sandbox_mode` and `permission_profile` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
if sandbox_mode.is_some() && default_permissions_override.is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"`sandbox_mode` and `default_permissions` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
if permission_profile.is_some() && default_permissions_override.is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"`permission_profile` and `default_permissions` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
|
||||
let active_profile_name = config_profile_key
|
||||
.as_ref()
|
||||
@@ -2040,6 +2081,7 @@ impl Config {
|
||||
.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(
|
||||
@@ -2057,13 +2099,16 @@ impl Config {
|
||||
.permissions
|
||||
.as_ref()
|
||||
.is_some_and(|profiles| !profiles.is_empty());
|
||||
let default_permissions = default_permissions_override
|
||||
.as_deref()
|
||||
.or(cfg.default_permissions.as_deref());
|
||||
validate_user_permission_profile_names(cfg.permissions.as_ref())?;
|
||||
if has_permission_profiles
|
||||
&& !matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Legacy)
|
||||
)
|
||||
&& cfg.default_permissions.is_none()
|
||||
&& default_permissions.is_none()
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
@@ -2085,15 +2130,19 @@ impl Config {
|
||||
additional_writable_roots.push(memories_root);
|
||||
}
|
||||
|
||||
let profiles_are_active = matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Profiles)
|
||||
) || permission_config_syntax.is_none();
|
||||
let using_implicit_builtin_profile = permission_config_syntax.is_none();
|
||||
let profiles_are_active = default_permissions_override.is_some()
|
||||
|| matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Profiles)
|
||||
)
|
||||
|| permission_config_syntax.is_none();
|
||||
let using_implicit_builtin_profile =
|
||||
permission_config_syntax.is_none() && default_permissions.is_none();
|
||||
let (
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
mut active_permission_profile,
|
||||
) = if let Some(mut permission_profile) = permission_profile {
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
@@ -2104,7 +2153,7 @@ impl Config {
|
||||
// PermissionProfile carries the active network sandbox bit, not the configured
|
||||
// proxy/allowlist policy. Keep that config so active profiles can round-trip
|
||||
// without broadening network behavior.
|
||||
let default_permissions = cfg.default_permissions.as_deref().unwrap_or_else(|| {
|
||||
let default_permissions = default_permissions.unwrap_or_else(|| {
|
||||
default_builtin_permission_profile_name(
|
||||
&active_project,
|
||||
windows_sandbox_level,
|
||||
@@ -2139,11 +2188,17 @@ impl Config {
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
None,
|
||||
)
|
||||
} else if profiles_are_active {
|
||||
let default_permissions = cfg.default_permissions.as_deref().unwrap_or_else(|| {
|
||||
let default_permissions = default_permissions.unwrap_or_else(|| {
|
||||
default_builtin_permission_profile_name(&active_project, windows_sandbox_level)
|
||||
});
|
||||
let builtin_workspace_write_settings = if using_implicit_builtin_profile {
|
||||
cfg.sandbox_workspace_write.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let configured_network_proxy_config = network_proxy_config_for_profile_selection(
|
||||
cfg.permissions.as_ref(),
|
||||
default_permissions,
|
||||
@@ -2152,12 +2207,12 @@ impl Config {
|
||||
compile_permission_profile_selection(
|
||||
cfg.permissions.as_ref(),
|
||||
default_permissions,
|
||||
cfg.sandbox_workspace_write.as_ref(),
|
||||
builtin_workspace_write_settings,
|
||||
resolved_cwd.as_path(),
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut permission_profile = if let Some(permission_profile) =
|
||||
builtin_permission_profile(default_permissions, cfg.sandbox_workspace_write.as_ref())
|
||||
builtin_permission_profile(default_permissions, builtin_workspace_write_settings)
|
||||
{
|
||||
permission_profile
|
||||
} else {
|
||||
@@ -2188,11 +2243,51 @@ impl Config {
|
||||
&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,
|
||||
);
|
||||
permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
}
|
||||
let active_permission_profile = if using_implicit_builtin_profile
|
||||
&& default_permissions == BUILT_IN_WORKSPACE_PROFILE
|
||||
&& cfg.sandbox_workspace_write.is_some()
|
||||
{
|
||||
// The implicit built-in profile preserves legacy
|
||||
// `[sandbox_workspace_write]` customizations, but explicitly
|
||||
// selecting `:workspace` intentionally ignores those legacy
|
||||
// settings. Do not advertise a re-selectable active profile
|
||||
// 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)
|
||||
};
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
active_permission_profile,
|
||||
)
|
||||
} else {
|
||||
let configured_network_proxy_config = NetworkProxyConfig::default();
|
||||
@@ -2254,6 +2349,7 @@ impl Config {
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
None,
|
||||
)
|
||||
};
|
||||
let approval_policy_was_explicit = approval_policy_override.is_some()
|
||||
@@ -2633,12 +2729,17 @@ impl Config {
|
||||
&mut constrained_approvals_reviewer,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
apply_requirement_constrained_value(
|
||||
let permission_profile_was_constrained = apply_requirement_constrained_value(
|
||||
"permission_profile",
|
||||
permission_profile,
|
||||
&mut constrained_permission_profile,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
if permission_profile_was_constrained {
|
||||
// The selected profile no longer describes the effective
|
||||
// permissions after requirements forced a fallback.
|
||||
active_permission_profile = None;
|
||||
}
|
||||
apply_requirement_constrained_value(
|
||||
"web_search_mode",
|
||||
web_search_mode,
|
||||
@@ -2721,6 +2822,7 @@ impl Config {
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
permission_profile: constrained_permission_profile.value,
|
||||
active_permission_profile,
|
||||
network,
|
||||
allow_login_shell,
|
||||
shell_environment_policy,
|
||||
|
||||
@@ -161,6 +161,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
permission_profile,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
collaboration_mode,
|
||||
reasoning_summary: summary,
|
||||
@@ -180,6 +181,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
effort,
|
||||
@@ -211,6 +213,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode,
|
||||
reasoning_summary: summary,
|
||||
|
||||
@@ -91,6 +91,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -603,6 +604,7 @@ impl Codex {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
||||
@@ -60,6 +60,9 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) approvals_reviewer: ApprovalsReviewer,
|
||||
/// Canonical permission profile for the session.
|
||||
pub(super) permission_profile: Constrained<PermissionProfile>,
|
||||
/// Named or implicit built-in permissions profile selected from config, if
|
||||
/// any.
|
||||
pub(super) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
/// Absolute working directory that should be treated as the *root* of the
|
||||
@@ -97,6 +100,10 @@ impl SessionConfiguration {
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
pub(super) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.active_permission_profile.clone()
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.permission_profile()
|
||||
.to_legacy_sandbox_policy(&self.cwd)
|
||||
@@ -127,6 +134,7 @@ impl SessionConfiguration {
|
||||
approval_policy: self.approval_policy.value(),
|
||||
approvals_reviewer: self.approvals_reviewer,
|
||||
permission_profile: self.permission_profile(),
|
||||
active_permission_profile: self.active_permission_profile(),
|
||||
cwd: self.cwd.clone(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
@@ -206,10 +214,19 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
let active_permission_profile =
|
||||
updates.active_permission_profile.clone().or_else(|| {
|
||||
if permission_profile == self.permission_profile() {
|
||||
self.active_permission_profile.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
next_configuration.set_permission_profile_projection(
|
||||
permission_profile,
|
||||
Some(¤t_file_system_sandbox_policy),
|
||||
)?;
|
||||
next_configuration.active_permission_profile = active_permission_profile;
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
@@ -225,6 +242,7 @@ impl SessionConfiguration {
|
||||
network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
next_configuration.active_permission_profile = None;
|
||||
} else if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_project_root_write
|
||||
@@ -285,6 +303,7 @@ pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub(crate) sandbox_policy: Option<SandboxPolicy>,
|
||||
pub(crate) permission_profile: Option<PermissionProfile>,
|
||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
||||
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
@@ -887,6 +906,7 @@ impl Session {
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
active_permission_profile: session_configuration.active_permission_profile(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
history_log_id,
|
||||
|
||||
@@ -2324,6 +2324,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2426,6 +2427,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2873,6 +2875,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3309,6 +3312,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3413,6 +3417,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3625,6 +3630,7 @@ async fn make_session_with_config_and_rx(
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -4206,6 +4212,7 @@ fn op_kind_distinguishes_turn_ops() {
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -4839,6 +4846,7 @@ where
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
||||
+128
-49
@@ -25,6 +25,8 @@ use codex_app_server_protocol::ConfigWarningNotification;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::McpServerElicitationAction;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestResponse;
|
||||
use codex_app_server_protocol::PermissionProfileModificationParams;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ReviewStartParams;
|
||||
use codex_app_server_protocol::ReviewStartResponse;
|
||||
@@ -76,13 +78,14 @@ use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_otel::set_parent_from_context;
|
||||
use codex_otel::traceparent_context_from_env;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ReviewRequest;
|
||||
use codex_protocol::protocol::ReviewTarget;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_protocol::protocol::RolloutLine;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
@@ -399,6 +402,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
approvals_reviewer: None,
|
||||
sandbox_mode,
|
||||
permission_profile: None,
|
||||
default_permissions: None,
|
||||
cwd: resolved_cwd,
|
||||
model_provider: model_provider.clone(),
|
||||
service_tier: None,
|
||||
@@ -665,37 +669,24 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
|
||||
// Handle resume subcommand through existing `thread/list` + `thread/resume`
|
||||
// APIs so exec no longer reaches into rollout storage directly.
|
||||
let (primary_thread_id, fallback_session_configured) =
|
||||
if let Some(ExecCommand::Resume(args)) = command.as_ref() {
|
||||
if let Some(thread_id) = resolve_resume_thread_id(&client, &config, args).await? {
|
||||
let response: ThreadResumeResponse = send_request_with_response(
|
||||
&client,
|
||||
ClientRequest::ThreadResume {
|
||||
request_id: request_ids.next(),
|
||||
params: thread_resume_params_from_config(&config, thread_id),
|
||||
},
|
||||
"thread/resume",
|
||||
)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured = session_configured_from_thread_resume_response(&response)
|
||||
let (primary_thread_id, fallback_session_configured) = if let Some(ExecCommand::Resume(args)) =
|
||||
command.as_ref()
|
||||
{
|
||||
if let Some(thread_id) = resolve_resume_thread_id(&client, &config, args).await? {
|
||||
let response: ThreadResumeResponse = send_request_with_response(
|
||||
&client,
|
||||
ClientRequest::ThreadResume {
|
||||
request_id: request_ids.next(),
|
||||
params: thread_resume_params_from_config(&config, thread_id),
|
||||
},
|
||||
"thread/resume",
|
||||
)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured =
|
||||
session_configured_from_thread_resume_response(&response, &config)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
(session_configured.session_id, session_configured)
|
||||
} else {
|
||||
let response: ThreadStartResponse = send_request_with_response(
|
||||
&client,
|
||||
ClientRequest::ThreadStart {
|
||||
request_id: request_ids.next(),
|
||||
params: thread_start_params_from_config(&config),
|
||||
},
|
||||
"thread/start",
|
||||
)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured = session_configured_from_thread_start_response(&response)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
(session_configured.session_id, session_configured)
|
||||
}
|
||||
(session_configured.session_id, session_configured)
|
||||
} else {
|
||||
let response: ThreadStartResponse = send_request_with_response(
|
||||
&client,
|
||||
@@ -707,10 +698,26 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured = session_configured_from_thread_start_response(&response)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured =
|
||||
session_configured_from_thread_start_response(&response, &config)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
(session_configured.session_id, session_configured)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let response: ThreadStartResponse = send_request_with_response(
|
||||
&client,
|
||||
ClientRequest::ThreadStart {
|
||||
request_id: request_ids.next(),
|
||||
params: thread_start_params_from_config(&config),
|
||||
},
|
||||
"thread/start",
|
||||
)
|
||||
.await
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let session_configured = session_configured_from_thread_start_response(&response, &config)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
(session_configured.session_id, session_configured)
|
||||
};
|
||||
|
||||
let primary_thread_id_for_span = primary_thread_id.to_string();
|
||||
// Use the start/resume response as the authoritative bootstrap payload.
|
||||
@@ -745,7 +752,6 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
items,
|
||||
output_schema,
|
||||
} => {
|
||||
let permission_profile = Some(config.permissions.permission_profile().into());
|
||||
let response: TurnStartResponse = send_request_with_response(
|
||||
&client,
|
||||
ClientRequest::TurnStart {
|
||||
@@ -759,7 +765,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
approval_policy: Some(default_approval_policy.into()),
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile,
|
||||
permissions: None,
|
||||
model: None,
|
||||
service_tier: None,
|
||||
effort: default_effort,
|
||||
@@ -916,14 +922,21 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
fn thread_start_params_from_config(config: &Config) -> ThreadStartParams {
|
||||
let permissions = permissions_selection_from_config(config);
|
||||
let sandbox = permissions.is_none().then(|| {
|
||||
sandbox_mode_from_permission_profile(
|
||||
&config.permissions.permission_profile(),
|
||||
config.cwd.as_path(),
|
||||
)
|
||||
});
|
||||
ThreadStartParams {
|
||||
model: config.model.clone(),
|
||||
model_provider: Some(config.model_provider_id.clone()),
|
||||
cwd: Some(config.cwd.to_string_lossy().to_string()),
|
||||
approval_policy: Some(config.permissions.approval_policy.value().into()),
|
||||
approvals_reviewer: approvals_reviewer_override_from_config(config),
|
||||
sandbox: None,
|
||||
permission_profile: Some(config.permissions.permission_profile().into()),
|
||||
sandbox: sandbox.flatten(),
|
||||
permissions,
|
||||
config: config_request_overrides_from_config(config),
|
||||
ephemeral: Some(config.ephemeral),
|
||||
..ThreadStartParams::default()
|
||||
@@ -931,6 +944,13 @@ fn thread_start_params_from_config(config: &Config) -> ThreadStartParams {
|
||||
}
|
||||
|
||||
fn thread_resume_params_from_config(config: &Config, thread_id: String) -> ThreadResumeParams {
|
||||
let permissions = permissions_selection_from_config(config);
|
||||
let sandbox = permissions.is_none().then(|| {
|
||||
sandbox_mode_from_permission_profile(
|
||||
&config.permissions.permission_profile(),
|
||||
config.cwd.as_path(),
|
||||
)
|
||||
});
|
||||
ThreadResumeParams {
|
||||
thread_id,
|
||||
model: config.model.clone(),
|
||||
@@ -938,13 +958,63 @@ fn thread_resume_params_from_config(config: &Config, thread_id: String) -> Threa
|
||||
cwd: Some(config.cwd.to_string_lossy().to_string()),
|
||||
approval_policy: Some(config.permissions.approval_policy.value().into()),
|
||||
approvals_reviewer: approvals_reviewer_override_from_config(config),
|
||||
sandbox: None,
|
||||
permission_profile: Some(config.permissions.permission_profile().into()),
|
||||
sandbox: sandbox.flatten(),
|
||||
permissions,
|
||||
config: config_request_overrides_from_config(config),
|
||||
..ThreadResumeParams::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn permissions_selection_from_config(config: &Config) -> Option<PermissionProfileSelectionParams> {
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.map(permissions_selection_from_active_profile)
|
||||
}
|
||||
|
||||
fn permissions_selection_from_active_profile(
|
||||
active: ActivePermissionProfile,
|
||||
) -> PermissionProfileSelectionParams {
|
||||
let modifications = active
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(|modification| match modification {
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
PermissionProfileModificationParams::AdditionalWritableRoot { path }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
PermissionProfileSelectionParams::Profile {
|
||||
id: active.id,
|
||||
modifications: (!modifications.is_empty()).then_some(modifications),
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_mode_from_permission_profile(
|
||||
permission_profile: &PermissionProfile,
|
||||
cwd: &Path,
|
||||
) -> Option<codex_app_server_protocol::SandboxMode> {
|
||||
match permission_profile {
|
||||
PermissionProfile::Disabled => {
|
||||
Some(codex_app_server_protocol::SandboxMode::DangerFullAccess)
|
||||
}
|
||||
PermissionProfile::External { .. } => None,
|
||||
PermissionProfile::Managed { .. } => {
|
||||
let file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
if file_system_policy.has_full_disk_write_access() {
|
||||
permission_profile
|
||||
.network_sandbox_policy()
|
||||
.is_enabled()
|
||||
.then_some(codex_app_server_protocol::SandboxMode::DangerFullAccess)
|
||||
} else if file_system_policy.can_write_path_with_cwd(cwd, cwd) {
|
||||
Some(codex_app_server_protocol::SandboxMode::WorkspaceWrite)
|
||||
} else {
|
||||
Some(codex_app_server_protocol::SandboxMode::ReadOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn config_request_overrides_from_config(config: &Config) -> Option<HashMap<String, Value>> {
|
||||
config
|
||||
.active_profile
|
||||
@@ -977,6 +1047,7 @@ where
|
||||
|
||||
fn session_configured_from_thread_start_response(
|
||||
response: &ThreadStartResponse,
|
||||
config: &Config,
|
||||
) -> Result<SessionConfiguredEvent, String> {
|
||||
session_configured_from_thread_response(
|
||||
&response.thread.id,
|
||||
@@ -987,8 +1058,12 @@ fn session_configured_from_thread_start_response(
|
||||
response.service_tier,
|
||||
response.approval_policy.to_core(),
|
||||
response.approvals_reviewer.to_core(),
|
||||
response.sandbox.to_core(),
|
||||
response.permission_profile.clone().map(Into::into),
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| config.permissions.permission_profile()),
|
||||
response.active_permission_profile.clone().map(Into::into),
|
||||
response.cwd.clone(),
|
||||
response.reasoning_effort,
|
||||
)
|
||||
@@ -996,6 +1071,7 @@ fn session_configured_from_thread_start_response(
|
||||
|
||||
fn session_configured_from_thread_resume_response(
|
||||
response: &ThreadResumeResponse,
|
||||
config: &Config,
|
||||
) -> Result<SessionConfiguredEvent, String> {
|
||||
session_configured_from_thread_response(
|
||||
&response.thread.id,
|
||||
@@ -1006,8 +1082,12 @@ fn session_configured_from_thread_resume_response(
|
||||
response.service_tier,
|
||||
response.approval_policy.to_core(),
|
||||
response.approvals_reviewer.to_core(),
|
||||
response.sandbox.to_core(),
|
||||
response.permission_profile.clone().map(Into::into),
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| config.permissions.permission_profile()),
|
||||
response.active_permission_profile.clone().map(Into::into),
|
||||
response.cwd.clone(),
|
||||
response.reasoning_effort,
|
||||
)
|
||||
@@ -1035,8 +1115,8 @@ fn session_configured_from_thread_response(
|
||||
service_tier: Option<codex_protocol::config_types::ServiceTier>,
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
|
||||
sandbox_policy: SandboxPolicy,
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<codex_protocol::models::ActivePermissionProfile>,
|
||||
cwd: AbsolutePathBuf,
|
||||
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
) -> Result<SessionConfiguredEvent, String> {
|
||||
@@ -1052,9 +1132,8 @@ fn session_configured_from_thread_response(
|
||||
service_tier,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile: permission_profile.unwrap_or_else(|| {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd.as_path())
|
||||
}),
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
cwd,
|
||||
reasoning_effort,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -363,8 +363,8 @@ async fn thread_start_params_include_review_policy_when_review_policy_is_manual_
|
||||
);
|
||||
assert_eq!(params.sandbox, None);
|
||||
assert_eq!(
|
||||
params.permission_profile,
|
||||
Some(config.permissions.permission_profile().into())
|
||||
params.permissions,
|
||||
permissions_selection_from_config(&config)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -391,9 +391,76 @@ async fn thread_start_params_include_review_policy_when_auto_review_is_enabled()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_configured_from_thread_response_uses_review_policy_from_response() {
|
||||
let response = ThreadStartResponse {
|
||||
#[tokio::test]
|
||||
async fn thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
sandbox_mode: Some(SandboxMode::DangerFullAccess),
|
||||
..Default::default()
|
||||
})
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build config with legacy sandbox override");
|
||||
|
||||
let start_params = thread_start_params_from_config(&config);
|
||||
let resume_params = thread_resume_params_from_config(&config, "thread-id".to_string());
|
||||
|
||||
assert_eq!(config.permissions.active_permission_profile(), None);
|
||||
assert_eq!(
|
||||
start_params.sandbox,
|
||||
Some(codex_app_server_protocol::SandboxMode::DangerFullAccess)
|
||||
);
|
||||
assert_eq!(start_params.permissions, None);
|
||||
assert_eq!(
|
||||
resume_params.sandbox,
|
||||
Some(codex_app_server_protocol::SandboxMode::DangerFullAccess)
|
||||
);
|
||||
assert_eq!(resume_params.permissions, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configured_from_thread_response_uses_review_policy_from_response() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build config");
|
||||
let response = sample_thread_start_response();
|
||||
|
||||
let event = session_configured_from_thread_start_response(&response, &config)
|
||||
.expect("build bootstrap session configured event");
|
||||
|
||||
assert_eq!(event.approvals_reviewer, ApprovalsReviewer::AutoReview);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configured_from_thread_response_uses_permission_profile_from_response() {
|
||||
let codex_home = tempdir().expect("create temp codex home");
|
||||
let cwd = tempdir().expect("create temp cwd");
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(cwd.path().to_path_buf()))
|
||||
.build()
|
||||
.await
|
||||
.expect("build config");
|
||||
let mut response = sample_thread_start_response();
|
||||
response.permission_profile = Some(PermissionProfile::Disabled.into());
|
||||
|
||||
let event = session_configured_from_thread_start_response(&response, &config)
|
||||
.expect("build bootstrap session configured event");
|
||||
|
||||
assert_eq!(event.permission_profile, PermissionProfile::Disabled);
|
||||
}
|
||||
|
||||
fn sample_thread_start_response() -> ThreadStartResponse {
|
||||
ThreadStartResponse {
|
||||
thread: codex_app_server_protocol::Thread {
|
||||
id: "67e55044-10b1-426f-9247-bb680e5fe0c8".to_string(),
|
||||
forked_from_id: None,
|
||||
@@ -426,17 +493,8 @@ fn session_configured_from_thread_response_uses_review_policy_from_response() {
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
},
|
||||
permission_profile: Some(
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&codex_protocol::protocol::SandboxPolicy::new_workspace_write_policy(),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
};
|
||||
|
||||
let event = session_configured_from_thread_start_response(&response)
|
||||
.expect("build bootstrap session configured event");
|
||||
|
||||
assert_eq!(event.approvals_reviewer, ApprovalsReviewer::AutoReview);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ fn session_configured_produces_thread_started_event() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -305,6 +305,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
@@ -349,6 +350,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
@@ -416,6 +418,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffort::default()),
|
||||
history_log_id: 1,
|
||||
|
||||
@@ -317,6 +317,59 @@ pub enum PermissionProfile {
|
||||
External { network: NetworkSandboxPolicy },
|
||||
}
|
||||
|
||||
/// Metadata for the named or implicit built-in permissions profile that
|
||||
/// produced the active `PermissionProfile`.
|
||||
///
|
||||
/// The runtime must honor `PermissionProfile`; this sidecar exists so clients
|
||||
/// can display stable profile identity without trying to reverse-engineer a
|
||||
/// name from the compiled permissions.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct ActivePermissionProfile {
|
||||
/// Profile identifier from `default_permissions` or the implicit built-in
|
||||
/// default, such as `:workspace` or a user-defined `[permissions.<id>]`
|
||||
/// profile.
|
||||
pub id: String,
|
||||
|
||||
/// Optional parent profile identifier once permissions profiles support
|
||||
/// inheritance. This is always `None` until that config feature exists.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub extends: Option<String>,
|
||||
|
||||
/// Bounded user-requested modifications applied on top of the named
|
||||
/// profile, if any.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub modifications: Vec<ActivePermissionProfileModification>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[ts(tag = "type")]
|
||||
pub enum ActivePermissionProfileModification {
|
||||
/// Additional concrete directory that should be writable.
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case")]
|
||||
AdditionalWritableRoot { path: AbsolutePathBuf },
|
||||
}
|
||||
|
||||
impl ActivePermissionProfile {
|
||||
pub fn new(id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
extends: None,
|
||||
modifications: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_modifications(
|
||||
mut self,
|
||||
modifications: Vec<ActivePermissionProfileModification>,
|
||||
) -> Self {
|
||||
self.modifications = modifications;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PermissionProfile {
|
||||
fn default() -> Self {
|
||||
Self::Managed {
|
||||
|
||||
@@ -33,6 +33,7 @@ use crate::mcp::ResourceTemplate as McpResourceTemplate;
|
||||
use crate::mcp::Tool as McpTool;
|
||||
use crate::memory_citation::MemoryCitation;
|
||||
use crate::message_history::HistoryEntry;
|
||||
use crate::models::ActivePermissionProfile;
|
||||
use crate::models::BaseInstructions;
|
||||
use crate::models::ContentItem;
|
||||
use crate::models::MessagePhase;
|
||||
@@ -479,6 +480,12 @@ pub enum Op {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
|
||||
/// Named or built-in profile that produced `permission_profile`, if
|
||||
/// the update selected a profile rather than supplying raw
|
||||
/// permissions.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
|
||||
/// Updated Windows sandbox mode for tool execution.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
@@ -3508,6 +3515,12 @@ pub struct SessionConfiguredEvent {
|
||||
/// Canonical effective permissions for commands executed in the session.
|
||||
pub permission_profile: PermissionProfile,
|
||||
|
||||
/// Named or implicit built-in profile that produced `permission_profile`,
|
||||
/// when known.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
|
||||
/// Working directory that should be treated as the *root* of the
|
||||
/// session.
|
||||
pub cwd: AbsolutePathBuf,
|
||||
@@ -3559,6 +3572,8 @@ impl<'de> Deserialize<'de> for SessionConfiguredEvent {
|
||||
// and immediately project it into the canonical `permission_profile`.
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
#[serde(default)]
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
cwd: AbsolutePathBuf,
|
||||
reasoning_effort: Option<ReasoningEffortConfig>,
|
||||
history_log_id: u64,
|
||||
@@ -3590,6 +3605,7 @@ impl<'de> Deserialize<'de> for SessionConfiguredEvent {
|
||||
approval_policy: wire.approval_policy,
|
||||
approvals_reviewer: wire.approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile: wire.active_permission_profile,
|
||||
cwd: wire.cwd,
|
||||
reasoning_effort: wire.reasoning_effort,
|
||||
history_log_id: wire.history_log_id,
|
||||
@@ -5124,6 +5140,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: permission_profile.clone(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -165,6 +165,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::default()),
|
||||
active_permission_profile: None,
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
|
||||
@@ -646,6 +646,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: next_cwd.clone().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -3571,6 +3571,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::High),
|
||||
history_log_id: 0,
|
||||
@@ -3810,6 +3811,7 @@ fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSessionState
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: cwd.abs(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
@@ -4322,6 +4324,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4385,6 +4388,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4478,6 +4482,7 @@ async fn backtrack_resubmit_preserves_data_image_urls_in_user_turn() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4864,6 +4869,7 @@ async fn new_session_requests_shutdown_for_previous_conversation() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4976,6 +4982,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -350,6 +350,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: cwd.abs(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -604,15 +604,24 @@ impl App {
|
||||
}
|
||||
}
|
||||
if should_start_turn {
|
||||
let config = self.chat_widget.config_ref();
|
||||
let approvals_reviewer =
|
||||
approvals_reviewer.unwrap_or(config.approvals_reviewer);
|
||||
let active_permission_profile =
|
||||
if config.permissions.permission_profile() == permission_profile.clone() {
|
||||
config.permissions.active_permission_profile()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
app_server
|
||||
.turn_start(
|
||||
thread_id,
|
||||
items.to_vec(),
|
||||
cwd.clone(),
|
||||
approval_policy,
|
||||
approvals_reviewer
|
||||
.unwrap_or(self.chat_widget.config_ref().approvals_reviewer),
|
||||
approvals_reviewer,
|
||||
permission_profile.clone(),
|
||||
active_permission_profile,
|
||||
model.to_string(),
|
||||
effort,
|
||||
*summary,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::app_server_session::ThreadSessionState;
|
||||
use crate::read_session_model;
|
||||
use codex_app_server_protocol::Thread;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
|
||||
impl App {
|
||||
@@ -18,10 +19,16 @@ impl App {
|
||||
.config_ref()
|
||||
.permissions
|
||||
.permission_profile();
|
||||
let active_permission_profile = self
|
||||
.chat_widget
|
||||
.config_ref()
|
||||
.permissions
|
||||
.active_permission_profile();
|
||||
let update_session = |session: &mut ThreadSessionState| {
|
||||
session.approval_policy = approval_policy;
|
||||
session.approvals_reviewer = approvals_reviewer;
|
||||
session.permission_profile = permission_profile.clone();
|
||||
session.active_permission_profile = active_permission_profile.clone();
|
||||
};
|
||||
|
||||
if self.primary_thread_id == Some(active_thread_id)
|
||||
@@ -43,7 +50,8 @@ impl App {
|
||||
thread_id: ThreadId,
|
||||
thread: &Thread,
|
||||
) -> ThreadSessionState {
|
||||
let permission_profile = self.active_permission_profile();
|
||||
let permission_profile = self.current_permission_profile();
|
||||
let active_permission_profile = self.current_active_permission_profile();
|
||||
let mut session = self
|
||||
.primary_session_configured
|
||||
.clone()
|
||||
@@ -58,6 +66,7 @@ impl App {
|
||||
approval_policy: self.config.permissions.approval_policy.value(),
|
||||
approvals_reviewer: self.config.approvals_reviewer,
|
||||
permission_profile: permission_profile.clone(),
|
||||
active_permission_profile: active_permission_profile.clone(),
|
||||
cwd: thread.cwd.clone(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: self.chat_widget.current_reasoning_effort(),
|
||||
@@ -71,6 +80,7 @@ impl App {
|
||||
session.model_provider_id = thread.model_provider.clone();
|
||||
session.cwd = thread.cwd.clone();
|
||||
session.permission_profile = permission_profile;
|
||||
session.active_permission_profile = active_permission_profile;
|
||||
session.instruction_source_paths = Vec::new();
|
||||
session.rollout_path = thread.path.clone();
|
||||
if let Some(model) =
|
||||
@@ -85,12 +95,19 @@ impl App {
|
||||
session
|
||||
}
|
||||
|
||||
fn active_permission_profile(&self) -> PermissionProfile {
|
||||
fn current_permission_profile(&self) -> PermissionProfile {
|
||||
self.chat_widget
|
||||
.config_ref()
|
||||
.permissions
|
||||
.permission_profile()
|
||||
}
|
||||
|
||||
fn current_active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.chat_widget
|
||||
.config_ref()
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -125,6 +142,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: cwd.abs(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
|
||||
@@ -29,6 +29,8 @@ use codex_app_server_protocol::MemoryResetResponse;
|
||||
use codex_app_server_protocol::Model as ApiModel;
|
||||
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::RequestId;
|
||||
use codex_app_server_protocol::ReviewDelivery;
|
||||
use codex_app_server_protocol::ReviewStartParams;
|
||||
@@ -93,6 +95,8 @@ use codex_app_server_protocol::TurnSteerParams;
|
||||
use codex_app_server_protocol::TurnSteerResponse;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelAvailabilityNux;
|
||||
@@ -156,10 +160,17 @@ pub(crate) struct ThreadSessionState {
|
||||
pub(crate) service_tier: Option<codex_protocol::config_types::ServiceTier>,
|
||||
pub(crate) approval_policy: AskForApproval,
|
||||
pub(crate) approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
|
||||
/// Canonical active permissions for this session. Legacy app-server
|
||||
/// responses are converted to a profile at ingestion time using the
|
||||
/// response cwd so cached sessions do not reinterpret cwd-bound grants.
|
||||
/// Canonical active permissions for this session.
|
||||
///
|
||||
/// App-server responses may echo the experimental profile payload; when
|
||||
/// they do, the TUI uses that exact runtime profile. Older/missing
|
||||
/// responses fall back to the local profile for embedded sessions or to a
|
||||
/// response-cwd legacy sandbox projection for remote sessions so cached
|
||||
/// sessions do not reinterpret cwd-bound grants.
|
||||
pub(crate) permission_profile: PermissionProfile,
|
||||
/// Named or implicit built-in profile that produced `permission_profile`,
|
||||
/// when the server knows it.
|
||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
pub(crate) instruction_source_paths: Vec<AbsolutePathBuf>,
|
||||
pub(crate) reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
@@ -365,7 +376,7 @@ impl AppServerSession {
|
||||
})
|
||||
.await
|
||||
.wrap_err("thread/start failed during TUI bootstrap")?;
|
||||
started_thread_from_start_response(response, config).await
|
||||
started_thread_from_start_response(response, config, self.thread_params_mode()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn resume_thread(
|
||||
@@ -390,7 +401,9 @@ impl AppServerSession {
|
||||
let fork_parent_title = self
|
||||
.fork_parent_title_from_app_server(response.thread.forked_from_id.as_deref())
|
||||
.await;
|
||||
let mut started = started_thread_from_resume_response(response, &config).await?;
|
||||
let mut started =
|
||||
started_thread_from_resume_response(response, &config, self.thread_params_mode())
|
||||
.await?;
|
||||
started.session.fork_parent_title = fork_parent_title;
|
||||
Ok(started)
|
||||
}
|
||||
@@ -417,7 +430,8 @@ impl AppServerSession {
|
||||
let fork_parent_title = self
|
||||
.fork_parent_title_from_app_server(response.thread.forked_from_id.as_deref())
|
||||
.await;
|
||||
let mut started = started_thread_from_fork_response(response, &config).await?;
|
||||
let mut started =
|
||||
started_thread_from_fork_response(response, &config, self.thread_params_mode()).await?;
|
||||
started.session.fork_parent_title = fork_parent_title;
|
||||
Ok(started)
|
||||
}
|
||||
@@ -533,6 +547,7 @@ impl AppServerSession {
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
model: String,
|
||||
effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
summary: Option<codex_protocol::config_types::ReasoningSummary>,
|
||||
@@ -542,26 +557,12 @@ impl AppServerSession {
|
||||
output_schema: Option<serde_json::Value>,
|
||||
) -> Result<TurnStartResponse> {
|
||||
let request_id = self.next_request_id();
|
||||
let sandbox_policy = if matches!(self.thread_params_mode(), ThreadParamsMode::Remote) {
|
||||
let legacy_profile =
|
||||
legacy_compatible_permission_profile(&permission_profile, cwd.as_path());
|
||||
let policy = legacy_profile
|
||||
.to_legacy_sandbox_policy(cwd.as_path())
|
||||
.unwrap_or_else(|err| {
|
||||
unreachable!(
|
||||
"legacy-compatible permissions must project to legacy policy: {err}"
|
||||
)
|
||||
});
|
||||
Some(policy.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let permission_profile = if matches!(self.thread_params_mode(), ThreadParamsMode::Embedded)
|
||||
{
|
||||
Some(permission_profile.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (sandbox_policy, permissions) = turn_permissions_overrides(
|
||||
&permission_profile,
|
||||
active_permission_profile,
|
||||
cwd.as_path(),
|
||||
self.thread_params_mode(),
|
||||
);
|
||||
self.client
|
||||
.request_typed(ClientRequest::TurnStart {
|
||||
request_id,
|
||||
@@ -574,7 +575,7 @@ impl AppServerSession {
|
||||
approval_policy: Some(approval_policy.into()),
|
||||
approvals_reviewer: Some(approvals_reviewer.into()),
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
permissions,
|
||||
model: Some(model),
|
||||
service_tier,
|
||||
effort,
|
||||
@@ -1127,15 +1128,64 @@ fn sandbox_mode_from_permission_profile(
|
||||
}
|
||||
}
|
||||
|
||||
fn permission_profile_override_from_config(
|
||||
fn permissions_selection_from_active_profile(
|
||||
active: ActivePermissionProfile,
|
||||
) -> PermissionProfileSelectionParams {
|
||||
let modifications = active
|
||||
.modifications
|
||||
.into_iter()
|
||||
.map(|modification| match modification {
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path } => {
|
||||
PermissionProfileModificationParams::AdditionalWritableRoot { path }
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
PermissionProfileSelectionParams::Profile {
|
||||
id: active.id,
|
||||
modifications: (!modifications.is_empty()).then_some(modifications),
|
||||
}
|
||||
}
|
||||
|
||||
fn turn_permissions_overrides(
|
||||
permission_profile: &PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
cwd: &std::path::Path,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> (
|
||||
Option<codex_app_server_protocol::SandboxPolicy>,
|
||||
Option<PermissionProfileSelectionParams>,
|
||||
) {
|
||||
let permissions = if matches!(thread_params_mode, ThreadParamsMode::Embedded) {
|
||||
active_permission_profile.map(permissions_selection_from_active_profile)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sandbox_policy = (matches!(thread_params_mode, ThreadParamsMode::Remote)
|
||||
|| permissions.is_none())
|
||||
.then(|| {
|
||||
let legacy_profile = legacy_compatible_permission_profile(permission_profile, cwd);
|
||||
let policy = legacy_profile
|
||||
.to_legacy_sandbox_policy(cwd)
|
||||
.unwrap_or_else(|err| {
|
||||
unreachable!("legacy-compatible permissions must project to legacy policy: {err}")
|
||||
});
|
||||
policy.into()
|
||||
});
|
||||
(sandbox_policy, permissions)
|
||||
}
|
||||
|
||||
fn permissions_selection_from_config(
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Option<codex_app_server_protocol::PermissionProfile> {
|
||||
) -> Option<PermissionProfileSelectionParams> {
|
||||
if matches!(thread_params_mode, ThreadParamsMode::Remote) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(config.permissions.permission_profile().into())
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.map(permissions_selection_from_active_profile)
|
||||
}
|
||||
|
||||
fn thread_start_params_from_config(
|
||||
@@ -1144,8 +1194,8 @@ fn thread_start_params_from_config(
|
||||
remote_cwd_override: Option<&std::path::Path>,
|
||||
session_start_source: Option<ThreadStartSource>,
|
||||
) -> ThreadStartParams {
|
||||
let permission_profile = permission_profile_override_from_config(config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
let permissions = permissions_selection_from_config(config, thread_params_mode);
|
||||
let sandbox = permissions
|
||||
.is_none()
|
||||
.then(|| {
|
||||
sandbox_mode_from_permission_profile(
|
||||
@@ -1161,7 +1211,7 @@ fn thread_start_params_from_config(
|
||||
approval_policy: Some(config.permissions.approval_policy.value().into()),
|
||||
approvals_reviewer: approvals_reviewer_override_from_config(config),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: config_request_overrides_from_config(config),
|
||||
ephemeral: Some(config.ephemeral),
|
||||
session_start_source,
|
||||
@@ -1176,8 +1226,8 @@ fn thread_resume_params_from_config(
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
remote_cwd_override: Option<&std::path::Path>,
|
||||
) -> ThreadResumeParams {
|
||||
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
let permissions = permissions_selection_from_config(&config, thread_params_mode);
|
||||
let sandbox = permissions
|
||||
.is_none()
|
||||
.then(|| {
|
||||
sandbox_mode_from_permission_profile(
|
||||
@@ -1194,7 +1244,7 @@ fn thread_resume_params_from_config(
|
||||
approval_policy: Some(config.permissions.approval_policy.value().into()),
|
||||
approvals_reviewer: approvals_reviewer_override_from_config(&config),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: config_request_overrides_from_config(&config),
|
||||
persist_extended_history: true,
|
||||
..ThreadResumeParams::default()
|
||||
@@ -1207,8 +1257,8 @@ fn thread_fork_params_from_config(
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
remote_cwd_override: Option<&std::path::Path>,
|
||||
) -> ThreadForkParams {
|
||||
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
let permissions = permissions_selection_from_config(&config, thread_params_mode);
|
||||
let sandbox = permissions
|
||||
.is_none()
|
||||
.then(|| {
|
||||
sandbox_mode_from_permission_profile(
|
||||
@@ -1225,7 +1275,7 @@ fn thread_fork_params_from_config(
|
||||
approval_policy: Some(config.permissions.approval_policy.value().into()),
|
||||
approvals_reviewer: approvals_reviewer_override_from_config(&config),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: config_request_overrides_from_config(&config),
|
||||
base_instructions: config.base_instructions.clone(),
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
@@ -1251,10 +1301,12 @@ fn thread_cwd_from_config(
|
||||
async fn started_thread_from_start_response(
|
||||
response: ThreadStartResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
let session = thread_session_state_from_thread_start_response(&response, config)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
let session =
|
||||
thread_session_state_from_thread_start_response(&response, config, thread_params_mode)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
Ok(AppServerStartedThread {
|
||||
session,
|
||||
turns: response.thread.turns,
|
||||
@@ -1264,10 +1316,12 @@ async fn started_thread_from_start_response(
|
||||
async fn started_thread_from_resume_response(
|
||||
response: ThreadResumeResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
let session = thread_session_state_from_thread_resume_response(&response, config)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
let session =
|
||||
thread_session_state_from_thread_resume_response(&response, config, thread_params_mode)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
Ok(AppServerStartedThread {
|
||||
session,
|
||||
turns: response.thread.turns,
|
||||
@@ -1277,10 +1331,12 @@ async fn started_thread_from_resume_response(
|
||||
async fn started_thread_from_fork_response(
|
||||
response: ThreadForkResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
let session = thread_session_state_from_thread_fork_response(&response, config)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
let session =
|
||||
thread_session_state_from_thread_fork_response(&response, config, thread_params_mode)
|
||||
.await
|
||||
.map_err(color_eyre::eyre::Report::msg)?;
|
||||
Ok(AppServerStartedThread {
|
||||
session,
|
||||
turns: response.thread.turns,
|
||||
@@ -1290,7 +1346,15 @@ async fn started_thread_from_fork_response(
|
||||
async fn thread_session_state_from_thread_start_response(
|
||||
response: &ThreadStartResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<ThreadSessionState, String> {
|
||||
let permission_profile = permission_profile_from_thread_response(
|
||||
&response.sandbox,
|
||||
response.permission_profile.as_ref(),
|
||||
response.cwd.as_path(),
|
||||
config,
|
||||
thread_params_mode,
|
||||
);
|
||||
thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
response.thread.forked_from_id.clone(),
|
||||
@@ -1301,16 +1365,8 @@ async fn thread_session_state_from_thread_start_response(
|
||||
response.service_tier,
|
||||
response.approval_policy.to_core(),
|
||||
response.approvals_reviewer.to_core(),
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(
|
||||
&response.sandbox.to_core(),
|
||||
response.cwd.as_path(),
|
||||
)
|
||||
}),
|
||||
permission_profile,
|
||||
response.active_permission_profile.clone().map(Into::into),
|
||||
response.cwd.clone(),
|
||||
response.instruction_sources.clone(),
|
||||
response.reasoning_effort,
|
||||
@@ -1322,7 +1378,15 @@ async fn thread_session_state_from_thread_start_response(
|
||||
async fn thread_session_state_from_thread_resume_response(
|
||||
response: &ThreadResumeResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<ThreadSessionState, String> {
|
||||
let permission_profile = permission_profile_from_thread_response(
|
||||
&response.sandbox,
|
||||
response.permission_profile.as_ref(),
|
||||
response.cwd.as_path(),
|
||||
config,
|
||||
thread_params_mode,
|
||||
);
|
||||
thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
response.thread.forked_from_id.clone(),
|
||||
@@ -1333,16 +1397,8 @@ async fn thread_session_state_from_thread_resume_response(
|
||||
response.service_tier,
|
||||
response.approval_policy.to_core(),
|
||||
response.approvals_reviewer.to_core(),
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(
|
||||
&response.sandbox.to_core(),
|
||||
response.cwd.as_path(),
|
||||
)
|
||||
}),
|
||||
permission_profile,
|
||||
response.active_permission_profile.clone().map(Into::into),
|
||||
response.cwd.clone(),
|
||||
response.instruction_sources.clone(),
|
||||
response.reasoning_effort,
|
||||
@@ -1354,7 +1410,15 @@ async fn thread_session_state_from_thread_resume_response(
|
||||
async fn thread_session_state_from_thread_fork_response(
|
||||
response: &ThreadForkResponse,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> Result<ThreadSessionState, String> {
|
||||
let permission_profile = permission_profile_from_thread_response(
|
||||
&response.sandbox,
|
||||
response.permission_profile.as_ref(),
|
||||
response.cwd.as_path(),
|
||||
config,
|
||||
thread_params_mode,
|
||||
);
|
||||
thread_session_state_from_thread_response(
|
||||
&response.thread.id,
|
||||
response.thread.forked_from_id.clone(),
|
||||
@@ -1365,16 +1429,8 @@ async fn thread_session_state_from_thread_fork_response(
|
||||
response.service_tier,
|
||||
response.approval_policy.to_core(),
|
||||
response.approvals_reviewer.to_core(),
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|| {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(
|
||||
&response.sandbox.to_core(),
|
||||
response.cwd.as_path(),
|
||||
)
|
||||
}),
|
||||
permission_profile,
|
||||
response.active_permission_profile.clone().map(Into::into),
|
||||
response.cwd.clone(),
|
||||
response.instruction_sources.clone(),
|
||||
response.reasoning_effort,
|
||||
@@ -1383,6 +1439,24 @@ async fn thread_session_state_from_thread_fork_response(
|
||||
.await
|
||||
}
|
||||
|
||||
fn permission_profile_from_thread_response(
|
||||
sandbox: &codex_app_server_protocol::SandboxPolicy,
|
||||
permission_profile: Option<&codex_app_server_protocol::PermissionProfile>,
|
||||
cwd: &std::path::Path,
|
||||
config: &Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
) -> PermissionProfile {
|
||||
if let Some(permission_profile) = permission_profile {
|
||||
return permission_profile.clone().into();
|
||||
}
|
||||
match thread_params_mode {
|
||||
ThreadParamsMode::Embedded => config.permissions.permission_profile(),
|
||||
ThreadParamsMode::Remote => {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(&sandbox.to_core(), cwd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn review_target_to_app_server(
|
||||
target: CoreReviewTarget,
|
||||
) -> codex_app_server_protocol::ReviewTarget {
|
||||
@@ -1417,6 +1491,7 @@ async fn thread_session_state_from_thread_response(
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
cwd: AbsolutePathBuf,
|
||||
instruction_source_paths: Vec<AbsolutePathBuf>,
|
||||
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
|
||||
@@ -1442,6 +1517,7 @@ async fn thread_session_state_from_thread_response(
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
cwd,
|
||||
instruction_source_paths,
|
||||
reasoning_effort,
|
||||
@@ -1505,6 +1581,7 @@ fn app_server_credits_snapshot_to_core(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::legacy_core::config::ConfigBuilder;
|
||||
use crate::legacy_core::config::ConfigOverrides;
|
||||
use codex_app_server_protocol::ThreadStatus;
|
||||
use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
@@ -1530,7 +1607,15 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn thread_start_params_include_cwd_for_embedded_sessions() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let config = build_config(&temp_dir).await;
|
||||
let config = ConfigBuilder::default()
|
||||
.codex_home(temp_dir.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
..ConfigOverrides::default()
|
||||
})
|
||||
.build()
|
||||
.await
|
||||
.expect("config should build");
|
||||
|
||||
let params = thread_start_params_from_config(
|
||||
&config,
|
||||
@@ -1542,8 +1627,11 @@ mod tests {
|
||||
assert_eq!(params.cwd, Some(config.cwd.to_string_lossy().to_string()));
|
||||
assert_eq!(params.sandbox, None);
|
||||
assert_eq!(
|
||||
params.permission_profile,
|
||||
Some(config.permissions.permission_profile().into())
|
||||
params.permissions,
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.map(permissions_selection_from_active_profile)
|
||||
);
|
||||
assert_eq!(params.model_provider, Some(config.model_provider_id));
|
||||
}
|
||||
@@ -1563,6 +1651,64 @@ mod tests {
|
||||
assert_eq!(params.session_start_source, Some(ThreadStartSource::Clear));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_turn_permissions_use_active_profile_selection() {
|
||||
let cwd = test_path_buf("/workspace/project").abs();
|
||||
let active_permission_profile = ActivePermissionProfile::new(":workspace");
|
||||
let expected_permissions =
|
||||
permissions_selection_from_active_profile(active_permission_profile.clone());
|
||||
|
||||
let (sandbox_policy, permissions) = turn_permissions_overrides(
|
||||
&PermissionProfile::workspace_write(),
|
||||
Some(active_permission_profile),
|
||||
cwd.as_path(),
|
||||
ThreadParamsMode::Embedded,
|
||||
);
|
||||
|
||||
assert_eq!(sandbox_policy, None);
|
||||
assert_eq!(permissions, Some(expected_permissions));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_turn_permissions_fall_back_to_sandbox_without_active_profile() {
|
||||
let cwd = test_path_buf("/workspace/project").abs();
|
||||
|
||||
let (sandbox_policy, permissions) = turn_permissions_overrides(
|
||||
&PermissionProfile::read_only(),
|
||||
/*active_permission_profile*/ None,
|
||||
cwd.as_path(),
|
||||
ThreadParamsMode::Embedded,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sandbox_policy,
|
||||
Some(codex_app_server_protocol::SandboxPolicy::ReadOnly {
|
||||
network_access: false
|
||||
})
|
||||
);
|
||||
assert_eq!(permissions, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_turn_permissions_use_sandbox_even_with_active_profile() {
|
||||
let cwd = test_path_buf("/workspace/project").abs();
|
||||
|
||||
let (sandbox_policy, permissions) = turn_permissions_overrides(
|
||||
&PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new(":read-only")),
|
||||
cwd.as_path(),
|
||||
ThreadParamsMode::Remote,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
sandbox_policy,
|
||||
Some(codex_app_server_protocol::SandboxPolicy::ReadOnly {
|
||||
network_access: false
|
||||
})
|
||||
);
|
||||
assert_eq!(permissions, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_lifecycle_params_omit_cwd_without_remote_override_for_remote_sessions() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
@@ -1601,9 +1747,9 @@ mod tests {
|
||||
assert_eq!(start.sandbox, expected_sandbox);
|
||||
assert_eq!(resume.sandbox, expected_sandbox);
|
||||
assert_eq!(fork.sandbox, expected_sandbox);
|
||||
assert_eq!(start.permission_profile, None);
|
||||
assert_eq!(resume.permission_profile, None);
|
||||
assert_eq!(fork.permission_profile, None);
|
||||
assert_eq!(start.permissions, None);
|
||||
assert_eq!(resume.permissions, None);
|
||||
assert_eq!(fork.permissions, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1704,9 +1850,9 @@ mod tests {
|
||||
assert_eq!(start.sandbox, expected_sandbox);
|
||||
assert_eq!(resume.sandbox, expected_sandbox);
|
||||
assert_eq!(fork.sandbox, expected_sandbox);
|
||||
assert_eq!(start.permission_profile, None);
|
||||
assert_eq!(resume.permission_profile, None);
|
||||
assert_eq!(fork.permission_profile, None);
|
||||
assert_eq!(start.permissions, None);
|
||||
assert_eq!(resume.permissions, None);
|
||||
assert_eq!(fork.permissions, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1791,30 +1937,92 @@ mod tests {
|
||||
.to_legacy_sandbox_policy(test_path_buf("/tmp/project").as_path())
|
||||
.expect("read-only profile must be legacy-compatible")
|
||||
.into(),
|
||||
permission_profile: Some(read_only_profile.into()),
|
||||
permission_profile: Some(read_only_profile.clone().into()),
|
||||
active_permission_profile: None,
|
||||
reasoning_effort: None,
|
||||
};
|
||||
|
||||
let started = started_thread_from_resume_response(response.clone(), &config)
|
||||
.await
|
||||
.expect("resume response should map");
|
||||
let started = started_thread_from_resume_response(
|
||||
response.clone(),
|
||||
&config,
|
||||
ThreadParamsMode::Remote,
|
||||
)
|
||||
.await
|
||||
.expect("resume response should map");
|
||||
assert_eq!(started.session.forked_from_id, Some(forked_from_id));
|
||||
assert_eq!(
|
||||
started.session.instruction_source_paths,
|
||||
response.instruction_sources
|
||||
);
|
||||
assert_eq!(
|
||||
started.session.permission_profile,
|
||||
response
|
||||
.permission_profile
|
||||
.clone()
|
||||
.map(Into::into)
|
||||
.expect("response includes profile")
|
||||
);
|
||||
assert_eq!(started.session.permission_profile, read_only_profile);
|
||||
assert_eq!(started.turns.len(), 1);
|
||||
assert_eq!(started.turns[0], response.thread.turns[0]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remote_thread_response_prefers_permission_profile_over_legacy_sandbox() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let config = build_config(&temp_dir).await;
|
||||
let cwd = test_path_buf("/tmp/project").abs();
|
||||
let fallback_sandbox = PermissionProfile::read_only()
|
||||
.to_legacy_sandbox_policy(cwd.as_path())
|
||||
.expect("read-only profile must be legacy-compatible")
|
||||
.into();
|
||||
let split_profile = PermissionProfile::Managed {
|
||||
file_system: ManagedFileSystemPermissions::Restricted {
|
||||
entries: vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::ProjectRoots {
|
||||
subpath: Some(".env".into()),
|
||||
},
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
},
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
let response_profile = split_profile.clone().into();
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_from_thread_response(
|
||||
&fallback_sandbox,
|
||||
Some(&response_profile),
|
||||
cwd.as_path(),
|
||||
&config,
|
||||
ThreadParamsMode::Remote,
|
||||
),
|
||||
split_profile
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn embedded_thread_response_prefers_permission_profile_when_present() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let config = build_config(&temp_dir).await;
|
||||
let cwd = test_path_buf("/tmp/project").abs();
|
||||
let response_profile = PermissionProfile::read_only().into();
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_from_thread_response(
|
||||
&codex_app_server_protocol::SandboxPolicy::DangerFullAccess,
|
||||
Some(&response_profile),
|
||||
cwd.as_path(),
|
||||
&config,
|
||||
ThreadParamsMode::Embedded,
|
||||
),
|
||||
PermissionProfile::read_only()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_configured_populates_history_metadata() {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
@@ -1839,6 +2047,7 @@ mod tests {
|
||||
AskForApproval::Never,
|
||||
codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
PermissionProfile::read_only(),
|
||||
/*active_permission_profile*/ None,
|
||||
test_path_buf("/tmp/project").abs(),
|
||||
Vec::new(),
|
||||
/*reasoning_effort*/ None,
|
||||
@@ -1869,6 +2078,7 @@ mod tests {
|
||||
AskForApproval::Never,
|
||||
codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
PermissionProfile::read_only(),
|
||||
/*active_permission_profile*/ None,
|
||||
test_path_buf("/tmp/project").abs(),
|
||||
Vec::new(),
|
||||
/*reasoning_effort*/ None,
|
||||
|
||||
@@ -1645,6 +1645,7 @@ fn thread_session_state_to_legacy_event(
|
||||
approval_policy: session.approval_policy,
|
||||
approvals_reviewer: session.approvals_reviewer,
|
||||
permission_profile: session.permission_profile,
|
||||
active_permission_profile: session.active_permission_profile,
|
||||
cwd: session.cwd,
|
||||
reasoning_effort: session.reasoning_effort,
|
||||
history_log_id: session.history_log_id,
|
||||
@@ -2377,11 +2378,16 @@ impl ChatWidget {
|
||||
let permission_sync = self
|
||||
.config
|
||||
.permissions
|
||||
.set_permission_profile(event.permission_profile.clone());
|
||||
.set_permission_profile_with_active_profile(
|
||||
event.permission_profile.clone(),
|
||||
event.active_permission_profile.clone(),
|
||||
);
|
||||
if let Err(err) = permission_sync {
|
||||
tracing::warn!(%err, "failed to sync permissions from SessionConfigured");
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(event.permission_profile.clone());
|
||||
self.config.permissions.active_permission_profile =
|
||||
event.active_permission_profile.clone();
|
||||
}
|
||||
self.config.approvals_reviewer = event.approvals_reviewer;
|
||||
self.status_line_project_root_name_cache = None;
|
||||
|
||||
@@ -17,6 +17,7 @@ async fn submission_preserves_text_elements_and_local_images() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -121,6 +122,7 @@ async fn submission_includes_configured_permission_profile() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -171,6 +173,7 @@ async fn submission_keeps_profile_when_legacy_projection_is_external() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -214,6 +217,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -309,6 +313,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -374,6 +379,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -414,6 +420,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -454,6 +461,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -497,6 +505,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -1059,6 +1059,7 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -24,6 +24,7 @@ async fn resumed_initial_messages_render_history() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -137,6 +138,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -198,6 +200,7 @@ async fn replayed_user_message_preserves_remote_image_urls() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -286,6 +289,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
active_permission_profile: None,
|
||||
cwd: expected_cwd.clone(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -344,6 +348,7 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile,
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/external").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -393,6 +398,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -446,6 +452,7 @@ async fn replayed_user_message_with_only_local_images_does_not_render_history_ce
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -767,6 +774,7 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -814,6 +822,7 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -542,6 +542,7 @@ async fn permissions_selection_marks_auto_review_current_after_session_configure
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::AutoReview,
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -596,6 +597,7 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::AutoReview,
|
||||
permission_profile,
|
||||
active_permission_profile: None,
|
||||
cwd,
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -1196,6 +1196,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -1441,6 +1442,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -1791,6 +1791,7 @@ async fn session_configured_clears_goal_status_footer() {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -3194,6 +3194,7 @@ mod tests {
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
+106
-13
@@ -9,6 +9,9 @@ use chrono::Local;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::account::PlanType;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
@@ -281,18 +284,20 @@ impl StatusHistoryCell {
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let permission_profile = config.permissions.permission_profile();
|
||||
let active_permission_profile = config.permissions.active_permission_profile();
|
||||
let sandbox = status_permission_summary(&permission_profile, config.cwd.as_path());
|
||||
let permissions = if config.permissions.approval_policy.value() == AskForApproval::OnRequest
|
||||
&& permission_profile == PermissionProfile::workspace_write()
|
||||
{
|
||||
"Default".to_string()
|
||||
} else if config.permissions.approval_policy.value() == AskForApproval::Never
|
||||
&& permission_profile == PermissionProfile::Disabled
|
||||
{
|
||||
"Full Access".to_string()
|
||||
} else {
|
||||
format!("Custom ({sandbox}, {approval})")
|
||||
};
|
||||
let approval = status_approval_label(
|
||||
config.permissions.approval_policy.value(),
|
||||
config.approvals_reviewer,
|
||||
&approval,
|
||||
);
|
||||
let permissions = status_permissions_label(
|
||||
active_permission_profile.as_ref(),
|
||||
&permission_profile,
|
||||
config.permissions.approval_policy.value(),
|
||||
&sandbox,
|
||||
&approval,
|
||||
);
|
||||
let model_provider = format_model_provider(config);
|
||||
let account = compose_account_display(account_display);
|
||||
let session_id = session_id.as_ref().map(std::string::ToString::to_string);
|
||||
@@ -537,11 +542,17 @@ impl StatusHistoryCell {
|
||||
|
||||
fn status_permission_summary(permission_profile: &PermissionProfile, cwd: &Path) -> String {
|
||||
let summary = summarize_permission_profile(permission_profile, cwd);
|
||||
if let Some(details) = summary.strip_prefix("read-only") {
|
||||
if details.contains("(network access enabled)") {
|
||||
return "read-only with network access".to_string();
|
||||
}
|
||||
return "read-only".to_string();
|
||||
}
|
||||
if let Some(details) = summary.strip_prefix("workspace-write") {
|
||||
if details.contains("(network access enabled)") {
|
||||
return "workspace-write with network access".to_string();
|
||||
return "workspace with network access".to_string();
|
||||
}
|
||||
return "workspace-write".to_string();
|
||||
return "workspace".to_string();
|
||||
}
|
||||
if summary == "custom permissions (network access enabled)" {
|
||||
return "custom permissions with network access".to_string();
|
||||
@@ -549,6 +560,88 @@ fn status_permission_summary(permission_profile: &PermissionProfile, cwd: &Path)
|
||||
summary
|
||||
}
|
||||
|
||||
fn status_permissions_label(
|
||||
active_permission_profile: Option<&ActivePermissionProfile>,
|
||||
permission_profile: &PermissionProfile,
|
||||
approval_policy: AskForApproval,
|
||||
sandbox: &str,
|
||||
approval: &str,
|
||||
) -> String {
|
||||
let active_id = active_permission_profile.map(|active| active.id.as_str());
|
||||
let writable_root_modifications = active_permission_profile
|
||||
.map(|active| {
|
||||
active
|
||||
.modifications
|
||||
.iter()
|
||||
.filter(|modification| {
|
||||
matches!(
|
||||
modification,
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { .. }
|
||||
)
|
||||
})
|
||||
.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let modification_suffix = match writable_root_modifications {
|
||||
0 => String::new(),
|
||||
1 => " + 1 writable root".to_string(),
|
||||
count => format!(" + {count} writable roots"),
|
||||
};
|
||||
match active_id {
|
||||
Some(":read-only") => {
|
||||
let label = if sandbox == "read-only with network access" {
|
||||
"Read Only with network access"
|
||||
} else {
|
||||
"Read Only"
|
||||
};
|
||||
return format!("{label}{modification_suffix} ({approval})");
|
||||
}
|
||||
Some(":workspace") => match sandbox {
|
||||
"workspace" => return format!("Workspace{modification_suffix} ({approval})"),
|
||||
"workspace with network access" => {
|
||||
return format!("Workspace with network access{modification_suffix} ({approval})");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Some(":danger-no-sandbox") if permission_profile == &PermissionProfile::Disabled => {
|
||||
return if approval_policy == AskForApproval::Never {
|
||||
"Full Access".to_string()
|
||||
} else {
|
||||
format!("No Sandbox ({approval})")
|
||||
};
|
||||
}
|
||||
Some(id) => return format!("Profile {id}{modification_suffix} ({sandbox}, {approval})"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
if sandbox == "read-only" {
|
||||
return format!("Read Only ({approval})");
|
||||
}
|
||||
if approval_policy == AskForApproval::OnRequest && sandbox == "workspace" {
|
||||
return format!("Workspace ({approval})");
|
||||
}
|
||||
if approval_policy == AskForApproval::Never
|
||||
&& permission_profile == &PermissionProfile::Disabled
|
||||
{
|
||||
return "Full Access".to_string();
|
||||
}
|
||||
format!("Custom ({sandbox}, {approval})")
|
||||
}
|
||||
|
||||
fn status_approval_label(
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
approval: &str,
|
||||
) -> String {
|
||||
if approval_policy == AskForApproval::OnRequest
|
||||
&& approvals_reviewer == ApprovalsReviewer::AutoReview
|
||||
{
|
||||
"auto-review".to_string()
|
||||
} else {
|
||||
approval.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl HistoryCell for StatusHistoryCell {
|
||||
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
|
||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||
|
||||
+17
-17
@@ -4,20 +4,20 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.05K total (700 input + 350 output) │
|
||||
│ Context window: 100% left (1.45K used / 272K) │
|
||||
│ 5h limit: [████████░░░░░░░░░░░░] 40% left (resets 11:32) │
|
||||
│ Weekly limit: [█████████████░░░░░░░] 65% left (resets 11:52) │
|
||||
│ Warning: limits may be stale - start new turn to refresh. │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.05K total (700 input + 350 output) │
|
||||
│ Context window: 100% left (1.45K used / 272K) │
|
||||
│ 5h limit: [████████░░░░░░░░░░░░] 40% left (resets 11:32) │
|
||||
│ Weekly limit: [█████████████░░░░░░░] 65% left (resets 11:52) │
|
||||
│ Warning: limits may be stale - start new turn to refresh. │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+17
-17
@@ -4,20 +4,20 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 2K total (1.4K input + 600 output) │
|
||||
│ Context window: 100% left (2.2K used / 272K) │
|
||||
│ 5h limit: [███████████░░░░░░░░░] 55% left (resets 09:25) │
|
||||
│ Weekly limit: [██████████████░░░░░░] 70% left (resets 09:55) │
|
||||
│ Credits: 38 credits │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 2K total (1.4K input + 600 output) │
|
||||
│ Context window: 100% left (2.2K used / 272K) │
|
||||
│ 5h limit: [███████████░░░░░░░░░] 55% left (resets 09:25) │
|
||||
│ Weekly limit: [██████████████░░░░░░] 70% left (resets 09:55) │
|
||||
│ Credits: 38 credits │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+17
-17
@@ -4,20 +4,20 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ Session: 0f0f3c13-6cf9-4aa4-8b80-7d49c2f1be2e │
|
||||
│ Forked from: e9f18a88-8081-4e51-9d4e-8af5cde2d8dd │
|
||||
│ │
|
||||
│ Token usage: 1.2K total (800 input + 400 output) │
|
||||
│ Context window: 100% left (1.2K used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ Session: 0f0f3c13-6cf9-4aa4-8b80-7d49c2f1be2e │
|
||||
│ Forked from: e9f18a88-8081-4e51-9d4e-8af5cde2d8dd │
|
||||
│ │
|
||||
│ Token usage: 1.2K total (800 input + 400 output) │
|
||||
│ Context window: 100% left (1.2K used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+15
-15
@@ -4,18 +4,18 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.2K total (800 input + 400 output) │
|
||||
│ Context window: 100% left (1.2K used / 272K) │
|
||||
│ Monthly limit: [██████████████████░░] 88% left (resets 07:08 on 7 May) │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.2K total (800 input + 400 output) │
|
||||
│ Context window: 100% left (1.2K used / 272K) │
|
||||
│ Monthly limit: [██████████████████░░] 88% left (resets 07:08 on 7 May) │
|
||||
╰────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ expression: sanitized
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning high, summaries detailed) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Default │
|
||||
│ Permissions: Workspace (on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.9K total (1K input + 900 output) │
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: tui/src/status/tests.rs
|
||||
expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Profile locked (read-only, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 0 total (0 input + 0 output) │
|
||||
│ Context window: 100% left (0 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: tui/src/status/tests.rs
|
||||
expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Workspace (auto-review) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 0 total (0 input + 0 output) │
|
||||
│ Context window: 100% left (0 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
+15
-15
@@ -4,18 +4,18 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+16
-16
@@ -4,19 +4,19 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ 5h limit: [███████████░░░░░░░░░] 55% left (resets 08:24) │
|
||||
│ Weekly limit: [██████████████░░░░░░] 70% left (resets 08:54) │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ 5h limit: [███████████░░░░░░░░░] 55% left (resets 08:24) │
|
||||
│ Weekly limit: [██████████████░░░░░░] 70% left (resets 08:54) │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+17
-17
@@ -4,20 +4,20 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.9K total (1K input + 900 output) │
|
||||
│ Context window: 100% left (2.25K used / 272K) │
|
||||
│ 5h limit: [██████░░░░░░░░░░░░░░] 28% left (resets 03:14) │
|
||||
│ Weekly limit: [████████████░░░░░░░░] 60% left (resets 03:34) │
|
||||
│ Warning: limits may be stale - start new turn to refresh. │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.9K total (1K input + 900 output) │
|
||||
│ Context window: 100% left (2.25K used / 272K) │
|
||||
│ 5h limit: [██████░░░░░░░░░░░░░░] 28% left (resets 03:14) │
|
||||
│ Weekly limit: [████████████░░░░░░░░] 60% left (resets 03:34) │
|
||||
│ Warning: limits may be stale - start new turn to refresh. │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+15
-15
@@ -4,18 +4,18 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: not available for this account │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: not available for this account │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+15
-15
@@ -4,18 +4,18 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: not available for this account │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭───────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning none, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: not available for this account │
|
||||
╰───────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ expression: sanitized
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning high, summaries de │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on │
|
||||
│ Permissions: Custom (workspace with network access, on-reque │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 1.9K total (1K input + 900 output) │
|
||||
|
||||
+15
-15
@@ -4,18 +4,18 @@ expression: sanitized
|
||||
---
|
||||
/status
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace-write with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰─────────────────────────────────────────────────────────────────────────────╯
|
||||
╭─────────────────────────────────────────────────────────────────────────╮
|
||||
│ >_ OpenAI Codex (v0.0.0) │
|
||||
│ │
|
||||
│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │
|
||||
│ information on rate limits and credits │
|
||||
│ │
|
||||
│ Model: gpt-5.1-codex-max (reasoning medium, summaries auto) │
|
||||
│ Directory: [[workspace]] │
|
||||
│ Permissions: Custom (workspace with network access, on-request) │
|
||||
│ Agents.md: <none> │
|
||||
│ │
|
||||
│ Token usage: 750 total (500 input + 250 output) │
|
||||
│ Context window: 100% left (750 used / 272K) │
|
||||
│ Limits: data not available yet │
|
||||
╰─────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
@@ -12,7 +12,10 @@ use chrono::Duration as ChronoDuration;
|
||||
use chrono::TimeZone;
|
||||
use chrono::Utc;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
@@ -212,7 +215,7 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_non_default_workspace_write_is_custom() {
|
||||
async fn status_permissions_non_default_workspace_write_uses_workspace_label() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
@@ -235,10 +238,292 @@ async fn status_permissions_non_default_workspace_write_is_custom() {
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Custom (workspace-write with network access, on-request)")
|
||||
Some("Custom (workspace with network access, on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_named_read_only_profile_shows_builtin_label() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new(":read-only")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Read Only (on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_read_only_profile_shows_additional_writable_roots() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
let extra_root = test_path_buf("/workspace/extra").abs();
|
||||
let file_system_policy = PermissionProfile::read_only()
|
||||
.file_system_sandbox_policy()
|
||||
.with_additional_writable_roots(config.cwd.as_path(), std::slice::from_ref(&extra_root));
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
Some(
|
||||
ActivePermissionProfile::new(":read-only").with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot {
|
||||
path: extra_root,
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Read Only + 1 writable root (on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_named_workspace_profile_shows_builtin_label() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Workspace (on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_workspace_auto_review_shows_reviewer_label() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.approvals_reviewer = ApprovalsReviewer::AutoReview;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Workspace (auto-review)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_named_profile_shows_additional_writable_roots() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
let extra_root = test_path_buf("/workspace/extra").abs();
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write_with(
|
||||
std::slice::from_ref(&extra_root),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
/*exclude_tmpdir_env_var*/ false,
|
||||
/*exclude_slash_tmp*/ false,
|
||||
),
|
||||
Some(
|
||||
ActivePermissionProfile::new(":workspace").with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot {
|
||||
path: extra_root,
|
||||
},
|
||||
]),
|
||||
),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Workspace + 1 writable root (on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_broadened_workspace_profile_shows_builtin_label() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write_with(
|
||||
&[],
|
||||
NetworkSandboxPolicy::Enabled,
|
||||
/*exclude_tmpdir_env_var*/ false,
|
||||
/*exclude_slash_tmp*/ false,
|
||||
),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Workspace with network access (on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_user_defined_profile_shows_name() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new("locked")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
assert_eq!(
|
||||
permissions_text_for(&config).as_deref(),
|
||||
Some("Profile locked (read-only, on-request)")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_snapshot_shows_active_user_defined_profile() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = test_path_buf("/workspace/tests").abs();
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::read_only(),
|
||||
Some(ActivePermissionProfile::new("locked")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
let usage = TokenUsage::default();
|
||||
let captured_at = chrono::Local
|
||||
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
|
||||
.single()
|
||||
.expect("timestamp");
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let token_info = token_info_for(&model_slug, &config, &usage);
|
||||
|
||||
let composite = new_status_output(
|
||||
&config,
|
||||
test_status_account_display().as_ref(),
|
||||
Some(&token_info),
|
||||
&usage,
|
||||
&None,
|
||||
/*thread_name*/ None,
|
||||
/*forked_from*/ None,
|
||||
/*rate_limits*/ None,
|
||||
None,
|
||||
captured_at,
|
||||
&model_slug,
|
||||
/*collaboration_mode*/ None,
|
||||
/*reasoning_effort_override*/ None,
|
||||
);
|
||||
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
|
||||
if cfg!(windows) {
|
||||
for line in &mut rendered_lines {
|
||||
*line = line.replace('\\', "/");
|
||||
}
|
||||
}
|
||||
let sanitized = sanitize_directory(rendered_lines).join("\n");
|
||||
assert_snapshot!(sanitized);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_snapshot_shows_auto_review_permissions() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
let mut config = test_config(&temp_home).await;
|
||||
config.model = Some("gpt-5.1-codex-max".to_string());
|
||||
config.cwd = test_path_buf("/workspace/tests").abs();
|
||||
config.approvals_reviewer = ApprovalsReviewer::AutoReview;
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_with_active_profile(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(":workspace")),
|
||||
)
|
||||
.expect("set permission profile");
|
||||
|
||||
let usage = TokenUsage::default();
|
||||
let captured_at = chrono::Local
|
||||
.with_ymd_and_hms(2024, 1, 2, 3, 4, 5)
|
||||
.single()
|
||||
.expect("timestamp");
|
||||
let model_slug = crate::legacy_core::test_support::get_model_offline(config.model.as_deref());
|
||||
let token_info = token_info_for(&model_slug, &config, &usage);
|
||||
|
||||
let composite = new_status_output(
|
||||
&config,
|
||||
test_status_account_display().as_ref(),
|
||||
Some(&token_info),
|
||||
&usage,
|
||||
&None,
|
||||
/*thread_name*/ None,
|
||||
/*forked_from*/ None,
|
||||
/*rate_limits*/ None,
|
||||
None,
|
||||
captured_at,
|
||||
&model_slug,
|
||||
/*collaboration_mode*/ None,
|
||||
/*reasoning_effort_override*/ None,
|
||||
);
|
||||
let mut rendered_lines = render_lines(&composite.display_lines(/*width*/ 80));
|
||||
if cfg!(windows) {
|
||||
for line in &mut rendered_lines {
|
||||
*line = line.replace('\\', "/");
|
||||
}
|
||||
}
|
||||
let sanitized = sanitize_directory(rendered_lines).join("\n");
|
||||
assert_snapshot!(sanitized);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn status_permissions_full_disk_managed_with_network_is_danger_full_access() {
|
||||
let temp_home = TempDir::new().expect("temp home");
|
||||
|
||||
Reference in New Issue
Block a user