Merge 7b40cacf12 into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-04-20 22:15:02 -07:00
committed by GitHub
134 changed files with 4717 additions and 494 deletions
@@ -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: 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: sample_permission_profile(),
reasoning_effort: None,
},
}
+325 -1
View File
@@ -12,6 +12,17 @@
],
"type": "string"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
@@ -217,6 +228,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [
@@ -233,7 +255,7 @@
"type": "null"
}
],
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted."
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`."
},
"size": {
"anyOf": [
@@ -697,6 +719,217 @@
],
"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": [
"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"
}
]
},
"FsCopyParams": {
"description": "Copy a file or directory tree on the host filesystem.",
"properties": {
@@ -1457,6 +1690,53 @@
],
"type": "string"
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -2815,6 +3095,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{
@@ -3186,6 +3477,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -3369,6 +3671,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -3565,6 +3878,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{
@@ -6291,6 +6291,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [
@@ -6307,7 +6318,7 @@
"type": "null"
}
],
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted."
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`."
},
"size": {
"anyOf": [
@@ -10507,6 +10518,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/v2/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -13584,6 +13642,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{
@@ -13652,6 +13721,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/v2/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -13685,6 +13757,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -14958,6 +15031,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -15036,6 +15120,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/v2/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -15069,6 +15156,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -15247,6 +15335,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -15335,6 +15434,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/v2/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -15368,6 +15470,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -16020,6 +16123,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/v2/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{
@@ -2908,6 +2908,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [
@@ -2924,7 +2935,7 @@
"type": "null"
}
],
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted."
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`."
},
"size": {
"anyOf": [
@@ -7279,6 +7290,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -11488,6 +11546,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{
@@ -11556,6 +11625,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -11589,6 +11661,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -12862,6 +12935,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -12940,6 +13024,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -12973,6 +13060,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -13151,6 +13239,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -13239,6 +13338,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -13272,6 +13374,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -13924,6 +14027,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{
@@ -5,6 +5,17 @@
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"CommandExecTerminalSize": {
"description": "PTY size in character cells for `command/exec` PTY sessions.",
"properties": {
@@ -27,6 +38,217 @@
],
"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": [
"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"
}
]
},
"NetworkAccess": {
"enum": [
"restricted",
@@ -34,6 +256,53 @@
],
"type": "string"
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"ReadOnlyAccess": {
"oneOf": [
{
@@ -247,6 +516,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Optional full permissions profile for this command.\n\nDefaults to the user's configured permissions when omitted. Cannot be combined with `sandboxPolicy`."
},
"processId": {
"description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.",
"type": [
@@ -263,7 +543,7 @@
"type": "null"
}
],
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted."
"description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`."
},
"size": {
"anyOf": [
@@ -1,6 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
@@ -59,6 +74,264 @@
}
]
},
"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"
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"SandboxMode": {
"enum": [
"read-only",
@@ -139,6 +412,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the forked thread. Cannot be combined with `sandbox`."
},
"sandbox": {
"anyOf": [
{
@@ -5,6 +5,17 @@
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"AgentPath": {
"type": "string"
},
@@ -448,6 +459,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 +908,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"ReadOnlyAccess": {
"oneOf": [
{
@@ -2219,6 +2488,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -2252,6 +2524,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -1,6 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
@@ -133,6 +148,217 @@
}
]
},
"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"
}
]
},
"FunctionCallOutputBody": {
"anyOf": [
{
@@ -325,6 +551,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -1052,6 +1325,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for the resumed thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -5,6 +5,17 @@
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"AgentPath": {
"type": "string"
},
@@ -448,6 +459,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 +908,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"ReadOnlyAccess": {
"oneOf": [
{
@@ -2219,6 +2488,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -2252,6 +2524,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -1,6 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
@@ -79,6 +94,264 @@
],
"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": [
"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"
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -175,6 +448,17 @@
"null"
]
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Full permissions override for this thread. Cannot be combined with `sandbox`."
},
"personality": {
"anyOf": [
{
@@ -5,6 +5,17 @@
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"AgentPath": {
"type": "string"
},
@@ -448,6 +459,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 +908,53 @@
}
]
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"ReadOnlyAccess": {
"oneOf": [
{
@@ -2219,6 +2488,9 @@
"modelProvider": {
"type": "string"
},
"permissionProfile": {
"$ref": "#/definitions/PermissionProfile"
},
"reasoningEffort": {
"anyOf": [
{
@@ -2252,6 +2524,7 @@
"cwd",
"model",
"modelProvider",
"permissionProfile",
"sandbox",
"thread"
],
@@ -5,6 +5,17 @@
"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"
},
"AdditionalNetworkPermissions": {
"properties": {
"enabled": {
"type": [
"boolean",
"null"
]
}
},
"type": "object"
},
"ApprovalsReviewer": {
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
"enum": [
@@ -98,6 +109,217 @@
],
"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": [
"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"
}
]
},
"ModeKind": {
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
@@ -113,6 +335,53 @@
],
"type": "string"
},
"PermissionProfile": {
"properties": {
"fileSystem": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
},
{
"type": "null"
}
]
},
"network": {
"anyOf": [
{
"$ref": "#/definitions/AdditionalNetworkPermissions"
},
{
"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"
},
"Personality": {
"enum": [
"none",
@@ -555,6 +824,17 @@
"outputSchema": {
"description": "Optional JSON Schema used to constrain the final assistant message for this turn."
},
"permissionProfile": {
"anyOf": [
{
"$ref": "#/definitions/PermissionProfile"
},
{
"type": "null"
}
],
"description": "Override the full permissions profile for this turn and subsequent turns. Cannot be combined with `sandboxPolicy`."
},
"personality": {
"anyOf": [
{
@@ -2,6 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CommandExecTerminalSize } from "./CommandExecTerminalSize";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
/**
@@ -92,6 +93,14 @@ size?: CommandExecTerminalSize | null,
* Optional sandbox policy for this command.
*
* Uses the same shape as thread/turn execution sandbox configuration and
* defaults to the user's configured policy when omitted.
* defaults to the user's configured policy when omitted. Cannot be
* combined with `permissionProfile`.
*/
sandboxPolicy?: SandboxPolicy | null, };
sandboxPolicy?: SandboxPolicy | null,
/**
* Optional full permissions profile for this command.
*
* Defaults to the user's configured permissions when omitted. Cannot be
* combined with `sandboxPolicy`.
*/
permissionProfile?: PermissionProfile | null, };
@@ -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 { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions";
import type { PermissionProfileFileSystemPermissions } from "./PermissionProfileFileSystemPermissions";
export type PermissionProfile = { network: AdditionalNetworkPermissions | null, fileSystem: PermissionProfileFileSystemPermissions | null, };
@@ -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<FileSystemSandboxEntry>, globScanMaxDepth?: number, };
@@ -5,6 +5,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -27,7 +28,11 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for the forked thread. Cannot be combined
* with `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, ephemeral?: boolean, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
@@ -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,4 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, permissionProfile: PermissionProfile, reasoningEffort: ReasoningEffort | null, };
@@ -7,6 +7,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
/**
@@ -36,7 +37,11 @@ model?: string | null, modelProvider?: string | null, serviceTier?: ServiceTier
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for the resumed thread. Cannot be combined
* with `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
* If true, persist additional rollout EventMsg variants required to
* reconstruct a richer thread history on subsequent resume/fork/read.
*/
@@ -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,4 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, permissionProfile: PermissionProfile, reasoningEffort: ReasoningEffort | null, };
@@ -6,6 +6,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxMode } from "./SandboxMode";
import type { ThreadStartSource } from "./ThreadStartSource";
@@ -13,7 +14,11 @@ export type ThreadStartParams = {model?: string | null, modelProvider?: string |
* Override where approval requests are routed for review on this thread
* and subsequent turns.
*/
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, /**
* Full permissions override for this thread. Cannot be combined with
* `sandbox`.
*/
permissionProfile?: PermissionProfile | null, config?: { [key in string]?: JsonValue } | null, serviceName?: string | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, ephemeral?: boolean | null, sessionStartSource?: ThreadStartSource | null, /**
* If true, opt into emitting raw Responses API items on the event stream.
* This is for internal use only (e.g. Codex Cloud).
*/
@@ -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,4 @@ instructionSources: Array<AbsolutePathBuf>, approvalPolicy: AskForApproval,
/**
* Reviewer currently used for approval requests on this thread.
*/
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, reasoningEffort: ReasoningEffort | null, };
approvalsReviewer: ApprovalsReviewer, sandbox: SandboxPolicy, permissionProfile: PermissionProfile, reasoningEffort: ReasoningEffort | null, };
@@ -9,6 +9,7 @@ import type { ServiceTier } from "../ServiceTier";
import type { JsonValue } from "../serde_json/JsonValue";
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
import type { AskForApproval } from "./AskForApproval";
import type { PermissionProfile } from "./PermissionProfile";
import type { SandboxPolicy } from "./SandboxPolicy";
import type { UserInput } from "./UserInput";
@@ -26,6 +27,10 @@ approvalsReviewer?: ApprovalsReviewer | null, /**
* Override the sandbox policy for this turn and subsequent turns.
*/
sandboxPolicy?: SandboxPolicy | null, /**
* Override the full permissions profile for this turn and subsequent
* turns. Cannot be combined with `sandboxPolicy`.
*/
permissionProfile?: PermissionProfile | null, /**
* Override the model for this turn and subsequent turns.
*/
model?: string | null, /**
@@ -230,6 +230,8 @@ 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 { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams";
export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse";
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
@@ -1448,6 +1448,12 @@ mod tests {
approval_policy: v2::AskForApproval::OnFailure,
approvals_reviewer: v2::ApprovalsReviewer::User,
sandbox: v2::SandboxPolicy::DangerFullAccess,
permission_profile:
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
std::path::Path::new("/tmp"),
)
.into(),
reasoning_effort: None,
},
};
@@ -1490,6 +1496,24 @@ mod tests {
"sandbox": {
"type": "dangerFullAccess"
},
"permissionProfile": {
"network": {
"enabled": true,
},
"fileSystem": {
"entries": [
{
"path": {
"type": "special",
"value": {
"kind": "root",
},
},
"access": "write",
},
],
},
},
"reasoningEffort": null
}
}),
@@ -18,6 +18,7 @@ impl From<v1::ExecOneOffCommandParams> for v2::CommandExecParams {
env: None,
size: None,
sandbox_policy: value.sandbox_policy.map(std::convert::Into::into),
permission_profile: None,
}
}
}
+156 -1
View File
@@ -1378,6 +1378,70 @@ impl From<FileSystemSandboxEntry> 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<FileSystemSandboxEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub glob_scan_max_depth: Option<NonZeroUsize>,
}
impl From<CoreFileSystemPermissions> 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<PermissionProfileFileSystemPermissions> 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, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionProfile {
pub network: Option<AdditionalNetworkPermissions>,
pub file_system: Option<PermissionProfileFileSystemPermissions>,
}
impl From<CorePermissionProfile> for PermissionProfile {
fn from(value: CorePermissionProfile) -> Self {
Self {
network: value.network.map(AdditionalNetworkPermissions::from),
file_system: value
.file_system
.map(PermissionProfileFileSystemPermissions::from),
}
}
}
impl From<PermissionProfile> 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/")]
@@ -2776,9 +2840,16 @@ pub struct CommandExecParams {
/// Optional sandbox policy for this command.
///
/// Uses the same shape as thread/turn execution sandbox configuration and
/// defaults to the user's configured policy when omitted.
/// defaults to the user's configured policy when omitted. Cannot be
/// combined with `permissionProfile`.
#[ts(optional = nullable)]
pub sandbox_policy: Option<SandboxPolicy>,
/// Optional full permissions profile for this command.
///
/// Defaults to the user's configured permissions when omitted. Cannot be
/// combined with `sandboxPolicy`.
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
}
/// Final buffered result for `command/exec`.
@@ -2897,6 +2968,10 @@ 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`.
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
#[ts(optional = nullable)]
pub config: Option<HashMap<String, JsonValue>>,
#[ts(optional = nullable)]
@@ -2965,6 +3040,7 @@ pub struct ThreadStartResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -3022,6 +3098,10 @@ 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`.
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
#[ts(optional = nullable)]
pub config: Option<HashMap<String, serde_json::Value>>,
#[ts(optional = nullable)]
@@ -3054,6 +3134,7 @@ pub struct ThreadResumeResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -3102,6 +3183,10 @@ 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`.
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
#[ts(optional = nullable)]
pub config: Option<HashMap<String, serde_json::Value>>,
#[ts(optional = nullable)]
@@ -3134,6 +3219,7 @@ pub struct ThreadForkResponse {
/// Reviewer currently used for approval requests on this thread.
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub reasoning_effort: Option<ReasoningEffort>,
}
@@ -4504,6 +4590,10 @@ 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`.
#[ts(optional = nullable)]
pub permission_profile: Option<PermissionProfile>,
/// Override the model for this turn and subsequent turns.
#[ts(optional = nullable)]
pub model: Option<String>,
@@ -7238,6 +7328,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::<PermissionProfileFileSystemPermissions>(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) {
@@ -7530,6 +7661,7 @@ mod tests {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
}
);
}
@@ -7550,6 +7682,7 @@ mod tests {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
};
let value = serde_json::to_value(&params).expect("serialize command/exec params");
@@ -7564,6 +7697,7 @@ mod tests {
"env": null,
"size": null,
"sandboxPolicy": null,
"permissionProfile": null,
"outputBytesCap": null,
})
);
@@ -7589,6 +7723,7 @@ mod tests {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
};
let value = serde_json::to_value(&params).expect("serialize command/exec params");
@@ -7605,6 +7740,7 @@ mod tests {
"env": null,
"size": null,
"sandboxPolicy": null,
"permissionProfile": null,
})
);
@@ -7633,6 +7769,7 @@ mod tests {
])),
size: None,
sandbox_policy: None,
permission_profile: None,
};
let value = serde_json::to_value(&params).expect("serialize command/exec params");
@@ -7651,6 +7788,7 @@ mod tests {
},
"size": null,
"sandboxPolicy": null,
"permissionProfile": null,
})
);
@@ -7720,6 +7858,7 @@ mod tests {
cols: 120,
}),
sandbox_policy: None,
permission_profile: None,
};
let value = serde_json::to_value(&params).expect("serialize command/exec params");
@@ -7738,6 +7877,7 @@ mod tests {
"cols": 120,
},
"sandboxPolicy": null,
"permissionProfile": null,
})
);
@@ -9379,6 +9519,20 @@ mod tests {
"approvalPolicy": "on-failure",
"approvalsReviewer": "user",
"sandbox": { "type": "dangerFullAccess" },
"permissionProfile": {
"network": { "enabled": true },
"fileSystem": {
"entries": [
{
"path": {
"type": "special",
"value": { "kind": "root" }
},
"access": "write"
}
]
}
},
"reasoningEffort": null
});
@@ -9418,6 +9572,7 @@ mod tests {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
model: None,
service_tier: None,
effort: None,
+18 -8
View File
@@ -82,7 +82,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, 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 `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.
- 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.
@@ -136,9 +136,9 @@ 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"`.
- `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it.
- `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.
- `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`.
- `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. Accepts the same permission override rules as `thread/start`.
- `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.
@@ -155,7 +155,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. 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 `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".
- `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"`.
@@ -220,6 +220,8 @@ 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".
"personality": "friendly",
"serviceName": "my_app_server_client", // optional metrics tag (`service_name`)
"sessionStartSource": "startup", // optional: "startup" (default) or "clear"
@@ -536,6 +538,8 @@ 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".
"model": "gpt-5.1-codex",
"effort": "medium",
"summary": "concise",
@@ -806,7 +810,13 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin
"cwd": "/Users/me/project", // optional; defaults to server cwd
"env": { "FOO": "override" }, // optional; merges into the server env and overrides matching names
"size": { "rows": 40, "cols": 120 }, // optional; PTY size in character cells, only valid with tty=true
"sandboxPolicy": { "type": "workspaceWrite" }, // optional; defaults to user config
"permissionProfile": { // optional; defaults to user config
"fileSystem": { "entries": [
{ "path": { "type": "special", "value": { "kind": "root" } }, "access": "read" },
{ "path": { "type": "special", "value": { "kind": "current_working_directory" } }, "access": "write" }
] },
"network": { "enabled": false }
},
"outputBytesCap": 1048576, // optional; per-stream capture cap
"disableOutputCap": false, // optional; cannot be combined with outputBytesCap
"timeoutMs": 10000, // optional; ms timeout; defaults to server timeout
@@ -819,12 +829,12 @@ Run a standalone command (argv vector) in the server’s sandbox without creatin
} }
```
- For clients that are already sandboxed externally, set `sandboxPolicy` to `{"type":"externalSandbox","networkAccess":"enabled"}` (or omit `networkAccess` to keep it restricted). Codex will not enforce its own sandbox in this mode; it tells the model it has full file-system access and passes the `networkAccess` state through `environment_context`.
- For clients that are already sandboxed externally, set the legacy `sandboxPolicy` to `{"type":"externalSandbox","networkAccess":"enabled"}` (or omit `networkAccess` to keep it restricted). Codex will not enforce its own sandbox in this mode; it tells the model it has full file-system access and passes the `networkAccess` state through `environment_context`.
Notes:
- Empty `command` arrays are rejected.
- `sandboxPolicy` accepts the same shape used by `turn/start` (e.g., `dangerFullAccess`, `readOnly`, `workspaceWrite` with flags, `externalSandbox` with `networkAccess` `restricted|enabled`).
- Prefer `permissionProfile` for command permission overrides. The legacy `sandboxPolicy` field accepts the same shape used by `turn/start` (e.g., `dangerFullAccess`, `readOnly`, `workspaceWrite` with flags, `externalSandbox` with `networkAccess` `restricted|enabled`), but cannot be combined with `permissionProfile`.
- `env` merges into the environment produced by the server's shell environment policy. Matching names are overridden; unspecified variables are left intact.
- When omitted, `timeoutMs` falls back to the server default.
- When omitted, `outputBytesCap` falls back to the server default of 1 MiB per stream.
+170 -18
View File
@@ -908,6 +908,10 @@ pub(crate) async fn apply_bespoke_event_handling(
.note_permission_requested(&conversation_id.to_string())
.await;
let requested_permissions = request.permissions.clone();
let request_cwd = match request.cwd.clone() {
Some(cwd) => cwd,
None => conversation.config_snapshot().await.cwd,
};
let params = PermissionsRequestApprovalParams {
thread_id: conversation_id.to_string(),
turn_id: request.turn_id.clone(),
@@ -918,17 +922,17 @@ pub(crate) async fn apply_bespoke_event_handling(
let (pending_request_id, rx) = outgoing
.send_request(ServerRequestPayload::PermissionsRequestApproval(params))
.await;
let pending_response = PendingRequestPermissionsResponse {
call_id: request.call_id,
requested_permissions,
request_cwd,
pending_request_id,
receiver: rx,
request_permissions_guard: permission_guard,
};
tokio::spawn(async move {
on_request_permissions_response(
request.call_id,
requested_permissions,
pending_request_id,
rx,
conversation,
thread_state,
permission_guard,
)
.await;
on_request_permissions_response(pending_response, conversation, thread_state)
.await;
});
} else {
error!(
@@ -2586,20 +2590,26 @@ fn mcp_server_elicitation_response_from_client_result(
}
async fn on_request_permissions_response(
call_id: String,
requested_permissions: CoreRequestPermissionProfile,
pending_request_id: RequestId,
receiver: oneshot::Receiver<ClientRequestResult>,
pending_response: PendingRequestPermissionsResponse,
conversation: Arc<CodexThread>,
thread_state: Arc<Mutex<ThreadState>>,
request_permissions_guard: ThreadWatchActiveGuard,
) {
let PendingRequestPermissionsResponse {
call_id,
requested_permissions,
request_cwd,
pending_request_id,
receiver,
request_permissions_guard,
} = pending_response;
let response = receiver.await;
resolve_server_request_on_thread_listener(&thread_state, pending_request_id).await;
drop(request_permissions_guard);
let Some(response) =
request_permissions_response_from_client_result(requested_permissions, response)
else {
let Some(response) = request_permissions_response_from_client_result(
requested_permissions,
response,
request_cwd.as_path(),
) else {
return;
};
@@ -2614,9 +2624,19 @@ async fn on_request_permissions_response(
}
}
struct PendingRequestPermissionsResponse {
call_id: String,
requested_permissions: CoreRequestPermissionProfile,
request_cwd: AbsolutePathBuf,
pending_request_id: RequestId,
receiver: oneshot::Receiver<ClientRequestResult>,
request_permissions_guard: ThreadWatchActiveGuard,
}
fn request_permissions_response_from_client_result(
requested_permissions: CoreRequestPermissionProfile,
response: std::result::Result<ClientRequestResult, oneshot::error::RecvError>,
cwd: &std::path::Path,
) -> Option<CoreRequestPermissionsResponse> {
let value = match response {
Ok(Ok(value)) => value,
@@ -2649,6 +2669,7 @@ fn request_permissions_response_from_client_result(
permissions: intersect_permission_profiles(
requested_permissions.into(),
response.permissions.into(),
cwd,
)
.into(),
scope: response.scope.to_core(),
@@ -3023,6 +3044,10 @@ mod tests {
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::plan_tool::PlanItemArg;
use codex_protocol::plan_tool::StepStatus;
use codex_protocol::protocol::CollabResumeBeginEvent;
@@ -3707,6 +3732,7 @@ mod tests {
let response = request_permissions_response_from_client_result(
CoreRequestPermissionProfile::default(),
Ok(Err(error)),
std::env::current_dir().expect("current dir").as_path(),
);
assert_eq!(response, None);
@@ -3793,12 +3819,14 @@ mod tests {
),
];
let cwd = std::env::current_dir().expect("current dir");
for (granted_permissions, expected_permissions) in cases {
let response = request_permissions_response_from_client_result(
requested_permissions.clone(),
Ok(Ok(serde_json::json!({
"permissions": granted_permissions,
}))),
cwd.as_path(),
)
.expect("response should be accepted");
@@ -3820,6 +3848,7 @@ mod tests {
"scope": "session",
"permissions": {},
}))),
std::env::current_dir().expect("current dir").as_path(),
)
.expect("response should be accepted");
@@ -3832,6 +3861,129 @@ mod tests {
);
}
#[test]
fn request_permissions_response_accepts_explicit_child_grant_for_requested_cwd_scope() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path()).expect("absolute cwd");
let child = cwd.join("child");
let requested_permissions = CoreRequestPermissionProfile {
file_system: Some(CoreFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: None,
}),
..Default::default()
};
let response = request_permissions_response_from_client_result(
requested_permissions,
Ok(Ok(serde_json::json!({
"permissions": {
"fileSystem": {
"write": [child],
},
},
}))),
cwd.as_path(),
)
.expect("response should be accepted");
assert_eq!(
response.permissions,
CoreRequestPermissionProfile {
file_system: Some(CoreFileSystemPermissions::from_read_write_roots(
/*read*/ None,
Some(vec![child]),
)),
..Default::default()
}
);
}
#[test]
fn request_permissions_response_rejects_child_grant_outside_requested_cwd_scope() {
let temp_dir = TempDir::new().expect("temp dir");
let request_cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path().join("request-cwd"))
.expect("absolute request cwd");
let later_cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path().join("later-cwd"))
.expect("absolute later cwd");
let later_child = later_cwd.join("child");
let requested_permissions = CoreRequestPermissionProfile {
file_system: Some(CoreFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: None,
}),
..Default::default()
};
let response = request_permissions_response_from_client_result(
requested_permissions,
Ok(Ok(serde_json::json!({
"permissions": {
"fileSystem": {
"write": [later_child],
},
},
}))),
request_cwd.as_path(),
)
.expect("response should be accepted");
assert_eq!(
response.permissions,
CoreRequestPermissionProfile::default()
);
}
#[test]
fn request_permissions_response_ignores_broader_cwd_grant_for_requested_child_path() {
let temp_dir = TempDir::new().expect("temp dir");
let cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path()).expect("absolute cwd");
let child = cwd.join("child");
let requested_permissions = CoreRequestPermissionProfile {
file_system: Some(CoreFileSystemPermissions::from_read_write_roots(
/*read*/ None,
Some(vec![child]),
)),
..Default::default()
};
let response = request_permissions_response_from_client_result(
requested_permissions,
Ok(Ok(serde_json::json!({
"permissions": {
"fileSystem": {
"entries": [{
"path": {
"type": "special",
"value": {
"kind": "current_working_directory"
}
},
"access": "write"
}],
},
},
}))),
cwd.as_path(),
)
.expect("response should be accepted");
assert_eq!(
response.permissions,
CoreRequestPermissionProfile::default()
);
}
#[test]
fn collab_resume_begin_maps_to_item_started_resume_agent() {
let event = CollabResumeBeginEvent {
@@ -102,6 +102,7 @@ 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::PluginDetail;
use codex_app_server_protocol::PluginInstallParams;
use codex_app_server_protocol::PluginInstallResponse;
@@ -2121,7 +2122,16 @@ impl CodexMessageProcessor {
env: env_overrides,
size,
sandbox_policy,
permission_profile,
} = params;
if sandbox_policy.is_some() && permission_profile.is_some() {
self.send_invalid_request_error(
request_id,
"`permissionProfile` cannot be combined with `sandboxPolicy`".to_string(),
)
.await;
return;
}
if size.is_some() && !tty {
let error = JSONRPCErrorError {
@@ -2253,13 +2263,52 @@ impl CodexMessageProcessor {
arg0: None,
};
let requested_policy = sandbox_policy.map(|policy| policy.to_core());
let (
effective_policy,
effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
) = match requested_policy {
Some(policy) => match self.config.permissions.sandbox_policy.can_set(&policy) {
) = if let Some(permission_profile) = permission_profile {
let permission_profile =
codex_protocol::models::PermissionProfile::from(permission_profile);
let sandbox_policy = match permission_profile.to_legacy_sandbox_policy(&sandbox_cwd) {
Ok(sandbox_policy) => sandbox_policy,
Err(err) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("invalid permission profile: {err}"),
data: None,
};
self.outgoing.send_error(request, error).await;
return;
}
};
match self
.config
.permissions
.sandbox_policy
.can_set(&sandbox_policy)
{
Ok(()) => {
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
(
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
}
Err(err) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: format!("invalid permission profile: {err}"),
data: None,
};
self.outgoing.send_error(request, error).await;
return;
}
}
} else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) {
match self.config.permissions.sandbox_policy.can_set(&policy) {
Ok(()) => {
let file_system_sandbox_policy =
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy(&policy, &sandbox_cwd);
@@ -2276,12 +2325,13 @@ impl CodexMessageProcessor {
self.outgoing.send_error(request, error).await;
return;
}
},
None => (
}
} else {
(
self.config.permissions.sandbox_policy.get().clone(),
self.config.permissions.file_system_sandbox_policy.clone(),
self.config.permissions.network_sandbox_policy,
),
)
};
let codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone();
@@ -2399,6 +2449,7 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
config,
service_name,
base_instructions,
@@ -2411,6 +2462,14 @@ impl CodexMessageProcessor {
session_start_source,
persist_extended_history,
} = params;
if sandbox.is_some() && permission_profile.is_some() {
self.send_invalid_request_error(
request_id,
"`permissionProfile` cannot be combined with `sandbox`".to_string(),
)
.await;
return;
}
let mut typesafe_overrides = self.build_thread_config_overrides(
model,
model_provider,
@@ -2419,6 +2478,7 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
base_instructions,
developer_instructions,
personality,
@@ -2778,6 +2838,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: config_snapshot.permission_profile.into(),
reasoning_effort: config_snapshot.reasoning_effort,
};
if listener_task_context.general_analytics_enabled {
@@ -2835,6 +2896,7 @@ 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>,
base_instructions: Option<String>,
developer_instructions: Option<String>,
personality: Option<Personality>,
@@ -2849,6 +2911,7 @@ 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,
@@ -4364,12 +4427,21 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
config: mut request_overrides,
base_instructions,
developer_instructions,
personality,
persist_extended_history,
} = params;
if sandbox.is_some() && permission_profile.is_some() {
self.send_invalid_request_error(
request_id,
"`permissionProfile` cannot be combined with `sandbox`".to_string(),
)
.await;
return;
}
let thread_history = if let Some(history) = history {
let Some(thread_history) = self
@@ -4398,6 +4470,7 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
base_instructions,
developer_instructions,
personality,
@@ -4509,6 +4582,7 @@ impl CodexMessageProcessor {
thread_status,
/*has_live_in_progress_turn*/ false,
);
let permission_profile = codex_thread.config_snapshot().await.permission_profile;
let response = ThreadResumeResponse {
thread,
@@ -4520,6 +4594,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: permission_profile.into(),
reasoning_effort: session_configured.reasoning_effort,
};
if self.config.features.enabled(Feature::GeneralAnalytics) {
@@ -4903,12 +4978,21 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
config: cli_overrides,
base_instructions,
developer_instructions,
ephemeral,
persist_extended_history,
} = params;
if sandbox.is_some() && permission_profile.is_some() {
self.send_invalid_request_error(
request_id,
"`permissionProfile` cannot be combined with `sandbox`".to_string(),
)
.await;
return;
}
let (rollout_path, source_thread_id) = if let Some(path) = path {
(path, None)
@@ -4985,6 +5069,7 @@ impl CodexMessageProcessor {
approval_policy,
approvals_reviewer,
sandbox,
permission_profile,
base_instructions,
developer_instructions,
/*personality*/ None,
@@ -5163,6 +5248,7 @@ impl CodexMessageProcessor {
.await,
/*has_in_progress_turn*/ false,
);
let permission_profile = forked_thread.config_snapshot().await.permission_profile;
let response = ThreadForkResponse {
thread: thread.clone(),
@@ -5174,6 +5260,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: permission_profile.into(),
reasoning_effort: session_configured.reasoning_effort,
};
if self.config.features.enabled(Feature::GeneralAnalytics) {
@@ -7302,6 +7389,7 @@ impl CodexMessageProcessor {
|| params.approval_policy.is_some()
|| params.approvals_reviewer.is_some()
|| params.sandbox_policy.is_some()
|| params.permission_profile.is_some()
|| params.model.is_some()
|| params.service_tier.is_some()
|| params.effort.is_some()
@@ -7309,6 +7397,15 @@ impl CodexMessageProcessor {
|| collaboration_mode.is_some()
|| params.personality.is_some();
if params.sandbox_policy.is_some() && params.permission_profile.is_some() {
self.send_invalid_request_error(
request_id,
"`permissionProfile` cannot be combined with `sandboxPolicy`".to_string(),
)
.await;
return;
}
// If any overrides are provided, update the session turn context first.
if has_any_overrides {
let _ = self
@@ -7322,6 +7419,7 @@ impl CodexMessageProcessor {
.approvals_reviewer
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
sandbox_policy: params.sandbox_policy.map(|p| p.to_core()),
permission_profile: params.permission_profile.map(Into::into),
windows_sandbox_level: None,
model: params.model,
effort: params.effort.map(Some),
@@ -9041,6 +9139,7 @@ async fn handle_pending_thread_resume_request(
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
cwd,
reasoning_effort,
..
@@ -9056,6 +9155,7 @@ async fn handle_pending_thread_resume_request(
approval_policy: approval_policy.into(),
approvals_reviewer: approvals_reviewer.into(),
sandbox: sandbox_policy.into(),
permission_profile: permission_profile.into(),
reasoning_effort,
};
let token_usage_thread = response.thread.clone();
@@ -9240,6 +9340,16 @@ 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 let Some(requested_personality) = request.personality.as_ref()
&& config_snapshot.personality.as_ref() != Some(requested_personality)
{
@@ -10733,6 +10843,7 @@ mod tests {
approval_policy: None,
approvals_reviewer: None,
sandbox: None,
permission_profile: None,
config: None,
base_instructions: None,
developer_instructions: None,
@@ -10746,6 +10857,11 @@ mod tests {
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile:
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
std::path::Path::new("/tmp"),
),
cwd: test_path_buf("/tmp").abs(),
ephemeral: false,
reasoning_effort: None,
@@ -619,6 +619,7 @@ async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> {
cwd: None,
approval_policy: None,
sandbox_policy: None,
permission_profile: None,
approvals_reviewer: None,
model: None,
service_tier: None,
@@ -5,6 +5,7 @@ use app_test_support::create_mock_responses_server_sequence_unchecked;
use app_test_support::to_response;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use codex_app_server_protocol::AdditionalNetworkPermissions;
use codex_app_server_protocol::CommandExecOutputDeltaNotification;
use codex_app_server_protocol::CommandExecOutputStream;
use codex_app_server_protocol::CommandExecParams;
@@ -13,9 +14,16 @@ use codex_app_server_protocol::CommandExecResponse;
use codex_app_server_protocol::CommandExecTerminalSize;
use codex_app_server_protocol::CommandExecTerminateParams;
use codex_app_server_protocol::CommandExecWriteParams;
use codex_app_server_protocol::FileSystemAccessMode;
use codex_app_server_protocol::FileSystemPath;
use codex_app_server_protocol::FileSystemSandboxEntry;
use codex_app_server_protocol::FileSystemSpecialPath;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::PermissionProfile;
use codex_app_server_protocol::PermissionProfileFileSystemPermissions;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SandboxPolicy;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use tempfile::TempDir;
@@ -57,6 +65,7 @@ async fn command_exec_without_streams_can_be_terminated() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
let terminate_request_id = mcp
@@ -109,6 +118,7 @@ async fn command_exec_without_process_id_keeps_buffered_compatibility() -> Resul
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -167,6 +177,7 @@ async fn command_exec_env_overrides_merge_with_server_environment_and_support_un
])),
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -186,6 +197,91 @@ async fn command_exec_env_overrides_merge_with_server_environment_and_support_un
Ok(())
}
#[tokio::test]
async fn command_exec_accepts_permission_profile() -> Result<()> {
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let command_request_id = mcp
.send_command_exec_request(CommandExecParams {
command: vec![
"sh".to_string(),
"-lc".to_string(),
"printf 'profile'".to_string(),
],
process_id: None,
tty: false,
stream_stdin: false,
stream_stdout_stderr: false,
output_bytes_cap: None,
disable_output_cap: false,
disable_timeout: false,
timeout_ms: None,
cwd: None,
env: None,
size: None,
sandbox_policy: None,
permission_profile: Some(root_read_only_permission_profile()),
})
.await?;
let response = mcp
.read_stream_until_response_message(RequestId::Integer(command_request_id))
.await?;
let response: CommandExecResponse = to_response(response)?;
assert_eq!(
response,
CommandExecResponse {
exit_code: 0,
stdout: "profile".to_string(),
stderr: String::new(),
}
);
Ok(())
}
#[tokio::test]
async fn command_exec_rejects_sandbox_policy_with_permission_profile() -> Result<()> {
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let command_request_id = mcp
.send_command_exec_request(CommandExecParams {
command: vec!["sh".to_string(), "-lc".to_string(), "true".to_string()],
process_id: None,
tty: false,
stream_stdin: false,
stream_stdout_stderr: false,
output_bytes_cap: None,
disable_output_cap: false,
disable_timeout: false,
timeout_ms: None,
cwd: None,
env: None,
size: None,
sandbox_policy: Some(SandboxPolicy::DangerFullAccess),
permission_profile: Some(root_read_only_permission_profile()),
})
.await?;
let error = mcp
.read_stream_until_error_message(RequestId::Integer(command_request_id))
.await?;
assert_eq!(
error.error.message,
"`permissionProfile` cannot be combined with `sandboxPolicy`"
);
Ok(())
}
#[tokio::test]
async fn command_exec_rejects_disable_timeout_with_timeout_ms() -> Result<()> {
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
@@ -209,6 +305,7 @@ async fn command_exec_rejects_disable_timeout_with_timeout_ms() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -246,6 +343,7 @@ async fn command_exec_rejects_disable_output_cap_with_output_bytes_cap() -> Resu
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -283,6 +381,7 @@ async fn command_exec_rejects_negative_timeout_ms() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -320,6 +419,7 @@ async fn command_exec_without_process_id_rejects_streaming() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -361,6 +461,7 @@ async fn command_exec_non_streaming_respects_output_cap() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -408,6 +509,7 @@ async fn command_exec_streaming_does_not_buffer_output() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -471,6 +573,7 @@ async fn command_exec_pipe_streams_output_and_accepts_write() -> Result<()> {
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -546,6 +649,7 @@ async fn command_exec_tty_implies_streaming_and_reports_pty_output() -> Result<(
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -619,6 +723,7 @@ async fn command_exec_tty_supports_initial_size_and_resize() -> Result<()> {
cols: 101,
}),
sandbox_policy: None,
permission_profile: None,
})
.await?;
@@ -888,6 +993,23 @@ fn decode_delta_notification(
serde_json::from_value(params).context("deserialize command/exec/outputDelta notification")
}
fn root_read_only_permission_profile() -> PermissionProfile {
PermissionProfile {
network: Some(AdditionalNetworkPermissions {
enabled: Some(false),
}),
file_system: Some(PermissionProfileFileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
}],
glob_scan_max_depth: None,
}),
}
}
async fn read_initialize_response(
stream: &mut super::connection_handling_websocket::WsClient,
request_id: i64,
@@ -315,6 +315,7 @@ async fn skills_changed_notification_is_emitted_after_skill_change() -> Result<(
approval_policy: None,
approvals_reviewer: None,
sandbox: None,
permission_profile: None,
config: None,
service_name: None,
base_instructions: None,
@@ -1749,6 +1749,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,
model: Some("mock-model".to_string()),
effort: Some(ReasoningEffort::Medium),
summary: Some(ReasoningSummary::Auto),
@@ -1783,6 +1784,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,
model: Some("mock-model".to_string()),
effort: Some(ReasoningEffort::Medium),
summary: Some(ReasoningSummary::Auto),
@@ -42,6 +42,7 @@ use codex_protocol::approvals::ElicitationRequest;
use codex_protocol::approvals::ElicitationRequestEvent;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::mcp::RequestId as ProtocolRequestId;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
@@ -646,6 +647,8 @@ pub const MCP_SANDBOX_STATE_META_CAPABILITY: &str = "codex/sandbox-state-meta";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SandboxState {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub permission_profile: Option<PermissionProfile>,
pub sandbox_policy: SandboxPolicy,
pub codex_linux_sandbox_exe: Option<PathBuf>,
pub sandbox_cwd: PathBuf,
+8 -2
View File
@@ -741,8 +741,14 @@ async fn handle_request_permissions(
reason: event.reason,
permissions: event.permissions,
};
let response_fut =
parent_session.request_permissions(parent_ctx, call_id.clone(), args, cancel_token.clone());
let cwd = event.cwd.unwrap_or_else(|| parent_ctx.cwd.clone());
let response_fut = parent_session.request_permissions_for_cwd(
parent_ctx,
call_id.clone(),
args,
cwd,
cancel_token.clone(),
);
let response =
await_request_permissions_with_cancel(response_fut, parent_session, &call_id, cancel_token)
.await;
@@ -180,8 +180,10 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() {
},
scope: PermissionGrantScope::Turn,
};
let delegated_cwd = parent_ctx.cwd.join("delegated-cwd");
let cancel_token = CancellationToken::new();
let request_call_id = call_id.clone();
let request_cwd = delegated_cwd.clone();
let handle = tokio::spawn({
let codex = Arc::clone(&codex);
@@ -203,6 +205,7 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() {
}),
..RequestPermissionProfile::default()
},
cwd: Some(request_cwd),
},
&cancel_token,
)
@@ -218,6 +221,7 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() {
panic!("expected RequestPermissions event");
};
assert_eq!(request.call_id, call_id.clone());
assert_eq!(request.cwd, Some(delegated_cwd));
parent_session
.notify_request_permissions_response(&call_id, expected_response.clone())
+2
View File
@@ -11,6 +11,7 @@ use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::models::ContentItem;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
@@ -42,6 +43,7 @@ pub struct ThreadConfigSnapshot {
pub approval_policy: AskForApproval,
pub approvals_reviewer: ApprovalsReviewer,
pub sandbox_policy: SandboxPolicy,
pub permission_profile: PermissionProfile,
pub cwd: AbsolutePathBuf,
pub ephemeral: bool,
pub reasoning_effort: Option<ReasoningEffort>,
+42
View File
@@ -53,6 +53,9 @@ 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::FileSystemPermissions;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -61,6 +64,7 @@ use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::ReadOnlyAccess;
use codex_protocol::protocol::RealtimeVoice;
use codex_protocol::protocol::SandboxPolicy;
use serde::Deserialize;
use tempfile::tempdir;
@@ -782,6 +786,44 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
Ok(())
}
#[tokio::test]
async fn permission_profile_override_populates_runtime_permissions() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cwd = TempDir::new()?;
let permission_profile = PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
entries: vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Write,
}],
glob_scan_max_depth: None,
}),
};
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides {
cwd: Some(cwd.path().to_path_buf()),
permission_profile: Some(permission_profile.clone()),
..Default::default()
},
codex_home.abs(),
)
.await?;
assert_eq!(config.permissions.permission_profile(), permission_profile);
assert_eq!(
config.permissions.sandbox_policy.get(),
&SandboxPolicy::DangerFullAccess
);
Ok(())
}
#[tokio::test]
async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+49 -1
View File
@@ -78,6 +78,7 @@ 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::PermissionProfile;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemSandboxPolicy;
@@ -219,6 +220,17 @@ pub struct Permissions {
pub windows_sandbox_private_desktop: bool,
}
impl Permissions {
/// Effective runtime permissions after config requirements and runtime
/// readable-root additions have been applied.
pub fn permission_profile(&self) -> PermissionProfile {
PermissionProfile::from_runtime_permissions(
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
}
}
/// Application configuration loaded from disk and merged with overrides.
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
@@ -1366,6 +1378,7 @@ pub struct ConfigOverrides {
pub approval_policy: Option<AskForApproval>,
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub sandbox_mode: Option<SandboxMode>,
pub permission_profile: Option<PermissionProfile>,
pub model_provider: Option<String>,
pub service_tier: Option<Option<ServiceTier>>,
pub config_profile: Option<String>,
@@ -1583,6 +1596,7 @@ impl Config {
approval_policy: approval_policy_override,
approvals_reviewer: approvals_reviewer_override,
sandbox_mode,
permission_profile,
model_provider,
service_tier: service_tier_override,
config_profile: config_profile_key,
@@ -1603,6 +1617,13 @@ impl Config {
additional_writable_roots,
} = overrides;
if sandbox_mode.is_some() && permission_profile.is_some() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"`sandbox_mode` and `permission_profile` overrides cannot both be set",
));
}
let active_profile_name = config_profile_key
.as_ref()
.or(cfg.profile.as_ref())
@@ -1723,7 +1744,34 @@ impl Config {
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
) = if profiles_are_active {
) = if let Some(permission_profile) = permission_profile {
let configured_network_proxy_config = NetworkProxyConfig::default();
let (mut file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
let mut sandbox_policy = permission_profile
.to_legacy_sandbox_policy(resolved_cwd.as_path())
.map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("invalid permission_profile override: {err}"),
)
})?;
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
file_system_sandbox_policy = file_system_sandbox_policy
.with_additional_writable_roots(
resolved_cwd.as_path(),
&additional_writable_roots,
);
sandbox_policy = file_system_sandbox_policy
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
}
(
configured_network_proxy_config,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
} else if profiles_are_active {
let permissions = cfg.permissions.as_ref().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
@@ -133,6 +133,7 @@ fn reference_context_item() -> TurnContextItem {
timezone: Some("America/Los_Angeles".to_string()),
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: "gpt-test".to_string(),
+1 -1
View File
@@ -42,7 +42,7 @@ fn build_permissions_update_item(
}
let prev = previous?;
if prev.sandbox_policy == *next.sandbox_policy.get()
if prev.permission_profile() == next.permission_profile()
&& prev.approval_policy == next.approval_policy.value()
{
return None;
@@ -597,6 +597,7 @@ async fn run_review_on_session(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: params.model.clone(),
effort: params.reasoning_effort,
summary: Some(params.reasoning_summary),
+1
View File
@@ -517,6 +517,7 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
}
let sandbox_state = serde_json::to_value(SandboxState {
permission_profile: Some(turn_context.permission_profile()),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
sandbox_cwd: turn_context.cwd.to_path_buf(),
+4
View File
@@ -131,6 +131,7 @@ pub(super) async fn user_input_or_turn_inner(
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
model,
effort,
summary,
@@ -157,6 +158,7 @@ pub(super) async fn user_input_or_turn_inner(
approval_policy: Some(approval_policy),
approvals_reviewer,
sandbox_policy: Some(sandbox_policy),
permission_profile,
windows_sandbox_level: None,
collaboration_mode,
reasoning_summary: summary,
@@ -1063,6 +1065,7 @@ pub(super) async fn submission_loop(
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
windows_sandbox_level,
model,
effort,
@@ -1089,6 +1092,7 @@ pub(super) async fn submission_loop(
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
windows_sandbox_level,
collaboration_mode: Some(collaboration_mode),
reasoning_summary: summary,
+19 -1
View File
@@ -791,7 +791,6 @@ pub(crate) fn session_loop_termination_from_handle(
.boxed()
.shared()
}
async fn thread_title_from_state_db(
state_db: Option<&state_db::StateDbHandle>,
codex_home: &AbsolutePathBuf,
@@ -1911,6 +1910,24 @@ impl Session {
call_id: String,
args: RequestPermissionsArgs,
cancellation_token: CancellationToken,
) -> Option<RequestPermissionsResponse> {
self.request_permissions_for_cwd(
turn_context,
call_id,
args,
turn_context.cwd.clone(),
cancellation_token,
)
.await
}
pub(crate) async fn request_permissions_for_cwd(
self: &Arc<Self>,
turn_context: &Arc<TurnContext>,
call_id: String,
args: RequestPermissionsArgs,
cwd: AbsolutePathBuf,
cancellation_token: CancellationToken,
) -> Option<RequestPermissionsResponse> {
match turn_context.as_ref().approval_policy.value() {
AskForApproval::Never => {
@@ -2019,6 +2036,7 @@ impl Session {
turn_id: turn_context.sub_id.clone(),
reason: args.reason,
permissions: args.permissions,
cwd: Some(cwd),
});
self.send_event(turn_context.as_ref(), event).await;
tokio::select! {
@@ -68,6 +68,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -108,6 +109,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -903,6 +905,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -979,6 +982,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -1009,6 +1013,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -1122,6 +1127,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: current_model.to_string(),
@@ -1233,6 +1239,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -1383,6 +1390,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
+32 -8
View File
@@ -1,4 +1,5 @@
use super::*;
use crate::config::ConstraintError;
use tokio::sync::Semaphore;
/// Context for an initialized model agent
@@ -91,6 +92,13 @@ impl SessionConfiguration {
&self.codex_home
}
pub(super) fn permission_profile(&self) -> PermissionProfile {
PermissionProfile::from_runtime_permissions(
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
}
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
ThreadConfigSnapshot {
model: self.collaboration_mode.model().to_string(),
@@ -99,6 +107,7 @@ impl SessionConfiguration {
approval_policy: self.approval_policy.value(),
approvals_reviewer: self.approvals_reviewer,
sandbox_policy: self.sandbox_policy.get().clone(),
permission_profile: self.permission_profile(),
cwd: self.cwd.clone(),
ephemeral: self.original_config_do_not_use.ephemeral,
reasoning_effort: self.collaboration_mode.reasoning_effort(),
@@ -132,13 +141,6 @@ impl SessionConfiguration {
if let Some(approvals_reviewer) = updates.approvals_reviewer {
next_configuration.approvals_reviewer = approvals_reviewer;
}
let mut sandbox_policy_changed = false;
if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
next_configuration.sandbox_policy.set(sandbox_policy)?;
next_configuration.network_sandbox_policy =
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
sandbox_policy_changed = true;
}
if let Some(windows_sandbox_level) = updates.windows_sandbox_level {
next_configuration.windows_sandbox_level = windows_sandbox_level;
}
@@ -159,13 +161,33 @@ impl SessionConfiguration {
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
next_configuration.cwd = absolute_cwd;
if sandbox_policy_changed {
if let Some(permission_profile) = updates.permission_profile.clone() {
let sandbox_policy = permission_profile
.to_legacy_sandbox_policy(&next_configuration.cwd)
.map_err(|err| ConstraintError::InvalidValue {
field_name: "permission_profile",
candidate: format!("{permission_profile:?}"),
allowed: format!(
"permission profiles that can be represented by the active sandbox constraints: {err}"
),
requirement_source: codex_config::RequirementSource::Unknown,
})?;
next_configuration.sandbox_policy.set(sandbox_policy)?;
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
next_configuration.file_system_sandbox_policy = file_system_sandbox_policy;
next_configuration.network_sandbox_policy = network_sandbox_policy;
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
next_configuration.sandbox_policy.set(sandbox_policy)?;
next_configuration.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
next_configuration.sandbox_policy.get(),
&next_configuration.cwd,
&self.file_system_sandbox_policy,
);
next_configuration.network_sandbox_policy =
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
} else if cwd_changed && file_system_policy_matches_legacy {
// Preserve richer split policies across cwd-only updates; only
// rederive when the session is already using the legacy bridge.
@@ -191,6 +213,7 @@ pub(crate) struct SessionSettingsUpdate {
pub(crate) approval_policy: Option<AskForApproval>,
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
pub(crate) sandbox_policy: Option<SandboxPolicy>,
pub(crate) permission_profile: Option<PermissionProfile>,
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
pub(crate) collaboration_mode: Option<CollaborationMode>,
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
@@ -747,6 +770,7 @@ impl Session {
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
permission_profile: Some(session_configuration.permission_profile()),
cwd: session_configuration.cwd.clone(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
history_log_id,
+13
View File
@@ -1677,6 +1677,7 @@ async fn fork_startup_context_then_first_turn_diff_snapshot() -> anyhow::Result<
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -1735,6 +1736,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
@@ -3534,6 +3536,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
panic!("expected request_permissions event");
};
assert_eq!(request.call_id, call_id);
assert_eq!(request.cwd, Some(turn_context.cwd.clone()));
session
.notify_request_permissions_response(&request.call_id, expected_response.clone())
@@ -3748,6 +3751,7 @@ fn op_kind_distinguishes_turn_ops() {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -3787,6 +3791,7 @@ async fn user_turn_updates_approvals_reviewer() {
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::GuardianSubagent),
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: turn_context.model_info.slug.clone(),
effort: config.model_reasoning_effort,
summary: config.model_reasoning_summary,
@@ -5261,6 +5266,10 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy()
let item = turn_context.to_turn_context_item();
assert_eq!(item.file_system_sandbox_policy, None);
assert_eq!(
item.permission_profile,
Some(turn_context.permission_profile())
);
}
#[tokio::test]
@@ -5275,6 +5284,10 @@ async fn turn_context_item_stores_split_file_system_sandbox_policy_when_differen
item.file_system_sandbox_policy,
Some(file_system_sandbox_policy)
);
assert_eq!(
item.permission_profile,
Some(turn_context.permission_profile())
);
}
#[tokio::test]
+14 -4
View File
@@ -1,6 +1,7 @@
use super::*;
use codex_model_provider::SharedModelProvider;
use codex_model_provider::create_model_provider;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
pub(super) fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> bool {
matches!(
@@ -73,6 +74,13 @@ pub(crate) struct TurnContext {
pub(crate) turn_timing_state: Arc<TurnTimingState>,
}
impl TurnContext {
pub(crate) fn permission_profile(&self) -> PermissionProfile {
PermissionProfile::from_runtime_permissions(
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
)
}
pub(crate) fn model_context_window(&self) -> Option<i64> {
let effective_context_window_percent = self.model_info.effective_context_window_percent;
self.model_info
@@ -209,17 +217,18 @@ impl TurnContext {
&self,
additional_permissions: Option<PermissionProfile>,
) -> FileSystemSandboxContext {
let base_permissions = self.permission_profile();
let permissions =
merge_permission_profiles(Some(&base_permissions), additional_permissions.as_ref())
.unwrap_or(base_permissions);
FileSystemSandboxContext {
sandbox_policy: self.sandbox_policy.get().clone(),
sandbox_policy_cwd: Some(self.cwd.clone()),
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
permissions,
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: self.features.use_legacy_landlock(),
additional_permissions,
}
}
@@ -251,6 +260,7 @@ impl TurnContext {
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
sandbox_policy: self.sandbox_policy.get().clone(),
permission_profile: Some(self.permission_profile()),
network: self.turn_context_network_item(),
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
model: self.model_info.slug.clone(),
@@ -259,6 +259,7 @@ async fn effective_patch_permissions(
);
let effective_additional_permissions = apply_granted_turn_permissions(
session,
turn.cwd.as_path(),
crate::sandboxing::SandboxPermissions::UseDefault,
write_permissions_for_paths(&file_paths, &file_system_sandbox_policy, &turn.cwd),
)
+2 -1
View File
@@ -169,6 +169,7 @@ pub(super) fn implicit_granted_permissions(
pub(super) async fn apply_granted_turn_permissions(
session: &Session,
cwd: &std::path::Path,
sandbox_permissions: SandboxPermissions,
additional_permissions: Option<PermissionProfile>,
) -> EffectiveAdditionalPermissions {
@@ -192,7 +193,7 @@ pub(super) async fn apply_granted_turn_permissions(
);
let permissions_preapproved = match (effective_permissions.as_ref(), granted_permissions) {
(Some(effective_permissions), Some(granted_permissions)) => {
intersect_permission_profiles(effective_permissions.clone(), granted_permissions)
intersect_permission_profiles(effective_permissions.clone(), granted_permissions, cwd)
== *effective_permissions
}
_ => false,
@@ -424,6 +424,7 @@ impl ShellHandler {
let requested_additional_permissions = additional_permissions.clone();
let effective_additional_permissions = apply_granted_turn_permissions(
session.as_ref(),
turn.cwd.as_path(),
exec_params.sandbox_permissions,
additional_permissions,
)
@@ -230,6 +230,7 @@ impl ToolHandler for UnifiedExecHandler {
let requested_additional_permissions = additional_permissions.clone();
let effective_additional_permissions = apply_granted_turn_permissions(
context.session.as_ref(),
context.turn.cwd.as_path(),
sandbox_permissions,
additional_permissions,
)
@@ -22,7 +22,6 @@ use codex_protocol::error::SandboxErr;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::exec_output::StreamOutput;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
@@ -32,6 +31,7 @@ use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use codex_utils_absolute_path::AbsolutePathBuf;
use futures::future::BoxFuture;
use std::path::PathBuf;
@@ -75,22 +75,18 @@ impl ApplyPatchRuntime {
return None;
}
let legacy_file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
attempt.policy,
attempt.sandbox_cwd,
let base_permissions = PermissionProfile::from_runtime_permissions(
attempt.file_system_policy,
attempt.network_policy,
);
let file_system_sandbox_policy = (attempt.file_system_policy
!= &legacy_file_system_sandbox_policy)
.then(|| attempt.file_system_policy.clone());
let permissions =
merge_permission_profiles(Some(&base_permissions), req.additional_permissions.as_ref())
.unwrap_or(base_permissions);
Some(FileSystemSandboxContext {
sandbox_policy: attempt.policy.clone(),
sandbox_policy_cwd: Some(attempt.sandbox_cwd.clone()),
file_system_sandbox_policy,
permissions,
windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
use_legacy_landlock: attempt.use_legacy_landlock,
additional_permissions: req.additional_permissions.clone(),
})
}
@@ -3,15 +3,13 @@ use crate::tools::sandboxing::SandboxAttempt;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxType;
use codex_sandboxing::policy_transforms::merge_permission_profiles;
use core_test_support::PathBufExt;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
@@ -102,12 +100,7 @@ fn file_system_sandbox_context_uses_active_attempt() {
permissions_preapproved: false,
};
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let mut file_system_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, path.as_path());
file_system_policy.entries.push(FileSystemSandboxEntry {
path: FileSystemPath::Path { path: path.clone() },
access: FileSystemAccessMode::None,
});
let file_system_policy = FileSystemSandboxPolicy::from(&sandbox_policy);
let manager = SandboxManager::new();
let attempt = SandboxAttempt {
sandbox: SandboxType::MacosSeatbelt,
@@ -126,13 +119,16 @@ fn file_system_sandbox_context_uses_active_attempt() {
let sandbox = ApplyPatchRuntime::file_system_sandbox_context_for_attempt(&req, &attempt)
.expect("sandbox context");
assert_eq!(sandbox.sandbox_policy, sandbox_policy);
assert_eq!(sandbox.sandbox_policy_cwd, Some(path.clone()));
assert_eq!(
sandbox.file_system_sandbox_policy,
Some(file_system_policy.clone())
let base_permissions = PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
);
assert_eq!(sandbox.additional_permissions, Some(additional_permissions));
let Some(expected_permissions) =
merge_permission_profiles(Some(&base_permissions), Some(&additional_permissions))
else {
panic!("merged permissions should not be empty");
};
assert_eq!(sandbox.permissions, expected_permissions);
assert_eq!(
sandbox.windows_sandbox_level,
WindowsSandboxLevel::RestrictedToken
@@ -141,47 +137,6 @@ fn file_system_sandbox_context_uses_active_attempt() {
assert_eq!(sandbox.use_legacy_landlock, true);
}
#[test]
fn file_system_sandbox_context_omits_legacy_equivalent_policy() {
let path = std::env::temp_dir()
.join("apply-patch-runtime-legacy-equivalent.txt")
.abs();
let req = ApplyPatchRequest {
action: ApplyPatchAction::new_add_for_test(&path, "hello".to_string()),
file_paths: vec![path.clone()],
changes: HashMap::new(),
exec_approval_requirement: ExecApprovalRequirement::Skip {
bypass_sandbox: false,
proposed_execpolicy_amendment: None,
},
additional_permissions: None,
permissions_preapproved: false,
};
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let file_system_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy(&sandbox_policy, path.as_path());
let manager = SandboxManager::new();
let attempt = SandboxAttempt {
sandbox: SandboxType::MacosSeatbelt,
policy: &sandbox_policy,
file_system_policy: &file_system_policy,
network_policy: NetworkSandboxPolicy::Restricted,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &path,
codex_linux_sandbox_exe: None,
use_legacy_landlock: true,
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
windows_sandbox_private_desktop: true,
};
let sandbox = ApplyPatchRuntime::file_system_sandbox_context_for_attempt(&req, &attempt)
.expect("sandbox context");
assert_eq!(sandbox.sandbox_policy_cwd, Some(path));
assert_eq!(sandbox.file_system_sandbox_policy, None);
}
#[test]
fn no_sandbox_attempt_has_no_file_system_context() {
let path = std::env::temp_dir()
@@ -53,8 +53,8 @@ use codex_shell_escalation::EscalationPolicy;
use codex_shell_escalation::EscalationSession;
use codex_shell_escalation::ExecParams;
use codex_shell_escalation::ExecResult;
use codex_shell_escalation::Permissions as EscalatedPermissions;
use codex_shell_escalation::PreparedExec;
use codex_shell_escalation::ResolvedPermissionProfile;
use codex_shell_escalation::ShellCommandExecutor;
use codex_shell_escalation::Stopwatch;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -369,13 +369,17 @@ impl CoreShellActionProvider {
.map(|_| {
// Shell request additional permissions were already normalized and
// merged into the first-attempt sandbox policy.
EscalationExecution::Permissions(EscalationPermissions::Permissions(
EscalatedPermissions {
sandbox_policy: sandbox_policy.clone(),
file_system_sandbox_policy: file_system_sandbox_policy.clone(),
network_sandbox_policy,
},
))
EscalationExecution::Permissions(
EscalationPermissions::ResolvedPermissionProfile(
ResolvedPermissionProfile {
permission_profile: PermissionProfile::from_runtime_permissions(
file_system_sandbox_policy,
network_sandbox_policy,
),
sandbox_policy: sandbox_policy.clone(),
},
),
)
})
.unwrap_or(EscalationExecution::TurnDefault),
}
@@ -841,9 +845,9 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
additional_permissions: None,
})?
}
EscalationExecution::Permissions(EscalationPermissions::PermissionProfile(
permission_profile,
)) => {
EscalationExecution::Permissions(
EscalationPermissions::AdditionalPermissionProfile(permission_profile),
) => {
// Merge additive permissions into the existing turn/request sandbox policy.
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command,
@@ -855,15 +859,19 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
additional_permissions: Some(permission_profile),
})?
}
EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => {
// Use a fully specified sandbox policy instead of merging into the turn policy.
EscalationExecution::Permissions(EscalationPermissions::ResolvedPermissionProfile(
permissions,
)) => {
// Use a fully specified permission profile instead of merging into the turn policy.
let (file_system_sandbox_policy, network_sandbox_policy) =
permissions.permission_profile.to_runtime_permissions();
self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command,
workdir,
env,
sandbox_policy: &permissions.sandbox_policy,
file_system_sandbox_policy: &permissions.file_system_sandbox_policy,
network_sandbox_policy: permissions.network_sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy,
additional_permissions: None,
})?
}
@@ -33,7 +33,7 @@ use codex_sandboxing::SandboxType;
use codex_shell_escalation::EscalationExecution;
use codex_shell_escalation::EscalationPermissions;
use codex_shell_escalation::ExecResult;
use codex_shell_escalation::Permissions as EscalatedPermissions;
use codex_shell_escalation::ResolvedPermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use serde_json::Value;
@@ -317,11 +317,13 @@ fn shell_request_escalation_execution_is_explicit() {
network_sandbox_policy,
Some(&requested_permissions),
),
EscalationExecution::Permissions(EscalationPermissions::Permissions(
EscalatedPermissions {
EscalationExecution::Permissions(EscalationPermissions::ResolvedPermissionProfile(
ResolvedPermissionProfile {
permission_profile: PermissionProfile::from_runtime_permissions(
&file_system_sandbox_policy,
network_sandbox_policy,
),
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
},
)),
);
+1
View File
@@ -625,6 +625,7 @@ impl TestCodex {
approval_policy,
approvals_reviewer: None,
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -366,6 +366,7 @@ async fn apply_patch_cli_move_without_content_change_has_no_turn_diff(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1003,6 +1004,7 @@ async fn apply_patch_custom_tool_streaming_emits_updated_changes() -> Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
@@ -1100,6 +1102,7 @@ async fn apply_patch_shell_command_heredoc_with_cd_emits_turn_diff() -> Result<(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1184,6 +1187,7 @@ async fn apply_patch_shell_command_failure_propagates_error_and_skips_diff() ->
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1339,6 +1343,7 @@ async fn apply_patch_emits_turn_diff_event_with_unified_diff(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1406,6 +1411,7 @@ async fn apply_patch_turn_diff_for_rename_with_content_change(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1482,6 +1488,7 @@ async fn apply_patch_aggregates_diff_across_multiple_tool_calls() -> Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -1558,6 +1565,7 @@ async fn apply_patch_aggregates_diff_preserves_success_after_failure() -> Result
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
+3 -1
View File
@@ -1,6 +1,7 @@
#![allow(clippy::unwrap_used, clippy::expect_used)]
use anyhow::Result;
use codex_config::types::ApprovalsReviewer;
use codex_core::CodexThread;
use codex_core::config::Constrained;
use codex_core::config_loader::ConfigLayerStack;
@@ -591,8 +592,9 @@ async fn submit_turn(
final_output_json_schema: None,
cwd: test.cwd.path().to_path_buf(),
approval_policy,
approvals_reviewer: None,
approvals_reviewer: Some(ApprovalsReviewer::User),
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
+2
View File
@@ -1646,6 +1646,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: session_configured.model.clone(),
effort: Some(ReasoningEffort::Low),
summary: Some(
@@ -1765,6 +1766,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: session_configured.model,
effort: None,
summary: Some(ReasoningSummary::Concise),
+1
View File
@@ -2617,6 +2617,7 @@ text(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
@@ -127,6 +127,7 @@ async fn user_input_includes_collaboration_instructions_after_override() -> Resu
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -182,6 +183,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: Some(
@@ -226,6 +228,7 @@ async fn override_then_next_turn_uses_updated_collaboration_instructions() -> Re
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -279,6 +282,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -299,6 +303,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: test.config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: Some(
@@ -350,6 +355,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -378,6 +384,7 @@ async fn collaboration_mode_update_emits_new_instruction_message() -> Result<()>
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -435,6 +442,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -463,6 +471,7 @@ async fn collaboration_mode_update_noop_does_not_append() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -519,6 +528,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -550,6 +560,7 @@ async fn collaboration_mode_update_emits_new_instruction_message_when_mode_chang
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -610,6 +621,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -641,6 +653,7 @@ async fn collaboration_mode_update_noop_does_not_append_when_mode_is_unchanged()
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -707,6 +720,7 @@ async fn resume_replays_collaboration_instructions() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -772,6 +786,7 @@ async fn empty_collaboration_instructions_are_ignored() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
+8
View File
@@ -1674,6 +1674,7 @@ async fn auto_compact_runs_after_resume_when_token_usage_is_over_limit() {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: resumed.session_configured.model.clone(),
effort: None,
summary: None,
@@ -1765,6 +1766,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: previous_model.to_string(),
effort: None,
summary: None,
@@ -1790,6 +1792,7 @@ async fn pre_sampling_compact_runs_on_switch_to_smaller_context_model() {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: next_model.to_string(),
effort: None,
summary: None,
@@ -1901,6 +1904,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: previous_model.to_string(),
effort: None,
summary: None,
@@ -1950,6 +1954,7 @@ async fn pre_sampling_compact_runs_after_resume_and_switch_to_smaller_model() {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: next_model.to_string(),
effort: None,
summary: None,
@@ -3053,6 +3058,7 @@ async fn snapshot_request_shape_pre_turn_compaction_including_incoming_user_mess
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -3171,6 +3177,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: previous_model.to_string(),
effort: None,
summary: None,
@@ -3196,6 +3203,7 @@ async fn snapshot_request_shape_pre_turn_compaction_strips_incoming_model_switch
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: next_model.to_string(),
effort: None,
summary: None,
@@ -2070,6 +2070,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_including_incoming_us
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -2183,6 +2184,7 @@ async fn snapshot_request_shape_remote_pre_turn_compaction_strips_incoming_model
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(next_model.to_string()),
effort: None,
@@ -575,6 +575,7 @@ async fn snapshot_rollback_followup_turn_trims_context_updates() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
+2
View File
@@ -53,6 +53,7 @@ async fn submit_user_turn(
approval_policy,
approvals_reviewer: None,
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -134,6 +135,7 @@ async fn execpolicy_blocks_shell_invocation() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -125,6 +125,7 @@ async fn copy_paste_local_image_persists_rollout_request_shape() -> anyhow::Resu
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -212,6 +213,7 @@ async fn drag_drop_image_persists_rollout_request_shape() -> anyhow::Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
+5
View File
@@ -532,6 +532,7 @@ async fn plan_mode_emits_plan_item_from_proposed_plan_block() -> anyhow::Result<
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_configured.model.clone(),
effort: None,
summary: None,
@@ -609,6 +610,7 @@ async fn plan_mode_strips_plan_from_agent_messages() -> anyhow::Result<()> {
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_configured.model.clone(),
effort: None,
summary: None,
@@ -718,6 +720,7 @@ async fn plan_mode_streaming_citations_are_stripped_across_added_deltas_and_done
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_configured.model.clone(),
effort: None,
summary: None,
@@ -905,6 +908,7 @@ async fn plan_mode_streaming_proposed_plan_tag_split_across_added_and_delta_is_p
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_configured.model.clone(),
effort: None,
summary: None,
@@ -1019,6 +1023,7 @@ async fn plan_mode_handles_missing_plan_close_tag() -> anyhow::Result<()> {
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_configured.model.clone(),
effort: None,
summary: None,
+1
View File
@@ -82,6 +82,7 @@ async fn codex_returns_json_result(model: String) -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
+1
View File
@@ -63,6 +63,7 @@ async fn submit_skill_turn(test: &TestCodex, skill_path: PathBuf, prompt: &str)
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -30,6 +30,7 @@ async fn override_turn_context_does_not_persist_when_config_exists() {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some("o3".to_string()),
effort: Some(Some(ReasoningEffort::High)),
@@ -68,6 +69,7 @@ async fn override_turn_context_does_not_create_config_file() {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some("o3".to_string()),
effort: Some(Some(ReasoningEffort::Medium)),
@@ -130,6 +130,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -146,6 +147,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(next_model.to_string()),
effort: None,
@@ -167,6 +169,7 @@ async fn model_change_appends_model_instructions_developer_message() -> Result<(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: next_model.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -227,6 +230,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -243,6 +247,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(next_model.to_string()),
effort: None,
@@ -264,6 +269,7 @@ async fn model_and_personality_change_only_appends_model_instructions() -> Resul
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: next_model.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -406,6 +412,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -427,6 +434,7 @@ async fn model_change_from_image_to_text_strips_prior_image_content() -> Result<
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: text_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -535,6 +543,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -556,6 +565,7 @@ async fn generated_image_is_replayed_for_image_capable_models() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -667,6 +677,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -688,6 +699,7 @@ async fn model_change_from_generated_image_to_text_preserves_prior_generated_ima
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: text_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -801,6 +813,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -830,6 +843,7 @@ async fn thread_rollback_after_generated_image_drops_entire_image_turn_history()
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: image_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
service_tier: None,
@@ -987,6 +1001,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: large_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -1025,6 +1040,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(smaller_model_slug.to_string()),
effort: None,
@@ -1046,6 +1062,7 @@ async fn model_switch_to_smaller_model_updates_token_context_window() -> Result<
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: smaller_model_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -123,6 +123,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -147,6 +148,7 @@ async fn snapshot_model_visible_layout_turn_overrides() -> Result<()> {
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -226,6 +228,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -250,6 +253,7 @@ async fn snapshot_model_visible_layout_cwd_change_does_not_refresh_agents() -> R
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -361,6 +365,7 @@ async fn snapshot_model_visible_layout_resume_with_personality_change() -> Resul
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: resumed.session_configured.model.clone(),
effort: resumed.config.model_reasoning_effort,
summary: None,
@@ -451,6 +456,7 @@ async fn snapshot_model_visible_layout_resume_override_matches_rollout_model() -
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some("gpt-5.2".to_string()),
effort: None,
@@ -99,6 +99,7 @@ async fn renews_cache_ttl_on_matching_models_etag() -> Result<()> {
approval_policy: codex_protocol::protocol::AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
@@ -110,6 +110,7 @@ async fn refresh_models_on_models_etag_mismatch_and_avoid_duplicate_models_fetch
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
+30 -6
View File
@@ -1132,7 +1132,11 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("user_approved_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"user_approved_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -1200,7 +1204,11 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("user_approved_session_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"user_approved_session_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -1267,7 +1275,11 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("sandbox_retry_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"sandbox_retry_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -1334,7 +1346,11 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("user_denied_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"user_denied_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -1401,7 +1417,11 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("sandbox_session_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"sandbox_session_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -1468,7 +1488,11 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
mount_sse_once(
&server,
sse(vec![
ev_local_shell_call("sandbox_deny_call", "completed", vec!["/bin/date"]),
ev_local_shell_call(
"sandbox_deny_call",
"completed",
vec!["/usr/bin/touch", "codex-otel-approval-test"],
),
ev_completed("done"),
]),
)
@@ -118,6 +118,7 @@ async fn override_turn_context_without_user_turn_does_not_record_permissions_upd
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -160,6 +161,7 @@ async fn override_turn_context_without_user_turn_does_not_record_environment_upd
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -199,6 +201,7 @@ async fn override_turn_context_without_user_turn_does_not_record_collaboration_u
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -118,6 +118,7 @@ async fn submit_danger_full_access_user_turn(test: &TestCodex, text: &str) {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
@@ -1,6 +1,7 @@
use anyhow::Result;
use codex_core::ForkSnapshot;
use codex_core::config::Constrained;
use codex_core::config_loader::ConfigLayerStack;
use codex_execpolicy::Policy;
use codex_protocol::models::DeveloperInstructions;
use codex_protocol::protocol::AskForApproval;
@@ -102,6 +103,7 @@ async fn permissions_message_added_on_override_change() -> Result<()> {
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -230,6 +232,7 @@ async fn permissions_message_omitted_when_disabled() -> Result<()> {
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -316,6 +319,7 @@ async fn resume_replays_permissions_messages() -> Result<()> {
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -418,6 +422,7 @@ async fn resume_and_fork_append_permissions_messages() -> Result<()> {
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -532,6 +537,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
config.config_layer_stack = ConfigLayerStack::default();
});
let test = builder.build(&server).await?;
+17
View File
@@ -105,6 +105,7 @@ async fn user_turn_personality_none_does_not_add_update_message() -> anyhow::Res
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -156,6 +157,7 @@ async fn config_personality_some_sets_instructions_template() -> anyhow::Result<
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -214,6 +216,7 @@ async fn config_personality_none_sends_no_personality() -> anyhow::Result<()> {
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -278,6 +281,7 @@ async fn default_personality_is_pragmatic_without_config_toml() -> anyhow::Resul
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -330,6 +334,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -347,6 +352,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -368,6 +374,7 @@ async fn user_turn_personality_some_adds_update_message() -> anyhow::Result<()>
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -435,6 +442,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -452,6 +460,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -473,6 +482,7 @@ async fn user_turn_personality_same_value_does_not_add_update_message() -> anyho
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -553,6 +563,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -570,6 +581,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -591,6 +603,7 @@ async fn user_turn_personality_skips_if_feature_disabled() -> anyhow::Result<()>
approval_policy: test.config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -710,6 +723,7 @@ async fn remote_model_friendly_personality_instructions_with_feature() -> anyhow
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: remote_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -831,6 +845,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: remote_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -848,6 +863,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
@@ -869,6 +885,7 @@ async fn user_turn_personality_remote_model_template_includes_update_message() -
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: remote_slug.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -437,6 +437,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() -> an
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: Some(new_policy.clone()),
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: Some(Some(ReasoningEffort::High)),
@@ -521,6 +522,7 @@ async fn override_before_first_turn_emits_environment_context() -> anyhow::Resul
approval_policy: Some(AskForApproval::Never),
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some("gpt-5.4".to_string()),
effort: Some(Some(ReasoningEffort::Low)),
@@ -715,6 +717,7 @@ async fn per_turn_overrides_keep_cached_prefix_and_key_constant() -> anyhow::Res
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: new_policy.clone(),
permission_profile: None,
model: "o3".to_string(),
effort: Some(ReasoningEffort::High),
summary: Some(ReasoningSummary::Detailed),
@@ -828,6 +831,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
approval_policy: default_approval_policy,
approvals_reviewer: None,
sandbox_policy: default_sandbox_policy.clone(),
permission_profile: None,
model: default_model.clone(),
effort: default_effort,
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
@@ -849,6 +853,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
approval_policy: default_approval_policy,
approvals_reviewer: None,
sandbox_policy: default_sandbox_policy.clone(),
permission_profile: None,
model: default_model.clone(),
effort: default_effort,
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
@@ -954,6 +959,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
approval_policy: default_approval_policy,
approvals_reviewer: None,
sandbox_policy: default_sandbox_policy.clone(),
permission_profile: None,
model: default_model,
effort: default_effort,
summary: Some(default_summary.unwrap_or(ReasoningSummary::Auto)),
@@ -975,6 +981,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: "o3".to_string(),
effort: Some(ReasoningEffort::High),
summary: Some(ReasoningSummary::Detailed),
@@ -171,6 +171,7 @@ async fn remote_models_config_context_window_override_clamps_to_max_context_wind
summary: None,
service_tier: None,
collaboration_mode: None,
permission_profile: None,
personality: None,
})
.await?;
@@ -247,6 +248,7 @@ async fn remote_models_config_override_above_max_uses_max_context_window() -> Re
summary: None,
service_tier: None,
collaboration_mode: None,
permission_profile: None,
personality: None,
})
.await?;
@@ -322,6 +324,7 @@ async fn remote_models_use_context_window_when_config_override_is_absent() -> Re
summary: None,
service_tier: None,
collaboration_mode: None,
permission_profile: None,
personality: None,
})
.await?;
@@ -405,6 +408,7 @@ async fn remote_models_long_model_slug_is_sent_with_high_reasoning() -> Result<(
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: requested_model.to_string(),
effort: None,
summary: None,
@@ -464,6 +468,7 @@ async fn namespaced_model_slug_uses_catalog_metadata_without_fallback_warning()
approval_policy: config.permissions.approval_policy.value(),
approvals_reviewer: None,
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
model: requested_model.to_string(),
effort: None,
summary: Some(
@@ -590,6 +595,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(REMOTE_MODEL_SLUG.to_string()),
effort: None,
@@ -630,6 +636,7 @@ async fn remote_models_remote_model_uses_unified_exec() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: REMOTE_MODEL_SLUG.to_string(),
effort: None,
summary: Some(ReasoningSummary::Auto),
@@ -834,6 +841,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some(model.to_string()),
effort: None,
@@ -855,6 +863,7 @@ async fn remote_models_apply_remote_base_instructions() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: model.to_string(),
effort: None,
summary: Some(ReasoningSummary::Auto),
@@ -196,6 +196,7 @@ async fn submit_turn(
approval_policy,
approvals_reviewer: Some(ApprovalsReviewer::User),
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1586,17 +1587,17 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
.unwrap_or_else(|| panic!("expected filesystem permissions"));
let (approval_reads, approval_writes) = approval_file_system
.legacy_read_write_roots()
.unwrap_or_default();
.unwrap_or_else(|| panic!("expected legacy-compatible permissions"));
assert!(approval_reads.as_ref().is_none_or(Vec::is_empty));
let mut approval_writes = approval_writes.unwrap_or_default();
approval_writes.sort_by_key(|path| path.display().to_string());
let (_expected_reads, expected_writes) = merged_permissions
let (_, expected_writes) = merged_permissions
.file_system
.unwrap_or_else(|| panic!("expected merged filesystem permissions"))
.legacy_read_write_roots()
.unwrap_or_default();
.unwrap_or_else(|| panic!("expected legacy-compatible permissions"));
let mut expected_writes = expected_writes.unwrap_or_default();
expected_writes.sort_by_key(|path| path.display().to_string());
@@ -147,6 +147,7 @@ async fn submit_turn(
approval_policy,
approvals_reviewer: None,
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -140,6 +140,7 @@ async fn request_user_input_round_trip_for_mode(mode: ModeKind) -> anyhow::Resul
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -258,6 +259,7 @@ where
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -143,6 +143,7 @@ async fn submit_turn_with_timeout(test: &TestCodex, prompt: &str) -> Result<()>
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
},
permission_profile: None,
model: session_model,
effort: None,
summary: None,
+1
View File
@@ -422,6 +422,7 @@ async fn resume_model_switch_is_not_duplicated_after_pre_turn_override() -> Resu
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: Some("gpt-5.4".to_string()),
effort: None,
@@ -33,6 +33,7 @@ fn resume_history(
timezone: None,
approval_policy: config.permissions.approval_policy.value(),
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
model: previous_model.to_string(),
+1
View File
@@ -838,6 +838,7 @@ async fn review_uses_overridden_cwd_for_base_branch_merge_base() {
approval_policy: None,
approvals_reviewer: None,
sandbox_policy: None,
permission_profile: None,
windows_sandbox_level: None,
model: None,
effort: None,
+9
View File
@@ -423,6 +423,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -791,6 +792,7 @@ async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -923,6 +925,7 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1022,6 +1025,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1173,6 +1177,7 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1410,6 +1415,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: text_only_model_slug.to_string(),
effort: None,
summary: None,
@@ -1518,6 +1524,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1887,6 +1894,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -2103,6 +2111,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -47,6 +47,7 @@ async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() ->
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: REQUESTED_MODEL.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -146,6 +147,7 @@ async fn response_model_field_mismatch_emits_warning_when_header_matches_request
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: REQUESTED_MODEL.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -232,6 +234,7 @@ async fn openai_model_header_mismatch_only_emits_one_warning_per_turn() -> Resul
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: REQUESTED_MODEL.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -282,6 +285,7 @@ async fn openai_model_header_casing_only_mismatch_does_not_warn() -> Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: REQUESTED_MODEL.to_string(),
effort: test.config.model_reasoning_effort,
summary: None,
@@ -166,6 +166,7 @@ async fn run_snapshot_command_with_options(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -257,6 +258,7 @@ async fn run_shell_command_snapshot_with_options(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -328,6 +330,7 @@ async fn run_tool_turn_on_harness(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -563,6 +566,7 @@ async fn shell_command_snapshot_still_intercepts_apply_patch() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model,
effort: None,
summary: None,
@@ -53,6 +53,7 @@ async fn submit_turn_with_policies(
approval_policy,
approvals_reviewer: None,
sandbox_policy,
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
+1
View File
@@ -113,6 +113,7 @@ async fn user_turn_includes_skill_instructions() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -406,6 +406,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result<
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: test.session_configured.model.clone(),
effort: None,
summary: None,
@@ -87,6 +87,7 @@ async fn shell_tool_executes_command_and_streams_output() -> anyhow::Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -158,6 +159,7 @@ async fn update_plan_tool_emits_plan_update_event() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -239,6 +241,7 @@ async fn update_plan_tool_rejects_malformed_payload() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -335,6 +338,7 @@ async fn apply_patch_tool_executes_and_emits_patch_events() -> anyhow::Result<()
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -439,6 +443,7 @@ async fn apply_patch_reports_parse_diagnostics() -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -44,6 +44,7 @@ async fn run_turn(test: &TestCodex, prompt: &str) -> anyhow::Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -361,6 +362,7 @@ async fn shell_tools_start_before_response_completed_when_stream_delayed() -> an
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
+1
View File
@@ -509,6 +509,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -175,6 +175,7 @@ async fn submit_unified_exec_turn(
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -259,6 +260,7 @@ async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1749,6 +1751,7 @@ async fn unified_exec_keeps_long_running_session_after_turn_end() -> Result<()>
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -1842,6 +1845,7 @@ async fn unified_exec_interrupt_preserves_long_running_session() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -2315,6 +2319,7 @@ async fn unified_exec_runs_under_sandbox() -> Result<()> {
approvals_reviewer: None,
// Important!
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -2423,6 +2428,7 @@ async fn unified_exec_enforces_glob_deny_read_policy() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: read_only_policy,
permission_profile: None,
model: session_model,
effort: None,
summary: None,
@@ -2551,6 +2557,7 @@ async fn unified_exec_python_prompt_under_seatbelt() -> Result<()> {
approval_policy: AskForApproval::Never,
approvals_reviewer: None,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
model: session_model,
effort: None,
summary: None,

Some files were not shown because too many files have changed in this diff Show More