diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index bf97da4284..03592c9ff6 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -65,6 +65,7 @@ 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; @@ -91,6 +92,7 @@ use codex_plugin::PluginTelemetryMetadata; use codex_protocol::approvals::NetworkApprovalProtocol; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::ModeKind; +use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::HookEventName; use codex_protocol::protocol::HookRunStatus; @@ -152,11 +154,20 @@ fn sample_thread_start_response(thread_id: &str, ephemeral: bool, model: &str) - approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, + permission_profile: Some(sample_permission_profile()), reasoning_effort: None, }, } } +fn sample_permission_profile() -> AppServerPermissionProfile { + CorePermissionProfile::from_legacy_sandbox_policy( + &SandboxPolicy::DangerFullAccess, + &test_path_buf("/tmp"), + ) + .into() +} + fn sample_app_server_client_metadata() -> CodexAppServerClientMetadata { CodexAppServerClientMetadata { product_client_id: DEFAULT_ORIGINATOR.to_string(), @@ -203,6 +214,7 @@ fn sample_thread_resume_response_with_source( approval_policy: AppServerAskForApproval::OnFailure, approvals_reviewer: AppServerApprovalsReviewer::User, sandbox: AppServerSandboxPolicy::DangerFullAccess, + permission_profile: Some(sample_permission_profile()), reasoning_effort: None, }, } diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 5e1e44bdd6..231df093f3 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -10896,6 +10896,64 @@ } ] }, + "PermissionProfile": { + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PermissionProfileFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PermissionProfileNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "PermissionProfileFileSystemPermissions": { + "properties": { + "entries": { + "items": { + "$ref": "#/definitions/v2/FileSystemSandboxEntry" + }, + "type": "array" + }, + "globScanMaxDepth": { + "format": "uint", + "minimum": 1.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "entries" + ], + "type": "object" + }, + "PermissionProfileNetworkPermissions": { + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "Personality": { "enum": [ "none", @@ -14067,6 +14125,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -14078,7 +14148,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/v2/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ @@ -15457,6 +15532,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -15468,7 +15555,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/v2/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ @@ -15756,6 +15848,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -15767,7 +15871,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/v2/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d91c1c1604..842043ff30 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -7658,6 +7658,64 @@ } ] }, + "PermissionProfile": { + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "PermissionProfileFileSystemPermissions": { + "properties": { + "entries": { + "items": { + "$ref": "#/definitions/FileSystemSandboxEntry" + }, + "type": "array" + }, + "globScanMaxDepth": { + "format": "uint", + "minimum": 1.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "entries" + ], + "type": "object" + }, + "PermissionProfileNetworkPermissions": { + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "Personality": { "enum": [ "none", @@ -11961,6 +12019,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -11972,7 +12042,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ @@ -13351,6 +13426,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -13362,7 +13449,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ @@ -13650,6 +13742,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -13661,7 +13765,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index a01f7bee71..91f6db519f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -448,6 +448,217 @@ ], "type": "string" }, + "FileSystemAccessMode": { + "enum": [ + "read", + "write", + "none" + ], + "type": "string" + }, + "FileSystemPath": { + "oneOf": [ + { + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "path" + ], + "title": "PathFileSystemPathType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "PathFileSystemPath", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": "string" + }, + "type": { + "enum": [ + "glob_pattern" + ], + "title": "GlobPatternFileSystemPathType", + "type": "string" + } + }, + "required": [ + "pattern", + "type" + ], + "title": "GlobPatternFileSystemPath", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "special" + ], + "title": "SpecialFileSystemPathType", + "type": "string" + }, + "value": { + "$ref": "#/definitions/FileSystemSpecialPath" + } + }, + "required": [ + "type", + "value" + ], + "title": "SpecialFileSystemPath", + "type": "object" + } + ] + }, + "FileSystemSandboxEntry": { + "properties": { + "access": { + "$ref": "#/definitions/FileSystemAccessMode" + }, + "path": { + "$ref": "#/definitions/FileSystemPath" + } + }, + "required": [ + "access", + "path" + ], + "type": "object" + }, + "FileSystemSpecialPath": { + "oneOf": [ + { + "properties": { + "kind": { + "enum": [ + "root" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "RootFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "minimal" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "MinimalFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "current_working_directory" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "CurrentWorkingDirectoryFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "project_roots" + ], + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "title": "KindFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "tmpdir" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "TmpdirFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "slash_tmp" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "SlashTmpFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "unknown" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "path" + ], + "type": "object" + } + ] + }, "FileUpdateChange": { "properties": { "diff": { @@ -686,6 +897,64 @@ } ] }, + "PermissionProfile": { + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "PermissionProfileFileSystemPermissions": { + "properties": { + "entries": { + "items": { + "$ref": "#/definitions/FileSystemSandboxEntry" + }, + "type": "array" + }, + "globScanMaxDepth": { + "format": "uint", + "minimum": 1.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "entries" + ], + "type": "object" + }, + "PermissionProfileNetworkPermissions": { + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ReadOnlyAccess": { "oneOf": [ { @@ -2225,6 +2494,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -2236,7 +2517,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 6d504c6004..79128bdac8 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -448,6 +448,217 @@ ], "type": "string" }, + "FileSystemAccessMode": { + "enum": [ + "read", + "write", + "none" + ], + "type": "string" + }, + "FileSystemPath": { + "oneOf": [ + { + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "path" + ], + "title": "PathFileSystemPathType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "PathFileSystemPath", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": "string" + }, + "type": { + "enum": [ + "glob_pattern" + ], + "title": "GlobPatternFileSystemPathType", + "type": "string" + } + }, + "required": [ + "pattern", + "type" + ], + "title": "GlobPatternFileSystemPath", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "special" + ], + "title": "SpecialFileSystemPathType", + "type": "string" + }, + "value": { + "$ref": "#/definitions/FileSystemSpecialPath" + } + }, + "required": [ + "type", + "value" + ], + "title": "SpecialFileSystemPath", + "type": "object" + } + ] + }, + "FileSystemSandboxEntry": { + "properties": { + "access": { + "$ref": "#/definitions/FileSystemAccessMode" + }, + "path": { + "$ref": "#/definitions/FileSystemPath" + } + }, + "required": [ + "access", + "path" + ], + "type": "object" + }, + "FileSystemSpecialPath": { + "oneOf": [ + { + "properties": { + "kind": { + "enum": [ + "root" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "RootFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "minimal" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "MinimalFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "current_working_directory" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "CurrentWorkingDirectoryFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "project_roots" + ], + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "title": "KindFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "tmpdir" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "TmpdirFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "slash_tmp" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "SlashTmpFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "unknown" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "path" + ], + "type": "object" + } + ] + }, "FileUpdateChange": { "properties": { "diff": { @@ -686,6 +897,64 @@ } ] }, + "PermissionProfile": { + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "PermissionProfileFileSystemPermissions": { + "properties": { + "entries": { + "items": { + "$ref": "#/definitions/FileSystemSandboxEntry" + }, + "type": "array" + }, + "globScanMaxDepth": { + "format": "uint", + "minimum": 1.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "entries" + ], + "type": "object" + }, + "PermissionProfileNetworkPermissions": { + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ReadOnlyAccess": { "oneOf": [ { @@ -2225,6 +2494,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -2236,7 +2517,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 0d0ff5d9dc..95063f2c92 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -448,6 +448,217 @@ ], "type": "string" }, + "FileSystemAccessMode": { + "enum": [ + "read", + "write", + "none" + ], + "type": "string" + }, + "FileSystemPath": { + "oneOf": [ + { + "properties": { + "path": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "type": { + "enum": [ + "path" + ], + "title": "PathFileSystemPathType", + "type": "string" + } + }, + "required": [ + "path", + "type" + ], + "title": "PathFileSystemPath", + "type": "object" + }, + { + "properties": { + "pattern": { + "type": "string" + }, + "type": { + "enum": [ + "glob_pattern" + ], + "title": "GlobPatternFileSystemPathType", + "type": "string" + } + }, + "required": [ + "pattern", + "type" + ], + "title": "GlobPatternFileSystemPath", + "type": "object" + }, + { + "properties": { + "type": { + "enum": [ + "special" + ], + "title": "SpecialFileSystemPathType", + "type": "string" + }, + "value": { + "$ref": "#/definitions/FileSystemSpecialPath" + } + }, + "required": [ + "type", + "value" + ], + "title": "SpecialFileSystemPath", + "type": "object" + } + ] + }, + "FileSystemSandboxEntry": { + "properties": { + "access": { + "$ref": "#/definitions/FileSystemAccessMode" + }, + "path": { + "$ref": "#/definitions/FileSystemPath" + } + }, + "required": [ + "access", + "path" + ], + "type": "object" + }, + "FileSystemSpecialPath": { + "oneOf": [ + { + "properties": { + "kind": { + "enum": [ + "root" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "RootFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "minimal" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "MinimalFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "current_working_directory" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "CurrentWorkingDirectoryFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "project_roots" + ], + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind" + ], + "title": "KindFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "tmpdir" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "TmpdirFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "slash_tmp" + ], + "type": "string" + } + }, + "required": [ + "kind" + ], + "title": "SlashTmpFileSystemSpecialPath", + "type": "object" + }, + { + "properties": { + "kind": { + "enum": [ + "unknown" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "kind", + "path" + ], + "type": "object" + } + ] + }, "FileUpdateChange": { "properties": { "diff": { @@ -686,6 +897,64 @@ } ] }, + "PermissionProfile": { + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfileNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, + "PermissionProfileFileSystemPermissions": { + "properties": { + "entries": { + "items": { + "$ref": "#/definitions/FileSystemSandboxEntry" + }, + "type": "array" + }, + "globScanMaxDepth": { + "format": "uint", + "minimum": 1.0, + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "entries" + ], + "type": "object" + }, + "PermissionProfileNetworkPermissions": { + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ReadOnlyAccess": { "oneOf": [ { @@ -2225,6 +2494,18 @@ "modelProvider": { "type": "string" }, + "permissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/PermissionProfile" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Canonical active permissions view for this thread when representable. This is `null` for external sandbox policies because external enforcement cannot be round-tripped as a `PermissionProfile`." + }, "reasoningEffort": { "anyOf": [ { @@ -2236,7 +2517,12 @@ ] }, "sandbox": { - "$ref": "#/definitions/SandboxPolicy" + "allOf": [ + { + "$ref": "#/definitions/SandboxPolicy" + } + ], + "description": "Legacy sandbox policy retained for compatibility. New clients should use `permissionProfile` when present as the canonical active permissions view." }, "serviceTier": { "anyOf": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfile.ts new file mode 100644 index 0000000000..c38bde54b0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfile.ts @@ -0,0 +1,7 @@ +// 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 { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions"; +import type { PermissionProfileNetworkPermissions } from "./PermissionProfileNetworkPermissions"; + +export type PermissionProfile = { network: PermissionProfileNetworkPermissions | null, fileSystem: PermissionProfileFileSystemPermissions | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileFileSystemPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileFileSystemPermissions.ts new file mode 100644 index 0000000000..204a42764c --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileFileSystemPermissions.ts @@ -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 { FileSystemSandboxEntry } from "./FileSystemSandboxEntry"; + +export type PermissionProfileFileSystemPermissions = { entries: Array, globScanMaxDepth?: number, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileNetworkPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileNetworkPermissions.ts new file mode 100644 index 0000000000..9aa130412a --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfileNetworkPermissions.ts @@ -0,0 +1,5 @@ +// 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. + +export type PermissionProfileNetworkPermissions = { enabled: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts index 470e98c9b8..5dc6b82a34 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadForkResponse.ts @@ -6,6 +6,7 @@ import type { ReasoningEffort } from "../ReasoningEffort"; import type { ServiceTier } from "../ServiceTier"; import type { ApprovalsReviewer } from "./ApprovalsReviewer"; import type { AskForApproval } from "./AskForApproval"; +import type { PermissionProfile } from "./PermissionProfile"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; @@ -17,4 +18,16 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** * Reviewer currently used for approval requests on this thread. */ -approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; +approvalsReviewer: ApprovalsReviewer, +/** + * Legacy sandbox policy retained for compatibility. New clients should use + * `permissionProfile` when present as the canonical active permissions + * view. + */ +sandbox: SandboxPolicy, +/** + * Canonical active permissions view for this thread when representable. + * This is `null` for external sandbox policies because external + * enforcement cannot be round-tripped as a `PermissionProfile`. + */ +permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts index 177add8350..d76ad5a58a 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadResumeResponse.ts @@ -6,6 +6,7 @@ import type { ReasoningEffort } from "../ReasoningEffort"; import type { ServiceTier } from "../ServiceTier"; import type { ApprovalsReviewer } from "./ApprovalsReviewer"; import type { AskForApproval } from "./AskForApproval"; +import type { PermissionProfile } from "./PermissionProfile"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; @@ -17,4 +18,16 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** * Reviewer currently used for approval requests on this thread. */ -approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; +approvalsReviewer: ApprovalsReviewer, +/** + * Legacy sandbox policy retained for compatibility. New clients should use + * `permissionProfile` when present as the canonical active permissions + * view. + */ +sandbox: SandboxPolicy, +/** + * Canonical active permissions view for this thread when representable. + * This is `null` for external sandbox policies because external + * enforcement cannot be round-tripped as a `PermissionProfile`. + */ +permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts index fd84a41ae8..5a83011abd 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadStartResponse.ts @@ -6,6 +6,7 @@ import type { ReasoningEffort } from "../ReasoningEffort"; import type { ServiceTier } from "../ServiceTier"; import type { ApprovalsReviewer } from "./ApprovalsReviewer"; import type { AskForApproval } from "./AskForApproval"; +import type { PermissionProfile } from "./PermissionProfile"; import type { SandboxPolicy } from "./SandboxPolicy"; import type { Thread } from "./Thread"; @@ -17,4 +18,16 @@ instructionSources: Array, approvalPolicy: AskForApproval, /** * Reviewer currently used for approval requests on this thread. */ -approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, }; +approvalsReviewer: ApprovalsReviewer, +/** + * Legacy sandbox policy retained for compatibility. New clients should use + * `permissionProfile` when present as the canonical active permissions + * view. + */ +sandbox: SandboxPolicy, +/** + * Canonical active permissions view for this thread when representable. + * This is `null` for external sandbox policies because external + * enforcement cannot be round-tripped as a `PermissionProfile`. + */ +permissionProfile: PermissionProfile | null, reasoningEffort: ReasoningEffort | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 0b4c13efef..4fe2d57f50 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -240,6 +240,9 @@ export type { OverriddenMetadata } from "./OverriddenMetadata"; export type { PatchApplyStatus } from "./PatchApplyStatus"; export type { PatchChangeKind } from "./PatchChangeKind"; export type { PermissionGrantScope } from "./PermissionGrantScope"; +export type { PermissionProfile } from "./PermissionProfile"; +export type { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions"; +export type { PermissionProfileNetworkPermissions } from "./PermissionProfileNetworkPermissions"; export type { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams"; export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse"; export type { PlanDeltaNotification } from "./PlanDeltaNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index f59551057c..5c858a026a 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1435,6 +1435,7 @@ mod tests { #[test] fn serialize_client_response() -> Result<()> { + let cwd = absolute_path("/tmp"); let response = ClientResponse::ThreadStart { request_id: RequestId::Integer(7), response: v2::ThreadStartResponse { @@ -1448,7 +1449,7 @@ mod tests { updated_at: 2, status: v2::ThreadStatus::Idle, path: None, - cwd: absolute_path("/tmp"), + cwd: cwd.clone(), cli_version: "0.0.0".to_string(), source: v2::SessionSource::Exec, agent_nickname: None, @@ -1460,11 +1461,18 @@ mod tests { model: "gpt-5".to_string(), model_provider: "openai".to_string(), service_tier: None, - cwd: absolute_path("/tmp"), + cwd: cwd.clone(), instruction_sources: vec![absolute_path("/tmp/AGENTS.md")], 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, + cwd.as_path(), + ) + .into(), + ), reasoning_effort: None, }, }; @@ -1507,6 +1515,24 @@ mod tests { "sandbox": { "type": "dangerFullAccess" }, + "permissionProfile": { + "network": { + "enabled": true, + }, + "fileSystem": { + "entries": [ + { + "path": { + "type": "special", + "value": { + "kind": "root", + }, + }, + "access": "write", + }, + ], + }, + }, "reasoningEffort": null } }), diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 3f602d90ef..1720b5005d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -1225,6 +1225,13 @@ pub struct AdditionalNetworkPermissions { pub enabled: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PermissionProfileNetworkPermissions { + pub enabled: Option, +} + impl From for AdditionalNetworkPermissions { fn from(value: CoreNetworkPermissions) -> Self { Self { @@ -1241,6 +1248,22 @@ impl From for CoreNetworkPermissions { } } +impl From for PermissionProfileNetworkPermissions { + fn from(value: CoreNetworkPermissions) -> Self { + Self { + enabled: value.enabled, + } + } +} + +impl From for CoreNetworkPermissions { + fn from(value: PermissionProfileNetworkPermissions) -> Self { + Self { + enabled: value.enabled, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] @@ -1383,6 +1406,70 @@ impl From for CoreFileSystemSandboxEntry { } } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PermissionProfileFileSystemPermissions { + pub entries: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub glob_scan_max_depth: Option, +} + +impl From for PermissionProfileFileSystemPermissions { + fn from(value: CoreFileSystemPermissions) -> Self { + Self { + entries: value + .entries + .into_iter() + .map(FileSystemSandboxEntry::from) + .collect(), + glob_scan_max_depth: value.glob_scan_max_depth, + } + } +} + +impl From for CoreFileSystemPermissions { + fn from(value: PermissionProfileFileSystemPermissions) -> Self { + Self { + entries: value + .entries + .into_iter() + .map(CoreFileSystemSandboxEntry::from) + .collect(), + glob_scan_max_depth: value.glob_scan_max_depth, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PermissionProfile { + pub network: Option, + pub file_system: Option, +} + +impl From for PermissionProfile { + fn from(value: CorePermissionProfile) -> Self { + Self { + network: value.network.map(PermissionProfileNetworkPermissions::from), + file_system: value + .file_system + .map(PermissionProfileFileSystemPermissions::from), + } + } +} + +impl From for CorePermissionProfile { + fn from(value: PermissionProfile) -> Self { + Self { + network: value.network.map(CoreNetworkPermissions::from), + file_system: value.file_system.map(CoreFileSystemPermissions::from), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -3127,7 +3214,15 @@ 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. pub sandbox: SandboxPolicy, + /// Canonical active permissions view for this thread when representable. + /// This is `null` for external sandbox policies because external + /// enforcement cannot be round-tripped as a `PermissionProfile`. + #[serde(default)] + pub permission_profile: Option, pub reasoning_effort: Option, } @@ -3216,7 +3311,15 @@ 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. pub sandbox: SandboxPolicy, + /// Canonical active permissions view for this thread when representable. + /// This is `null` for external sandbox policies because external + /// enforcement cannot be round-tripped as a `PermissionProfile`. + #[serde(default)] + pub permission_profile: Option, pub reasoning_effort: Option, } @@ -3296,7 +3399,15 @@ 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. pub sandbox: SandboxPolicy, + /// Canonical active permissions view for this thread when representable. + /// This is `null` for external sandbox policies because external + /// enforcement cannot be round-tripped as a `PermissionProfile`. + #[serde(default)] + pub permission_profile: Option, pub reasoning_effort: Option, } @@ -7419,6 +7530,47 @@ mod tests { .expect_err("zero glob scan depth should fail deserialization"); } + #[test] + fn permission_profile_file_system_permissions_preserves_glob_scan_depth() { + let core_permissions = CoreFileSystemPermissions { + entries: vec![CoreFileSystemSandboxEntry { + path: CoreFileSystemPath::GlobPattern { + pattern: "**/*.env".to_string(), + }, + access: CoreFileSystemAccessMode::None, + }], + glob_scan_max_depth: NonZeroUsize::new(2), + }; + + let permissions = PermissionProfileFileSystemPermissions::from(core_permissions.clone()); + + assert_eq!( + permissions, + PermissionProfileFileSystemPermissions { + entries: vec![FileSystemSandboxEntry { + path: FileSystemPath::GlobPattern { + pattern: "**/*.env".to_string(), + }, + access: FileSystemAccessMode::None, + }], + glob_scan_max_depth: NonZeroUsize::new(2), + } + ); + assert_eq!( + CoreFileSystemPermissions::from(permissions), + core_permissions + ); + } + + #[test] + fn permission_profile_file_system_permissions_rejects_zero_glob_scan_depth() { + serde_json::from_value::(json!({ + "entries": [], + "globScanMaxDepth": 0, + })) + .expect_err("zero glob scan depth should fail deserialization"); + } + #[test] fn permissions_request_approval_response_uses_granted_permission_profile_without_macos() { let read_only_path = if cfg!(windows) { @@ -9708,7 +9860,7 @@ mod tests { } #[test] - fn thread_lifecycle_responses_default_missing_instruction_sources() { + fn thread_lifecycle_responses_default_missing_compat_fields() { let response = json!({ "thread": { "id": "thread-id", @@ -9749,6 +9901,9 @@ mod tests { assert_eq!(start.instruction_sources, Vec::::new()); assert_eq!(resume.instruction_sources, Vec::::new()); assert_eq!(fork.instruction_sources, Vec::::new()); + assert_eq!(start.permission_profile, None); + assert_eq!(resume.permission_profile, None); + assert_eq!(fork.permission_profile, None); } #[test] diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 6c4b559278..d6efa1050b 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -2691,6 +2691,11 @@ impl CodexMessageProcessor { /*has_in_progress_turn*/ false, ); + let permission_profile = thread_response_permission_profile( + &config_snapshot.sandbox_policy, + config_snapshot.permission_profile, + ); + let response = ThreadStartResponse { thread: thread.clone(), model: config_snapshot.model, @@ -2701,6 +2706,7 @@ impl CodexMessageProcessor { approval_policy: config_snapshot.approval_policy.into(), approvals_reviewer: config_snapshot.approvals_reviewer.into(), sandbox: config_snapshot.sandbox_policy.into(), + permission_profile, reasoning_effort: config_snapshot.reasoning_effort, }; if listener_task_context.general_analytics_enabled { @@ -4423,6 +4429,10 @@ impl CodexMessageProcessor { thread_status, /*has_live_in_progress_turn*/ false, ); + let permission_profile = thread_response_permission_profile( + &session_configured.sandbox_policy, + codex_thread.config_snapshot().await.permission_profile, + ); let response = ThreadResumeResponse { thread, @@ -4434,6 +4444,7 @@ impl CodexMessageProcessor { approval_policy: session_configured.approval_policy.into(), approvals_reviewer: session_configured.approvals_reviewer.into(), sandbox: session_configured.sandbox_policy.into(), + permission_profile, reasoning_effort: session_configured.reasoning_effort, }; if self.config.features.enabled(Feature::GeneralAnalytics) { @@ -5068,6 +5079,10 @@ impl CodexMessageProcessor { .await, /*has_in_progress_turn*/ false, ); + let permission_profile = thread_response_permission_profile( + &session_configured.sandbox_policy, + forked_thread.config_snapshot().await.permission_profile, + ); let response = ThreadForkResponse { thread: thread.clone(), @@ -5079,6 +5094,7 @@ impl CodexMessageProcessor { approval_policy: session_configured.approval_policy.into(), approvals_reviewer: session_configured.approvals_reviewer.into(), sandbox: session_configured.sandbox_policy.into(), + permission_profile, reasoning_effort: session_configured.reasoning_effort, }; if self.config.features.enabled(Feature::GeneralAnalytics) { @@ -8456,11 +8472,15 @@ async fn handle_pending_thread_resume_request( approval_policy, approvals_reviewer, sandbox_policy, + permission_profile, cwd, reasoning_effort, .. } = pending.config_snapshot; let instruction_sources = pending.instruction_sources; + let permission_profile = + thread_response_permission_profile(&sandbox_policy, permission_profile); + let response = ThreadResumeResponse { thread, model, @@ -8471,6 +8491,7 @@ async fn handle_pending_thread_resume_request( approval_policy: approval_policy.into(), approvals_reviewer: approvals_reviewer.into(), sandbox: sandbox_policy.into(), + permission_profile, reasoning_effort, }; let token_usage_thread = response.thread.clone(); @@ -9573,6 +9594,20 @@ fn with_thread_spawn_agent_metadata( } } +fn thread_response_permission_profile( + sandbox_policy: &codex_protocol::protocol::SandboxPolicy, + permission_profile: codex_protocol::models::PermissionProfile, +) -> Option { + match sandbox_policy { + codex_protocol::protocol::SandboxPolicy::DangerFullAccess + | codex_protocol::protocol::SandboxPolicy::ReadOnly { .. } + | codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. } => { + Some(permission_profile.into()) + } + codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. } => None, + } +} + fn parse_datetime(timestamp: Option<&str>) -> Option> { timestamp.and_then(|ts| { chrono::DateTime::parse_from_rfc3339(ts) @@ -10061,6 +10096,29 @@ mod tests { ); } + #[test] + fn thread_response_permission_profile_omits_external_sandbox() { + let cwd = test_path_buf("/tmp").abs(); + let profile = codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy( + &SandboxPolicy::DangerFullAccess, + cwd.as_path(), + ); + + assert_eq!( + thread_response_permission_profile( + &SandboxPolicy::ExternalSandbox { + network_access: codex_protocol::protocol::NetworkAccess::Restricted, + }, + profile.clone(), + ), + None + ); + assert_eq!( + thread_response_permission_profile(&SandboxPolicy::DangerFullAccess, profile.clone()), + Some(profile.into()) + ); + } + #[test] fn config_load_error_marks_cloud_requirements_failures_for_relogin() { let err = std::io::Error::other(CloudRequirementsLoadError::new( diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index 46f8ca61ff..708cc9d0db 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -422,6 +422,13 @@ 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(), + &test_path_buf("/tmp"), + ) + .into(), + ), reasoning_effort: None, }; diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 1fcb329581..c7335a3540 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -1504,6 +1504,13 @@ mod tests { approval_policy: codex_protocol::protocol::AskForApproval::Never.into(), approvals_reviewer: codex_app_server_protocol::ApprovalsReviewer::User, sandbox: codex_protocol::protocol::SandboxPolicy::new_read_only_policy().into(), + permission_profile: Some( + codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy( + &codex_protocol::protocol::SandboxPolicy::new_read_only_policy(), + &test_path_buf("/tmp/project"), + ) + .into(), + ), reasoning_effort: None, };